# Add new texture loading system with automatic compression.

Replace almost all texture uses with calls to the new system.
Add some anistropic filtering to terrain textures.
Let Atlas load terrain texture previews partly-asynchronously by
polling.
Fix inefficient texture colour determination for minimap.
Remove unused global g_TerrainModified.
Change GUI texcoord computation to be less efficient but to cope with
dynamic texture changes.
Fix GUI renderer effects leaving bogus colour state.

This was SVN commit r8099.
This commit is contained in:
Ykkrosh 2010-09-10 21:02:10 +00:00
parent b15c5c1c45
commit 67a94572ec
47 changed files with 2288 additions and 645 deletions

View File

@ -69,14 +69,64 @@ function TerrainPreviewPage(panel, name)
{ {
this.panel = panel; this.panel = panel;
this.name = name; this.name = name;
// Size of texture preview images
this.w = 120
this.h = 40;
this.previewReloadTimer = null;
} }
TerrainPreviewPage.prototype = { TerrainPreviewPage.prototype = {
reloadPreviews: function() {
this.scrolled.destroyChildren();
this.itemSizer.clear();
// TODO: Do something clever like load the preview images asynchronously,
// to avoid the annoying freeze when switching tabs
var previews = Atlas.Message.GetTerrainGroupPreviews(this.name, this.w, this.h).previews;
var i = 0;
var names = [];
var allLoaded = true;
for each (var p in previews)
{
if (!p.loaded)
allLoaded = false;
// Create a wrapped-text label (replacing '_' with ' ' so there are more wrapping opportunities)
var labelText = p.name.replace(/_/g, ' ');
var label = new wxStaticText(this.scrolled, -1, labelText, wxDefaultPosition, wxDefaultSize, wxStaticText.ALIGN_CENTER);
label.wrap(this.w);
var imgSizer = new wxBoxSizer(wxOrientation.VERTICAL);
var button = new wxBitmapButton(this.scrolled, -1, p.imagedata);
button.terrainName = p.name;
button.onClicked = onTerrainSelect;
imgSizer.add(button, 0, wxAlignment.CENTRE);
imgSizer.add(label, 1, wxAlignment.CENTRE);
this.itemSizer.add(imgSizer, 0, wxAlignment.CENTRE | wxStretch.EXPAND);
}
this.panel.layout();
// If not all textures were loaded yet, run a timer to reload the previews
// every so often until they've all finished
if (allLoaded && this.previewReloadTimer)
{
this.previewReloadTimer.stop();
this.previewReloadTimer = null;
}
else if (!allLoaded && !this.previewReloadTimer)
{
this.previewReloadTimer = new wxTimer();
var self = this;
this.previewReloadTimer.onNotify = function() { self.reloadPreviews(); };
this.previewReloadTimer.start(2000);
}
},
display: function() { display: function() {
if (this.loaded) if (this.loaded)
return; return;
// Size of texture preview images
var w = 120, h = 40;
this.panel.sizer = new wxBoxSizer(wxOrientation.VERTICAL); this.panel.sizer = new wxBoxSizer(wxOrientation.VERTICAL);
var scrolled = new wxScrolledWindow(this.panel, -1, wxDefaultPosition, wxDefaultSize, wxWindow.VSCROLL); var scrolled = new wxScrolledWindow(this.panel, -1, wxDefaultPosition, wxDefaultSize, wxWindow.VSCROLL);
@ -89,36 +139,17 @@ TerrainPreviewPage.prototype = {
// Adjust the number of columns to fit in the available area // Adjust the number of columns to fit in the available area
scrolled.onSize = function (evt) { scrolled.onSize = function (evt) {
var numCols = Math.max(1, Math.floor(evt.size.width / (w+16))); var numCols = Math.max(1, Math.floor(evt.size.width / (this.w+16)));
if (itemSizer.cols != numCols) if (itemSizer.cols != numCols)
itemSizer.cols = numCols; itemSizer.cols = numCols;
}; };
// TODO: Do something clever like load the preview images asynchronously, this.scrolled = scrolled;
// to avoid the annoying freeze when switching tabs this.itemSizer = itemSizer;
var previews = Atlas.Message.GetTerrainGroupPreviews(this.name, w, h).previews; this.reloadPreviews();
var i = 0;
var names = [];
for each (var p in previews)
{
// Create a wrapped-text label (replacing '_' with ' ' so there are more wrapping opportunities)
var labelText = p.name.replace(/_/g, ' ');
var label = new wxStaticText(scrolled, -1, labelText, wxDefaultPosition, wxDefaultSize, wxStaticText.ALIGN_CENTER);
label.wrap(w);
var imgSizer = new wxBoxSizer(wxOrientation.VERTICAL);
var button = new wxBitmapButton(scrolled, -1, p.imagedata);
button.terrainName = p.name;
button.onClicked = onTerrainSelect;
imgSizer.add(button, 0, wxAlignment.CENTRE);
imgSizer.add(label, 1, wxAlignment.CENTRE);
itemSizer.add(imgSizer, 0, wxAlignment.CENTRE | wxStretch.EXPAND);
}
// TODO: fix keyboard navigation of the terrain previews // TODO: fix keyboard navigation of the terrain previews
this.panel.layout();
this.loaded = true; this.loaded = true;
} }
}; };

View File

@ -48,7 +48,6 @@
#include "ps/Pyrogenesis.h" #include "ps/Pyrogenesis.h"
#include "ps/World.h" #include "ps/World.h"
#include "renderer/Renderer.h" #include "renderer/Renderer.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h" #include "renderer/WaterManager.h"
#include "scripting/ScriptableObject.h" #include "scripting/ScriptableObject.h"
#include "simulation/LOSManager.h" #include "simulation/LOSManager.h"
@ -387,7 +386,6 @@ void CGameView::RegisterInit()
RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60);
RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5);
RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80); RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80);
RegMemFun(g_Renderer.GetSingletonPtr()->GetSkyManager(), &SkyManager::LoadSkyTextures, L"LoadSkyTextures", 15);
} }

View File

@ -40,7 +40,6 @@ public:
int Priority; int Priority;
CTerrainTextureEntry* GetTextureEntry() { return Tex; } CTerrainTextureEntry* GetTextureEntry() { return Tex; }
Handle GetHandle() { return Tex ? Tex->GetHandle() : 0; }
int GetPriority() { return Priority; } int GetPriority() { return Priority; }
}; };

View File

@ -85,9 +85,7 @@ void CModel::ReleaseData()
m_Props.clear(); m_Props.clear();
m_pModelDef = CModelDefPtr(); m_pModelDef = CModelDefPtr();
Handle h = m_Texture.GetHandle(); m_Texture.reset();
ogl_tex_free(h);
m_Texture.SetHandle(0);
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -452,8 +450,6 @@ CModel* CModel::Clone() const
clone->m_ObjectBounds = m_ObjectBounds; clone->m_ObjectBounds = m_ObjectBounds;
clone->InitModel(m_pModelDef); clone->InitModel(m_pModelDef);
clone->SetTexture(m_Texture); clone->SetTexture(m_Texture);
if (m_Texture.GetHandle())
h_add_ref(m_Texture.GetHandle());
clone->SetMaterial(m_Material); clone->SetMaterial(m_Material);
clone->SetAnimation(m_Anim); clone->SetAnimation(m_Anim);
clone->SetFlags(m_Flags); clone->SetFlags(m_Flags);

View File

@ -75,7 +75,7 @@ public:
CModelDefPtr GetModelDef() { return m_pModelDef; } CModelDefPtr GetModelDef() { return m_pModelDef; }
// set the model's texture // set the model's texture
void SetTexture(const CTexture& tex) { m_Texture=tex; } void SetTexture(const CTexturePtr& tex) { m_Texture=tex; }
// set the model's material // set the model's material
void SetMaterial(const CMaterial &material); void SetMaterial(const CMaterial &material);
// set the model's player ID, recursively through props // set the model's player ID, recursively through props
@ -87,10 +87,10 @@ public:
// set the models mod color // set the models mod color
void SetShadingColor(const CColor& colour); void SetShadingColor(const CColor& colour);
// get the model's texture // get the model's texture
CTexture* GetTexture() { return &m_Texture; } CTexturePtr& GetTexture() { return m_Texture; }
// get the models material // get the model's material
CMaterial &GetMaterial() { return m_Material; } CMaterial& GetMaterial() { return m_Material; }
// get the model's texture // get the model's shading color
CColor GetShadingColor() { return m_ShadingColor; } CColor GetShadingColor() { return m_ShadingColor; }
// set the given animation as the current animation on this model // set the given animation as the current animation on this model
@ -214,7 +214,7 @@ private:
// object flags // object flags
int m_Flags; int m_Flags;
// texture used by model // texture used by model
CTexture m_Texture; CTexturePtr m_Texture;
// model's material // model's material
CMaterial m_Material; CMaterial m_Material;
// pointer to the model's raw 3d data // pointer to the model's raw 3d data

View File

@ -26,6 +26,8 @@
#include "MaterialManager.h" #include "MaterialManager.h"
#include "MeshManager.h" #include "MeshManager.h"
#include "SkeletonAnim.h" #include "SkeletonAnim.h"
#include "TextureManager.h"
#include "renderer/Renderer.h"
#include "lib/rand.h" #include "lib/rand.h"
@ -91,11 +93,16 @@ bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections
// delete old model, create new // delete old model, create new
delete m_Model; delete m_Model;
m_Model = new CModel(objectManager.GetSkeletonAnimManager()); m_Model = new CModel(objectManager.GetSkeletonAnimManager());
m_Model->SetTexture(CTexture(m_TextureName));
m_Model->SetMaterial(g_MaterialManager.LoadMaterial(m_Base->m_Material)); m_Model->SetMaterial(g_MaterialManager.LoadMaterial(m_Base->m_Material));
m_Model->InitModel(modeldef); m_Model->InitModel(modeldef);
m_Model->SetPlayerColor(m_Color); m_Model->SetPlayerColor(m_Color);
CTextureProperties textureProps(m_TextureName);
textureProps.SetWrap(GL_CLAMP_TO_EDGE);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch(); // if we've loaded this model we're probably going to render it soon, so prefetch its texture
m_Model->SetTexture(texture);
// calculate initial object space bounds, based on vertex positions // calculate initial object space bounds, based on vertex positions
m_Model->CalcObjectBounds(); m_Model->CalcObjectBounds();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -20,12 +20,14 @@
*/ */
#include "precompiled.h" #include "precompiled.h"
#include "ParticleEmitter.h" #include "ParticleEmitter.h"
#include "ParticleEngine.h" #include "ParticleEngine.h"
#include "graphics/TextureManager.h"
#include "ps/Filesystem.h" #include "ps/Filesystem.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
#include "ps/XML/Xeromyces.h" #include "ps/XML/Xeromyces.h"
#define LOG_CATEGORY L"particleSystem"
//forward declaration //forward declaration
void GetValueAndVariation(CXeromyces XeroFile, XMBElement parent, CStr& value, CStr& variation); void GetValueAndVariation(CXeromyces XeroFile, XMBElement parent, CStr& value, CStr& variation);
@ -41,7 +43,6 @@ CEmitter::CEmitter(const int MAX_PARTICLES, const int lifetime, int UNUSED(textu
m_decrementAlpha = true; m_decrementAlpha = true;
m_renderParticles = true; m_renderParticles = true;
isFinished = false; isFinished = false;
m_texture = NULL;
// init the used/open list // init the used/open list
m_usedList = NULL; m_usedList = NULL;
@ -107,7 +108,7 @@ bool CEmitter::LoadXml(const VfsPath& pathname)
if( root.GetNodeName() != el_Emitter ) if( root.GetNodeName() != el_Emitter )
{ {
LOG(CLogger::Error, LOG_CATEGORY, L"CEmitter::LoadEmitterXML: XML root was not \"Emitter\" in file %ls. Load failed.", pathname.string().c_str() ); LOGERROR(L"CEmitter::LoadXml: XML root was not \"Emitter\" in file %ls. Load failed.", pathname.string().c_str() );
return( false ); return( false );
} }
@ -151,11 +152,8 @@ bool CEmitter::LoadXml(const VfsPath& pathname)
} }
else if( settingName == el_Texture ) else if( settingName == el_Texture )
{ {
stringValue = settingElement.GetText(); CTextureProperties textureProps(CStrW(settingElement.GetText()));
m_texture = new CTexture(CStrW(stringValue)); m_texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
u32 flags = 0;
if(!(CRenderer::GetSingletonPtr()->LoadTexture(m_texture, flags)))
return false;
} }
else if( settingName == el_Size ) else if( settingName == el_Size )
{ {
@ -408,9 +406,7 @@ bool CEmitter::Render()
break; break;
} }
// Bind the texture. Use the texture assigned to this emitter. m_texture->Bind();
int unit = 0;
g_Renderer.SetTexture(unit, m_texture);
glBegin(GL_QUADS); glBegin(GL_QUADS);
{ {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -22,12 +22,11 @@
#ifndef INCLUDED_PARTICLEEMITTER #ifndef INCLUDED_PARTICLEEMITTER
#define INCLUDED_PARTICLEEMITTER #define INCLUDED_PARTICLEEMITTER
#include "graphics/Texture.h"
#include "lib/file/vfs/vfs_path.h" #include "lib/file/vfs/vfs_path.h"
#include "maths/Vector3D.h" #include "maths/Vector3D.h"
#include "ps/CStr.h" #include "ps/CStr.h"
class CTexture;
class CEmitter class CEmitter
{ {
static const int HALF_RAND = (RAND_MAX / 2); static const int HALF_RAND = (RAND_MAX / 2);
@ -89,7 +88,7 @@ protected:
tParticle* m_usedList; // linked list of used particles tParticle* m_usedList; // linked list of used particles
//Particle appearence //Particle appearence
CTexture* m_texture; // Texture CTexturePtr m_texture; // Texture
float m_size; // size of the particles (if point sprites is not enabled) float m_size; // size of the particles (if point sprites is not enabled)
tColor m_startColor, m_startColorVar; // Current color of particle tColor m_startColor, m_startColorVar; // Current color of particle
tColor m_endColor, m_endColorVar; // End color of particle tColor m_endColor, m_endColorVar; // End color of particle
@ -188,7 +187,7 @@ public:
{ {
m_pos = newPos; m_pos = newPos;
} }
void SetTexture(CTexture* id) { m_texture = id; } void SetTexture(CTexturePtr id) { m_texture = id; }
void SetIsFinished(bool finished) { isFinished = finished; } void SetIsFinished(bool finished) { isFinished = finished; }
void SetEmitterLife(int life) { m_emitterLife = life; } void SetEmitterLife(int life) { m_emitterLife = life; }
void SetLife(int newlife) { m_life = newlife; } void SetLife(int newlife) { m_life = newlife; }

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -20,8 +20,11 @@
*/ */
#include "precompiled.h" #include "precompiled.h"
#include "ParticleEngine.h" #include "ParticleEngine.h"
#include "graphics/TextureManager.h"
CParticleEngine *CParticleEngine::m_pInstance = 0; CParticleEngine *CParticleEngine::m_pInstance = 0;
CParticleEngine::CParticleEngine(void) CParticleEngine::CParticleEngine(void)
{ {
@ -76,31 +79,17 @@ void CParticleEngine::DeleteInstance()
bool CParticleEngine::InitParticleSystem() bool CParticleEngine::InitParticleSystem()
{ {
// Texture Loading // Texture Loading
CTexture pTex(L"art/textures/particles/sprite.tga"); CTextureProperties textureProps(L"art/textures/particles/sprite.tga");
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
int flags = 0; idTexture[DEFAULTTEXT] = texture;
if(!(g_Renderer.LoadTexture(&pTex, flags)))
return false;
g_Renderer.SetTexture(0, &pTex);
idTexture[DEFAULTTEXT] = pTex;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
//glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
//glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_BLEND);
//glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);
return true; return true;
} }
bool CParticleEngine::AddEmitter(CEmitter *emitter, int type, int ID) bool CParticleEngine::AddEmitter(CEmitter *emitter, int type, int ID)
{ {
emitter->SetTexture(&idTexture[type]); emitter->SetTexture(idTexture[type]);
if(m_pHead == NULL) if(m_pHead == NULL)
{ {
tEmitterNode *temp = new tEmitterNode; tEmitterNode *temp = new tEmitterNode;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -93,7 +93,7 @@ private:
CParticleEngine(void); CParticleEngine(void);
static CParticleEngine* m_pInstance; // The singleton instance static CParticleEngine* m_pInstance; // The singleton instance
CTexture idTexture[MAX_TEXTURES]; CTexturePtr idTexture[MAX_TEXTURES];
int totalParticles; // Total Amount of particles of all emitters. int totalParticles; // Total Amount of particles of all emitters.
struct tEmitterNode struct tEmitterNode

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -15,25 +15,17 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
* Billboarding sprite class - always faces the camera. It does this by
* getting the current model view matrix state.
*/
// Usage: Instantiate, then be sure to pass a loaded
// (using ogl_tex_load()) texture before calling Render().
#include "precompiled.h" #include "precompiled.h"
#include "Sprite.h" #include "Sprite.h"
#include "graphics/TextureManager.h"
#include "renderer/Renderer.h" #include "renderer/Renderer.h"
#include "lib/ogl.h" #include "lib/ogl.h"
#include "lib/res/graphics/ogl_tex.h" #include "lib/res/graphics/ogl_tex.h"
CSprite::CSprite() : CSprite::CSprite()
m_texture(NULL)
{ {
// default scale 1:1 // default scale 1:1
m_scale.X = m_scale.Y = m_scale.Z = 1.0f; m_scale.X = m_scale.Y = m_scale.Z = 1.0f;
@ -59,8 +51,8 @@ void CSprite::Render()
BeginBillboard(); BeginBillboard();
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
if ( m_texture && m_texture->GetHandle() != 0 ) if (m_texture)
(void)ogl_tex_bind(m_texture->GetHandle()); m_texture->Bind();
glColor4fv(m_colour); glColor4fv(m_colour);
@ -87,12 +79,9 @@ void CSprite::Render()
EndBillboard(); EndBillboard();
} }
int CSprite::SetTexture(CTexture *texture) void CSprite::SetTexture(const CTexturePtr& texture)
{ {
if (texture == NULL) return -1;
m_texture = texture; m_texture = texture;
return 0;
} }
void CSprite::SetSize(float width, float height) void CSprite::SetSize(float width, float height)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -21,7 +21,7 @@
*/ */
// Usage: Instantiate, then be sure to pass a loaded // Usage: Instantiate, then be sure to pass a loaded
// (using ogl_tex_load()) texture before calling Render(). // (using CTextureManager) texture before calling Render().
#ifndef INCLUDED_SPRITE #ifndef INCLUDED_SPRITE
#define INCLUDED_SPRITE #define INCLUDED_SPRITE
@ -45,7 +45,7 @@ public:
void Render(); void Render();
int SetTexture(CTexture *texture); void SetTexture(const CTexturePtr& texture);
void SetSize(float width, float height); void SetSize(float width, float height);
@ -69,7 +69,7 @@ private:
void BeginBillboard(); void BeginBillboard();
void EndBillboard(); void EndBillboard();
CTexture *m_texture; CTexturePtr m_texture;
CVector3D m_coords[4]; CVector3D m_coords[4];

View File

@ -30,11 +30,19 @@
CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path): CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path):
m_pProperties(props), m_pProperties(props),
m_Handle(-1),
m_BaseColor(0), m_BaseColor(0),
m_BaseColorValid(false), m_BaseColorValid(false)
m_TexturePath(path)
{ {
CTextureProperties texture(path);
texture.SetWrap(GL_REPEAT);
// TODO: anisotropy should probably be user-configurable, but we want it to be
// at least 2 for terrain else the ground looks very blurry when you tilt the
// camera upwards
texture.SetMaxAnisotropy(2.0f);
m_Texture = g_Renderer.GetTextureManager().CreateTexture(texture);
if (m_pProperties) if (m_pProperties)
m_Groups = m_pProperties->GetGroups(); m_Groups = m_pProperties->GetGroups();
@ -42,64 +50,31 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr props, const Vf
for (;it!=m_Groups.end();++it) for (;it!=m_Groups.end();++it)
(*it)->AddTerrain(this); (*it)->AddTerrain(this);
m_Tag = fs::basename(m_TexturePath); m_Tag = fs::basename(path);
} }
CTerrainTextureEntry::~CTerrainTextureEntry() CTerrainTextureEntry::~CTerrainTextureEntry()
{ {
if (m_Handle > 0)
(void)ogl_tex_free(m_Handle);
for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it) for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it)
(*it)->RemoveTerrain(this); (*it)->RemoveTerrain(this);
} }
// LoadTexture: actually load the texture resource from file
void CTerrainTextureEntry::LoadTexture()
{
CTexture texture(m_TexturePath);
if (g_Renderer.LoadTexture(&texture,GL_REPEAT)) {
m_Handle=texture.GetHandle();
} else {
m_Handle=0;
}
}
// BuildBaseColor: calculate the root colour of the texture, used for coloring minimap, and store // BuildBaseColor: calculate the root colour of the texture, used for coloring minimap, and store
// in m_BaseColor member // in m_BaseColor member
void CTerrainTextureEntry::BuildBaseColor() void CTerrainTextureEntry::BuildBaseColor()
{ {
// Use the explicit properties value if possible
if (m_pProperties && m_pProperties->HasBaseColor()) if (m_pProperties && m_pProperties->HasBaseColor())
{ {
m_BaseColor=m_pProperties->GetBaseColor(); m_BaseColor=m_pProperties->GetBaseColor();
m_BaseColorValid=true; m_BaseColorValid = true;
return; return;
} }
LibError ret = ogl_tex_bind(GetHandle()); // Use the texture colour if available
debug_assert(ret == INFO::OK); if (m_Texture->TryLoad())
{
// get root colour for coloring minimap by querying root level of the texture m_BaseColor = m_Texture->GetBaseColour();
// (this should decompress any compressed textures for us), m_BaseColorValid = true;
// then scaling it down to a 1x1 size }
// - an alternative approach of just grabbing the top level of the mipmap tree fails
// (or gives an incorrect colour) in some cases:
// - suspect bug on Radeon cards when SGIS_generate_mipmap is used
// - any textures without mipmaps
// we'll just take the basic approach here.
//
// jw: this is horribly inefficient (taking 750ms for 10 texture types),
// but it is no longer called, since terrain XML files are supposed to
// include a minimap color attribute. therefore, leave it as-is.
GLint width,height;
glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_WIDTH,&width);
glGetTexLevelParameteriv(GL_TEXTURE_2D,0,GL_TEXTURE_HEIGHT,&height);
unsigned char* buf=new unsigned char[width*height*4];
glGetTexImage(GL_TEXTURE_2D,0,GL_BGRA_EXT,GL_UNSIGNED_BYTE,buf);
gluScaleImage(GL_BGRA_EXT,width,height,GL_UNSIGNED_BYTE,buf,
1,1,GL_UNSIGNED_BYTE,&m_BaseColor);
delete[] buf;
m_BaseColorValid=true;
} }

View File

@ -15,17 +15,18 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef INCLUDED_TEXTUREENTRY #ifndef INCLUDED_TERRAINTEXTUREENTRY
#define INCLUDED_TEXTUREENTRY #define INCLUDED_TERRAINTEXTUREENTRY
#include <map> #include <map>
#include "TerrainTextureManager.h"
#include "TextureManager.h"
#include "lib/res/handle.h" #include "lib/res/handle.h"
#include "lib/file/vfs/vfs_path.h" #include "lib/file/vfs/vfs_path.h"
#include "ps/CStr.h" #include "ps/CStr.h"
#include "TerrainTextureManager.h"
class XMBElement; class XMBElement;
class CXeromyces; class CXeromyces;
@ -44,9 +45,7 @@ private:
// The property sheet used by this texture // The property sheet used by this texture
CTerrainPropertiesPtr m_pProperties; CTerrainPropertiesPtr m_pProperties;
VfsPath m_TexturePath; CTexturePtr m_Texture;
Handle m_Handle; // handle to GL texture data
// BGRA color of topmost mipmap level, for coloring minimap, or a color // BGRA color of topmost mipmap level, for coloring minimap, or a color
// specified by the terrain properties // specified by the terrain properties
@ -57,8 +56,6 @@ private:
// All terrain type groups we're a member of // All terrain type groups we're a member of
GroupVector m_Groups; GroupVector m_Groups;
// load texture from file
void LoadTexture();
// calculate the root color of the texture, used for coloring minimap // calculate the root color of the texture, used for coloring minimap
void BuildBaseColor(); void BuildBaseColor();
@ -74,13 +71,9 @@ public:
CTerrainPropertiesPtr GetProperties() const CTerrainPropertiesPtr GetProperties() const
{ return m_pProperties; } { return m_pProperties; }
VfsPath GetTexturePath() const
{ return m_TexturePath; }
// Get texture handle, load texture if not loaded. // Get texture handle, load texture if not loaded.
Handle GetHandle() { const CTexturePtr& GetTexture() {
if (m_Handle==-1) LoadTexture(); return m_Texture;
return m_Handle;
} }
// Get mipmap color in BGRA format // Get mipmap color in BGRA format
u32 GetBaseColor() { u32 GetBaseColor() {
@ -93,9 +86,6 @@ public:
{ return m_Groups[0]->GetIndex(); } { return m_Groups[0]->GetIndex(); }
const GroupVector &GetGroups() const const GroupVector &GetGroups() const
{ return m_Groups; } { return m_Groups; }
// returns whether this texture-entry has loaded any data yet
bool IsLoaded() { return (m_Handle!=-1); }
}; };
#endif #endif

View File

@ -15,8 +15,8 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef INCLUDED_TEXTUREMANAGER #ifndef INCLUDED_TERRAINTEXTUREMANAGER
#define INCLUDED_TEXTUREMANAGER #define INCLUDED_TERRAINTEXTUREMANAGER
#include <vector> #include <vector>
#include <map> #include <map>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -15,33 +15,11 @@
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/ */
//-----------------------------------------------------------
// Name: Texture.h
// Description: Basic texture class
//
//-----------------------------------------------------------
#ifndef INCLUDED_TEXTURE #ifndef INCLUDED_TEXTURE
#define INCLUDED_TEXTURE #define INCLUDED_TEXTURE
#include "lib/res/handle.h" // Forward-declare for CTextureManager handles
#include "lib/file/vfs/vfs_path.h" class CTexture;
typedef shared_ptr<CTexture> CTexturePtr;
class CTexture
{
public:
CTexture() : m_Handle(0) {}
CTexture(const VfsPath& pathname) : m_Name(pathname), m_Handle(0) {}
void SetName(const VfsPath& pathname) { m_Name=pathname; }
const VfsPath& GetName() const { return m_Name; }
Handle GetHandle() const { return m_Handle; }
void SetHandle(Handle handle) { m_Handle=handle; }
private:
VfsPath m_Name;
Handle m_Handle;
};
#endif #endif

View File

@ -0,0 +1,521 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "TextureConverter.h"
#include "lib/regex.h"
#include "lib/timer.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/file/io/io.h"
#include "lib/tex/tex.h"
#include "maths/MD5.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/XML/Xeromyces.h"
#include "nvtt/nvtt.h"
/**
* Output handler to collect NVTT's output into a simplistic buffer.
* WARNING: Used in the worker thread - must be thread-safe.
*/
struct BufferOutputHandler : public nvtt::OutputHandler
{
std::vector<u8> buffer;
virtual void beginImage(int UNUSED(size), int UNUSED(width), int UNUSED(height), int UNUSED(depth), int UNUSED(face), int UNUSED(miplevel))
{
}
virtual bool writeData(const void* data, int size)
{
size_t off = buffer.size();
buffer.resize(off + size);
memcpy(&buffer[off], data, size);
return true;
}
};
/**
* Request for worker thread to process.
*/
struct CTextureConverter::ConversionRequest
{
VfsPath dest;
CTexturePtr texture;
nvtt::InputOptions inputOptions;
nvtt::CompressionOptions compressionOptions;
nvtt::OutputOptions outputOptions;
bool isDXT1a; // see comment in RunThread
};
/**
* Result from worker thread.
*/
struct CTextureConverter::ConversionResult
{
VfsPath dest;
CTexturePtr texture;
BufferOutputHandler output;
bool ret; // true if the conversion succeeded
};
void CTextureConverter::Settings::Hash(MD5& hash)
{
hash.Update((const u8*)&format, sizeof(format));
hash.Update((const u8*)&mipmap, sizeof(mipmap));
hash.Update((const u8*)&normal, sizeof(normal));
hash.Update((const u8*)&alpha, sizeof(alpha));
hash.Update((const u8*)&filter, sizeof(filter));
hash.Update((const u8*)&kaiserWidth, sizeof(kaiserWidth));
hash.Update((const u8*)&kaiserAlpha, sizeof(kaiserAlpha));
hash.Update((const u8*)&kaiserStretch, sizeof(kaiserStretch));
}
CTextureConverter::SettingsFile* CTextureConverter::LoadSettings(const VfsPath& path) const
{
CXeromyces XeroFile;
if (XeroFile.Load(m_VFS, path) != PSRETURN_OK)
return NULL;
// Define all the elements used in the XML file
#define EL(x) int el_##x = XeroFile.GetElementID(#x)
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
EL(textures);
EL(file);
AT(pattern);
AT(format);
AT(mipmap);
AT(normal);
AT(alpha);
AT(filter);
AT(kaiserwidth);
AT(kaiseralpha);
AT(kaiserstretch);
#undef AT
#undef EL
XMBElement root = XeroFile.GetRoot();
if (root.GetNodeName() != el_textures)
{
LOGERROR(L"Invalid texture settings file \"%ls\" (unrecognised root element)", path.string().c_str());
return NULL;
}
std::auto_ptr<SettingsFile> settings(new SettingsFile());
XERO_ITER_EL(root, child)
{
if (child.GetNodeName() == el_file)
{
Match p;
XERO_ITER_ATTR(child, attr)
{
if (attr.Name == at_pattern)
{
p.pattern = CStrW(attr.Value);
}
else if (attr.Name == at_format)
{
CStr v(attr.Value);
if (v == "dxt1")
p.settings.format = FMT_DXT1;
else if (v == "dxt3")
p.settings.format = FMT_DXT3;
else if (v == "dxt5")
p.settings.format = FMT_DXT5;
else if (v == "rgba")
p.settings.format = FMT_RGBA;
else
LOGERROR(L"Invalid attribute value <file format='%hs'>", v.c_str());
}
else if (attr.Name == at_mipmap)
{
CStr v(attr.Value);
if (v == "true")
p.settings.mipmap = MIP_TRUE;
else if (v == "false")
p.settings.mipmap = MIP_FALSE;
else
LOGERROR(L"Invalid attribute value <file mipmap='%hs'>", v.c_str());
}
else if (attr.Name == at_normal)
{
CStr v(attr.Value);
if (v == "true")
p.settings.normal = NORMAL_TRUE;
else if (v == "false")
p.settings.normal = NORMAL_FALSE;
else
LOGERROR(L"Invalid attribute value <file normal='%hs'>", v.c_str());
}
else if (attr.Name == at_alpha)
{
CStr v(attr.Value);
if (v == "none")
p.settings.alpha = ALPHA_NONE;
else if (v == "player")
p.settings.alpha = ALPHA_PLAYER;
else if (v == "transparency")
p.settings.alpha = ALPHA_TRANSPARENCY;
else
LOGERROR(L"Invalid attribute value <file alpha='%hs'>", v.c_str());
}
else if (attr.Name == at_filter)
{
CStr v(attr.Value);
if (v == "box")
p.settings.filter = FILTER_BOX;
else if (v == "triangle")
p.settings.filter = FILTER_TRIANGLE;
else if (v == "kaiser")
p.settings.filter = FILTER_KAISER;
else
LOGERROR(L"Invalid attribute value <file filter='%hs'>", v.c_str());
}
else if (attr.Name == at_kaiserwidth)
{
p.settings.kaiserWidth = CStr(attr.Value).ToFloat();
}
else if (attr.Name == at_kaiseralpha)
{
p.settings.kaiserAlpha = CStr(attr.Value).ToFloat();
}
else if (attr.Name == at_kaiserstretch)
{
p.settings.kaiserStretch = CStr(attr.Value).ToFloat();
}
else
{
LOGERROR(L"Invalid attribute name <file %hs='...'>", XeroFile.GetAttributeString(attr.Name).c_str());
}
}
settings->patterns.push_back(p);
}
}
return settings.release();
}
CTextureConverter::Settings CTextureConverter::ComputeSettings(const std::wstring& filename, const std::vector<SettingsFile*>& settingsFiles) const
{
// Set sensible defaults
Settings settings;
settings.format = FMT_DXT1;
settings.mipmap = MIP_TRUE;
settings.normal = NORMAL_FALSE;
settings.alpha = ALPHA_NONE;
settings.filter = FILTER_BOX;
settings.kaiserWidth = 3.f;
settings.kaiserAlpha = 4.f;
settings.kaiserStretch = 1.f;
for (size_t i = 0; i < settingsFiles.size(); ++i)
{
for (size_t j = 0; j < settingsFiles[i]->patterns.size(); ++j)
{
Match p = settingsFiles[i]->patterns[j];
// Check that the pattern matches the texture file
if (!match_wildcard(filename.c_str(), p.pattern.c_str()))
continue;
if (p.settings.format != FMT_UNSPECIFIED)
settings.format = p.settings.format;
if (p.settings.mipmap != MIP_UNSPECIFIED)
settings.mipmap = p.settings.mipmap;
if (p.settings.normal != NORMAL_UNSPECIFIED)
settings.normal = p.settings.normal;
if (p.settings.alpha != ALPHA_UNSPECIFIED)
settings.alpha = p.settings.alpha;
if (p.settings.filter != FILTER_UNSPECIFIED)
settings.filter = p.settings.filter;
if (p.settings.kaiserWidth != -1.f)
settings.kaiserWidth = p.settings.kaiserWidth;
if (p.settings.kaiserAlpha != -1.f)
settings.kaiserAlpha = p.settings.kaiserAlpha;
if (p.settings.kaiserStretch != -1.f)
settings.kaiserStretch = p.settings.kaiserStretch;
}
}
return settings;
}
CTextureConverter::CTextureConverter(PIVFS vfs) :
m_VFS(vfs), m_Shutdown(false)
{
// Verify that we are running with at least the version we were compiled with,
// to avoid bugs caused by ABI changes
debug_assert(nvtt::version() >= NVTT_VERSION);
// Start up the worker thread
sem_init(&m_WorkerSem, 0, 0);
pthread_mutex_init(&m_WorkerMutex, NULL);
pthread_create(&m_WorkerThread, NULL, &RunThread, this);
// Maybe we should share some centralised pool of worker threads?
// For now we'll just stick with a single thread for this specific use.
}
CTextureConverter::~CTextureConverter()
{
// Tell the thread to shut down
pthread_mutex_lock(&m_WorkerMutex);
m_Shutdown = true;
pthread_mutex_unlock(&m_WorkerMutex);
// Wake it up so it sees the notification
sem_post(&m_WorkerSem);
// Wait for it to shut down cleanly
pthread_join(m_WorkerThread, NULL);
}
bool CTextureConverter::ConvertTexture(const CTexturePtr& texture, const VfsPath& src, const VfsPath& dest, const Settings& settings)
{
shared_ptr<u8> file;
size_t fileSize;
if (m_VFS->LoadFile(src, file, fileSize) < 0)
{
LOGERROR(L"Failed to load texture \"%ls\"", src.string().c_str());
return false;
}
Tex tex;
if (tex_decode(file, fileSize, &tex) < 0)
{
LOGERROR(L"Failed to decode texture \"%ls\"", src.string().c_str());
return false;
}
// Check whether there's any alpha channel
bool hasAlpha = ((tex.flags & TEX_ALPHA) != 0);
// Convert to uncompressed BGRA with no mipmaps
if (tex_transform_to(&tex, (tex.flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) < 0)
{
LOGERROR(L"Failed to transform texture \"%ls\"", src.string().c_str());
tex_free(&tex);
return false;
}
// Check if the texture has all alpha=255, so we can automatically
// switch from DXT3/DXT5 to DXT1 with no loss
if (hasAlpha)
{
hasAlpha = false;
u8* data = tex_get_data(&tex);
for (size_t i = 0; i < tex.w * tex.h; ++i)
{
if (data[i*4+3] != 0xFF)
{
hasAlpha = true;
break;
}
}
}
shared_ptr<ConversionRequest> request(new ConversionRequest);
request->dest = dest;
request->texture = texture;
// Apply the chosen settings:
request->inputOptions.setMipmapGeneration(settings.mipmap == MIP_TRUE);
if (settings.alpha == ALPHA_TRANSPARENCY)
request->inputOptions.setAlphaMode(nvtt::AlphaMode_Transparency);
else
request->inputOptions.setAlphaMode(nvtt::AlphaMode_None);
request->isDXT1a = false;
if (settings.format == FMT_RGBA)
{
request->compressionOptions.setFormat(nvtt::Format_RGBA);
// Change the default component order (see tex_dds.cpp decode_pf)
request->compressionOptions.setPixelFormat(32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000u);
}
else if (!hasAlpha)
{
// if no alpha channel then there's no point using DXT3 or DXT5
request->compressionOptions.setFormat(nvtt::Format_DXT1);
}
else if (settings.format == FMT_DXT1)
{
request->compressionOptions.setFormat(nvtt::Format_DXT1a);
request->isDXT1a = true;
}
else if (settings.format == FMT_DXT3)
{
request->compressionOptions.setFormat(nvtt::Format_DXT3);
}
else if (settings.format == FMT_DXT5)
{
request->compressionOptions.setFormat(nvtt::Format_DXT5);
}
if (settings.filter == FILTER_BOX)
request->inputOptions.setMipmapFilter(nvtt::MipmapFilter_Box);
else if (settings.filter == FILTER_TRIANGLE)
request->inputOptions.setMipmapFilter(nvtt::MipmapFilter_Triangle);
else if (settings.filter == FILTER_KAISER)
request->inputOptions.setMipmapFilter(nvtt::MipmapFilter_Kaiser);
if (settings.normal == NORMAL_TRUE)
request->inputOptions.setNormalMap(true);
request->inputOptions.setKaiserParameters(settings.kaiserWidth, settings.kaiserAlpha, settings.kaiserStretch);
request->inputOptions.setWrapMode(nvtt::WrapMode_Mirror); // TODO: should this be configurable?
request->compressionOptions.setQuality(nvtt::Quality_Fastest); // TODO: allow running with higher quality
// TODO: normal maps, gamma, etc
// Load the texture data
request->inputOptions.setTextureLayout(nvtt::TextureType_2D, tex.w, tex.h);
request->inputOptions.setMipmapData(tex_get_data(&tex), tex.w, tex.h);
// NVTT copies the texture data so we can free it now
tex_free(&tex);
pthread_mutex_lock(&m_WorkerMutex);
m_RequestQueue.push_back(request);
pthread_mutex_unlock(&m_WorkerMutex);
// Wake up the worker thread
sem_post(&m_WorkerSem);
return true;
}
bool CTextureConverter::Poll(CTexturePtr& texture, VfsPath& dest, bool& ok)
{
shared_ptr<ConversionResult> result;
// Grab the first result (if any)
pthread_mutex_lock(&m_WorkerMutex);
if (!m_ResultQueue.empty())
{
result = m_ResultQueue.front();
m_ResultQueue.pop_front();
}
pthread_mutex_unlock(&m_WorkerMutex);
if (!result)
{
// no work to do
return false;
}
if (!result->ret)
{
// conversion had failed
ok = false;
return true;
}
// Move output into a correctly-aligned buffer
size_t size = result->output.buffer.size();
shared_ptr<u8> file = io_Allocate(size);
memcpy(file.get(), &result->output.buffer[0], size);
if (m_VFS->CreateFile(result->dest, file, size) < 0)
{
// error writing file
ok = false;
return true;
}
// Succeeded in converting texture
texture = result->texture;
dest = result->dest;
ok = true;
return true;
}
bool CTextureConverter::IsBusy()
{
pthread_mutex_lock(&m_WorkerMutex);
bool busy = !m_RequestQueue.empty();
pthread_mutex_unlock(&m_WorkerMutex);
return busy;
}
void* CTextureConverter::RunThread(void* data)
{
CTextureConverter* textureConverter = static_cast<CTextureConverter*>(data);
// Wait until the main thread wakes us up
while (sem_wait(&textureConverter->m_WorkerSem) == 0)
{
pthread_mutex_lock(&textureConverter->m_WorkerMutex);
if (textureConverter->m_Shutdown)
{
pthread_mutex_unlock(&textureConverter->m_WorkerMutex);
break;
}
// If we weren't woken up for shutdown, we must have been woken up for
// a new request, so grab it from the queue
shared_ptr<ConversionRequest> request = textureConverter->m_RequestQueue.front();
textureConverter->m_RequestQueue.pop_front();
pthread_mutex_unlock(&textureConverter->m_WorkerMutex);
// Set up the result object
shared_ptr<ConversionResult> result(new ConversionResult());
result->dest = request->dest;
result->texture = request->texture;
request->outputOptions.setOutputHandler(&result->output);
// TIMER(L"TextureConverter compress");
// Perform the compression
nvtt::Compressor compressor;
result->ret = compressor.process(request->inputOptions, request->compressionOptions, request->outputOptions);
// Ugly hack: NVTT 2.0 doesn't set DDPF_ALPHAPIXELS for DXT1a, so we can't
// distinguish it from DXT1. (It's fixed in trunk by
// http://code.google.com/p/nvidia-texture-tools/source/detail?r=924&path=/trunk).
// Rather than using a trunk NVTT (unstable, makes packaging harder)
// or patching our copy (makes packaging harder), we'll just manually
// set the flag here.
if (request->isDXT1a && result->ret && result->output.buffer.size() > 80)
result->output.buffer[80] |= 1; // DDPF_ALPHAPIXELS in DDS_PIXELFORMAT.dwFlags
// Push the result onto the queue
pthread_mutex_lock(&textureConverter->m_WorkerMutex);
textureConverter->m_ResultQueue.push_back(result);
pthread_mutex_unlock(&textureConverter->m_WorkerMutex);
}
return NULL;
}

View File

@ -0,0 +1,215 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_TEXTURECONVERTER
#define INCLUDED_TEXTURECONVERTER
#include "lib/file/vfs/vfs.h"
#include "TextureManager.h"
class MD5;
/**
* Texture conversion helper class.
* Provides an asynchronous API to convert input image files into compressed DDS,
* given various conversion settings.
* (The (potentially very slow) compression is a performed in a background thread,
* so the game can remain responsive).
* Also provides an API to load conversion settings from XML files.
*
* XML files are of the form:
* @code
* <Textures>
* <File pattern="*" format="dxt5" mipmap="false" alpha="transparency"/>
* <File pattern="button_wood.*" format="rgba"/>
* </Entity>
* @endcode
*
* 'pattern' is a wildcard expression, matching on filenames.
* All other attributes are optional. Later elements override attributes from
* earlier elements.
*
* 'format' is 'dxt1', 'dxt3', 'dxt5' or 'rgba'.
*
* 'mipmap' is 'true' or 'false'.
*
* 'normal' is 'true' or 'false'.
*
* 'alpha' is 'transparency' or 'player' (it determines whether the colour value of
* 0-alpha pixels is significant or not).
*
* 'filter' is 'box', 'triangle' or 'kaiser'.
*
* 'kaiserwidth', 'kaiseralpha', 'kaiserstretch' are floats
* (see http://code.google.com/p/nvidia-texture-tools/wiki/ApiDocumentation#Mipmap_Generation)
*/
class CTextureConverter
{
public:
enum EFormat
{
FMT_UNSPECIFIED,
FMT_DXT1,
FMT_DXT3,
FMT_DXT5,
FMT_RGBA
};
enum EMipmap
{
MIP_UNSPECIFIED,
MIP_TRUE,
MIP_FALSE
};
enum ENormalMap
{
NORMAL_UNSPECIFIED,
NORMAL_TRUE,
NORMAL_FALSE
};
enum EAlpha
{
ALPHA_UNSPECIFIED,
ALPHA_NONE,
ALPHA_PLAYER,
ALPHA_TRANSPARENCY
};
enum EFilter
{
FILTER_UNSPECIFIED,
FILTER_BOX,
FILTER_TRIANGLE,
FILTER_KAISER
};
/**
* Texture conversion settings.
*/
struct Settings
{
Settings() :
format(FMT_UNSPECIFIED), mipmap(MIP_UNSPECIFIED), normal(NORMAL_UNSPECIFIED),
alpha(ALPHA_UNSPECIFIED), filter(FILTER_UNSPECIFIED),
kaiserWidth(-1.f), kaiserAlpha(-1.f), kaiserStretch(-1.f)
{
}
/**
* Append this object's state to the given hash.
*/
void Hash(MD5& hash);
EFormat format;
EMipmap mipmap;
ENormalMap normal;
EAlpha alpha;
EFilter filter;
float kaiserWidth;
float kaiserAlpha;
float kaiserStretch;
};
/**
* Representation of \<File\> line from settings XML file.
*/
struct Match
{
std::wstring pattern;
Settings settings;
};
/**
* Representation of settings XML file.
*/
struct SettingsFile
{
std::vector<Match> patterns;
};
/**
* Construct texture converter, for use with files in the given vfs.
*/
CTextureConverter(PIVFS vfs);
/**
* Destroy texture converter and wait to shut down worker thread.
* This might take a long time (maybe seconds) if the worker is busy
* processing a texture.
*/
~CTextureConverter();
/**
* Load a texture conversion settings XML file.
* Returns NULL on failure.
*/
SettingsFile* LoadSettings(const VfsPath& path) const;
/**
* Match a sequence of settings files against a given texture filename,
* and return the resulting settings.
* Later entries in settingsFiles override earlier entries.
*/
Settings ComputeSettings(const std::wstring& filename, const std::vector<SettingsFile*>& settingsFiles) const;
/**
* Begin converting a texture, using the given settings.
* This will load src and return false on failure.
* Otherwise it will return true and start an asynchronous conversion request,
* whose result will be returned from Poll() (with the texture and dest passed
* into this function).
*/
bool ConvertTexture(const CTexturePtr& texture, const VfsPath& src, const VfsPath& dest, const Settings& settings);
/**
* Returns the result of a successful ConvertTexture call.
* If no result is available yet, returns false.
* Otherwise, if the conversion succeeded, it sets ok to true and sets
* texture and dest to the corresponding values passed into ConvertTexture(),
* then returns true.
* If the conversion failed, it sets ok to false and doesn't touch the other arguments,
* then returns true.
*/
bool Poll(CTexturePtr& texture, VfsPath& dest, bool& ok);
/**
* Returns whether there is currently a queued request from ConvertTexture().
* (Note this may return false while the worker thread is still converting the last texture.)
*/
bool IsBusy();
private:
static void* RunThread(void* data);
PIVFS m_VFS;
pthread_t m_WorkerThread;
sem_t m_WorkerSem;
pthread_mutex_t m_WorkerMutex;
struct ConversionRequest;
struct ConversionResult;
std::deque<shared_ptr<ConversionRequest> > m_RequestQueue; // protected by m_WorkerMutex
std::deque<shared_ptr<ConversionResult> > m_ResultQueue; // protected by m_WorkerMutex
bool m_Shutdown; // protected by m_WorkerMutex
};
#endif // INCLUDED_TEXTURECONVERTER

View File

@ -0,0 +1,667 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "TextureManager.h"
#include "graphics/TextureConverter.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/res/h_mgr.h"
#include "lib/file/vfs/vfs_tree.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/timer.h"
#include "maths/MD5.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
// Comparison functor that operates over texture properties, or
// over the properties of a CTexturePtr (ignoring the mutable state like Handle).
struct TextureCacheCmp
{
bool operator()(const CTexturePtr& a, const CTexturePtr& b) const
{
return (*this)(a->m_Properties, b->m_Properties);
}
bool operator()(const CTextureProperties& a, const CTextureProperties& b) const
{
if (a.m_Path < b.m_Path)
return true;
if (b.m_Path < a.m_Path)
return false;
if (a.m_Filter < b.m_Filter)
return true;
if (b.m_Filter < a.m_Filter)
return false;
if (a.m_Wrap < b.m_Wrap)
return true;
if (b.m_Wrap < a.m_Wrap)
return false;
if (a.m_Aniso < b.m_Aniso)
return true;
if (b.m_Aniso < a.m_Aniso)
return false;
return false;
}
};
class CTextureManagerImpl
{
friend class CTexture;
public:
CTextureManagerImpl(PIVFS vfs, bool disableGL)
: m_VFS(vfs), m_DisableGL(disableGL), m_TextureConverter(vfs), m_DefaultHandle(0), m_ErrorHandle(0)
{
// Initialise some textures that will always be available,
// without needing to load any files
// Default placeholder texture (grey)
{
// Construct 1x1 24-bit texture
shared_ptr<u8> data(new u8[3], ArrayDeleter());
data.get()[0] = 64;
data.get()[1] = 64;
data.get()[2] = 64;
Tex t;
(void)tex_wrap(1, 1, 24, 0, data, 0, &t);
m_DefaultHandle = ogl_tex_wrap(&t, m_VFS, L"(default texture)");
(void)ogl_tex_set_filter(m_DefaultHandle, GL_LINEAR);
if (!m_DisableGL)
(void)ogl_tex_upload(m_DefaultHandle);
}
// Error texture (magenta)
{
// Construct 1x1 24-bit texture
shared_ptr<u8> data(new u8[3], ArrayDeleter());
data.get()[0] = 255;
data.get()[1] = 0;
data.get()[2] = 255;
Tex t;
(void)tex_wrap(1, 1, 24, 0, data, 0, &t);
m_ErrorHandle = ogl_tex_wrap(&t, m_VFS, L"(error texture)");
(void)ogl_tex_set_filter(m_ErrorHandle, GL_LINEAR);
if (!m_DisableGL)
(void)ogl_tex_upload(m_ErrorHandle);
// Construct a CTexture to return to callers who want an error texture
CTextureProperties props(L"(error texture)");
m_ErrorTexture = CTexturePtr(new CTexture(m_ErrorHandle, props, this));
m_ErrorTexture->m_State = CTexture::LOADED;
m_ErrorTexture->m_Self = m_ErrorTexture;
}
// Allow hotloading of textures
RegisterFileReloadFunc(ReloadChangedFileCB, this);
}
~CTextureManagerImpl()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
(void)ogl_tex_free(m_DefaultHandle);
(void)ogl_tex_free(m_ErrorHandle);
}
CTexturePtr GetErrorTexture()
{
return m_ErrorTexture;
}
/**
* See CTextureManager::CreateTexture
*/
CTexturePtr CreateTexture(const CTextureProperties& props)
{
// Construct a new default texture with the given properties to use as the search key
CTexturePtr texture(new CTexture(m_DefaultHandle, props, this));
// Try to find an existing texture with the given properties
TextureCache::iterator it = m_TextureCache.find(texture);
if (it != m_TextureCache.end())
return *it;
// Can't find an existing texture - finish setting up this new texture
texture->m_Self = texture;
m_TextureCache.insert(texture);
m_HotloadFiles[props.m_Path].insert(texture);
return texture;
}
/**
* Load the given file into the texture object and upload it to OpenGL.
* Assumes the file already exists.
*/
void LoadTexture(const CTexturePtr& texture, const VfsPath& path)
{
Handle h = ogl_tex_load(m_VFS, path, RES_UNIQUE);
if (h <= 0)
{
LOGERROR(L"Texture failed to load; \"%ls\"", texture->m_Properties.m_Path.string().c_str());
// Replace with error texture to make it obvious
texture->SetHandle(m_ErrorHandle);
return;
}
// Get some flags for later use
size_t flags = 0;
(void)ogl_tex_get_format(h, &flags, NULL);
// Initialise base colour from the texture
(void)ogl_tex_get_average_colour(h, &texture->m_BaseColour);
// Set GL upload properties
(void)ogl_tex_set_wrap(h, texture->m_Properties.m_Wrap);
(void)ogl_tex_set_anisotropy(h, texture->m_Properties.m_Aniso);
// Prevent ogl_tex automatically generating mipmaps (which is slow and unwanted),
// by avoiding mipmapped filters unless the source texture already has mipmaps
GLint filter = texture->m_Properties.m_Filter;
if (!(flags & TEX_MIPMAPS))
{
switch (filter)
{
case GL_NEAREST_MIPMAP_NEAREST:
case GL_NEAREST_MIPMAP_LINEAR:
filter = GL_NEAREST;
break;
case GL_LINEAR_MIPMAP_NEAREST:
case GL_LINEAR_MIPMAP_LINEAR:
filter = GL_LINEAR;
break;
}
}
(void)ogl_tex_set_filter(h, filter);
// Upload to GL
if (!m_DisableGL && ogl_tex_upload(h) < 0)
{
LOGERROR(L"Texture failed to upload: \"%ls\"", texture->m_Properties.m_Path.string().c_str());
ogl_tex_free(h);
// Replace with error texture to make it obvious
texture->SetHandle(m_ErrorHandle);
return;
}
// Let the texture object take ownership of this handle
texture->SetHandle(h, true);
}
/**
* Determines whether we can safely use the archived cache file, or need to
* re-convert the source file.
*/
bool CanUseArchiveCache(const VfsPath& sourcePath, const VfsPath& archiveCachePath)
{
// We want to use the archive cache whenever possible,
// unless it's superseded by a source file that the user has edited
size_t sourcePriority = 0;
size_t archiveCachePriority = 0;
bool sourceExists = (m_VFS->GetFilePriority(sourcePath, &sourcePriority) >= 0);
bool archiveCacheExists = (m_VFS->GetFilePriority(archiveCachePath, &archiveCachePriority) >= 0);
// Can't use it if there's no cache
if (!archiveCacheExists)
return false;
// Must use the cache if there's no source
if (!sourceExists)
return true;
// If source file is from a higher-priority mod than archive cache,
// don't use the old cache
if (archiveCachePriority < sourcePriority)
return false;
// If source file is more recent than the archive cache (i.e. the user has edited it),
// don't use the old cache
FileInfo sourceInfo, archiveCacheInfo;
if (m_VFS->GetFileInfo(sourcePath, &sourceInfo) >= 0 &&
m_VFS->GetFileInfo(archiveCachePath, &archiveCacheInfo) >= 0)
{
const double howMuchNewer = difftime(sourceInfo.MTime(), archiveCacheInfo.MTime());
const double threshold = 2.0; // FAT timestamp resolution [seconds]
if (howMuchNewer > threshold)
return false;
}
// Otherwise we can use the cache
return true;
}
/**
* Attempts to load a cached version of a texture.
* If the texture is loaded (or there was an error), returns true.
* Otherwise, returns false to indicate the caller should generate the cached version.
*/
bool TryLoadingCached(const CTexturePtr& texture)
{
VfsPath sourcePath = texture->m_Properties.m_Path;
VfsPath sourceDir = sourcePath.branch_path();
std::wstring sourceName = sourcePath.leaf();
VfsPath archiveCachePath = sourceDir / (sourceName + L".dds");
// Try the archive cache file first
if (CanUseArchiveCache(sourcePath, archiveCachePath))
{
LoadTexture(texture, archiveCachePath);
return true;
}
// Fail if no source or archive cache
if (m_VFS->GetFileInfo(sourcePath, NULL) < 0)
{
LOGERROR(L"Texture failed to find source file: \"%ls\"", texture->m_Properties.m_Path.string().c_str());
texture->SetHandle(m_ErrorHandle);
return true;
}
// Look for loose cache of source file
VfsPath looseCachePath = LooseCachePath(texture);
// If the loose cache file exists, use it
if (m_VFS->GetFileInfo(looseCachePath, NULL) >= 0)
{
LoadTexture(texture, looseCachePath);
return true;
}
// No cache - we'll need to regenerate it
return false;
}
/**
* Returns the pathname for storing a loose cache file, based on the size/mtime of
* the source file and the conversion settings. The source file must already exist.
*
* TODO: this code should probably be shared with other cached data (XMB files etc).
*/
VfsPath LooseCachePath(const CTexturePtr& texture)
{
VfsPath sourcePath = texture->m_Properties.m_Path;
FileInfo fileInfo;
if (m_VFS->GetFileInfo(sourcePath, &fileInfo) < 0)
{
debug_warn(L"source file disappeared"); // this should never happen
return VfsPath();
}
u64 mtime = (u64)fileInfo.MTime() & ~1; // skip lowest bit, since zip and FAT don't preserve it
u64 size = (u64)fileInfo.Size();
u32 version = 0; // change this if we update the code and need to invalidate old users' caches
// Construct a hash of the file data and settings.
CTextureConverter::Settings settings = GetConverterSettings(texture);
MD5 hash;
hash.Update((const u8*)&mtime, sizeof(mtime));
hash.Update((const u8*)&size, sizeof(size));
hash.Update((const u8*)&version, sizeof(version));
settings.Hash(hash);
// these are local cached files, so we don't care about endianness etc
// Use a short prefix of the full hash (we don't need high collision-resistance),
// converted to hex
u8 digest[MD5::DIGESTSIZE];
hash.Final(digest);
std::wstringstream digestPrefix;
digestPrefix << std::hex;
for (size_t i = 0; i < 8; ++i)
digestPrefix << std::setfill(L'0') << std::setw(2) << (int)digest[i];
// Construct the final path
VfsPath sourceDir = sourcePath.branch_path();
std::wstring sourceName = sourcePath.leaf();
return L"cache" / sourceDir / (sourceName + L"." + digestPrefix.str() + L".dds");
// TODO: we should probably include the mod name, once that's possible (http://trac.wildfiregames.com/ticket/564)
}
/**
* Initiates an asynchronous conversion process, from the texture's
* source file to the corresponding loose cache file.
*/
void ConvertTexture(const CTexturePtr& texture)
{
VfsPath sourcePath = texture->m_Properties.m_Path;
VfsPath looseCachePath = LooseCachePath(texture);
// LOGWARNING(L"Converting texture \"%ls\"", srcPath.string().c_str());
CTextureConverter::Settings settings = GetConverterSettings(texture);
m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings);
}
bool MakeProgress()
{
// Process any completed conversion tasks
{
CTexturePtr texture;
VfsPath dest;
bool ok;
if (m_TextureConverter.Poll(texture, dest, ok))
{
if (ok)
{
LoadTexture(texture, dest);
}
else
{
LOGERROR(L"Texture failed to convert: \"%ls\"", texture->m_Properties.m_Path.string().c_str());
texture->SetHandle(m_ErrorHandle);
}
texture->m_State = CTexture::LOADED;
return true;
}
}
// We'll only push new conversion requests if it's not already busy
bool converterBusy = m_TextureConverter.IsBusy();
if (!converterBusy)
{
// Look for all high-priority textures needing conversion.
// (Iterating over all textures isn't optimally efficient, but it
// doesn't seem to be a problem yet and it's simpler than maintaining
// multiple queues.)
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING)
{
// Start converting this texture
(*it)->m_State = CTexture::HIGH_IS_CONVERTING;
ConvertTexture(*it);
return true;
}
}
}
// Try loading prefetched textures from their cache
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING)
{
if (TryLoadingCached(*it))
{
(*it)->m_State = CTexture::LOADED;
}
else
{
(*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING;
}
return true;
}
}
// If we've got nothing better to do, then start converting prefetched textures.
if (!converterBusy)
{
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING)
{
(*it)->m_State = CTexture::PREFETCH_IS_CONVERTING;
ConvertTexture(*it);
return true;
}
}
}
return false;
}
/**
* Compute the conversion settings that apply to a given texture, by combining
* the textures.xml files from its directory and all parent directories
* (up to the VFS root).
*/
CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture)
{
VfsPath srcPath = texture->m_Properties.m_Path;
std::vector<CTextureConverter::SettingsFile*> files;
VfsPath p;
for (VfsPath::iterator it = srcPath.begin(); it != srcPath.end(); ++it)
{
VfsPath settingsPath = p/L"textures.xml";
m_HotloadFiles[settingsPath].insert(texture);
CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath);
if (f)
files.push_back(f);
p /= *it;
}
return m_TextureConverter.ComputeSettings(srcPath.leaf(), files);
}
/**
* Return the (cached) settings file with the given filename,
* or NULL if it doesn't exist.
*/
CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path)
{
SettingsFilesMap::iterator it = m_SettingsFiles.find(path);
if (it != m_SettingsFiles.end())
return it->second.get();
if (m_VFS->GetFileInfo(path, NULL) >= 0)
{
shared_ptr<CTextureConverter::SettingsFile> settings(m_TextureConverter.LoadSettings(path));
m_SettingsFiles.insert(std::make_pair(path, settings));
return settings.get();
}
else
{
m_SettingsFiles.insert(std::make_pair(path, shared_ptr<CTextureConverter::SettingsFile>()));
return NULL;
}
}
static LibError ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast<CTextureManagerImpl*>(param)->ReloadChangedFile(path);
}
LibError ReloadChangedFile(const VfsPath& path)
{
// Uncache settings file, if this is one
m_SettingsFiles.erase(path);
// Find all textures using this file
std::map<VfsPath, std::set<boost::weak_ptr<CTexture> > >::iterator files = m_HotloadFiles.find(path);
if (files != m_HotloadFiles.end())
{
// Flag all textures using this file as needing reloading
for (std::set<boost::weak_ptr<CTexture> >::iterator it = files->second.begin(); it != files->second.end(); ++it)
{
if (shared_ptr<CTexture> texture = it->lock())
{
texture->m_State = CTexture::UNLOADED;
texture->SetHandle(m_DefaultHandle);
}
}
}
return INFO::OK;
}
private:
PIVFS m_VFS;
bool m_DisableGL;
CTextureConverter m_TextureConverter;
Handle m_DefaultHandle;
Handle m_ErrorHandle;
CTexturePtr m_ErrorTexture;
// Cache of all loaded textures
typedef std::set<CTexturePtr, TextureCacheCmp> TextureCache;
TextureCache m_TextureCache;
// TODO: we ought to expire unused textures from the cache eventually
// Store the set of textures that need to be reloaded when the given file
// (a source file or settings.xml) is modified
std::map<VfsPath, std::set<boost::weak_ptr<CTexture> > > m_HotloadFiles;
// Cache for the conversion settings files
typedef std::map<VfsPath, shared_ptr<CTextureConverter::SettingsFile> > SettingsFilesMap;
SettingsFilesMap m_SettingsFiles;
};
CTexture::CTexture(Handle handle, const CTextureProperties& props, CTextureManagerImpl* textureManager) :
m_Handle(handle), m_BaseColour(0), m_State(UNLOADED), m_Properties(props), m_TextureManager(textureManager)
{
// Add a reference to the handle (it might be shared by multiple CTextures
// so we can't take ownership of it)
h_add_ref(m_Handle);
}
CTexture::~CTexture()
{
ogl_tex_free(m_Handle);
}
void CTexture::Bind(size_t unit)
{
// TODO: TryLoad might call ogl_tex_upload which enables GL_TEXTURE_2D
// on texture unit 0, regardless of 'unit', which callers might
// not be expecting. Ideally that wouldn't happen.
TryLoad();
ogl_tex_bind(m_Handle, unit);
}
bool CTexture::TryLoad()
{
// If we haven't started loading, then try loading, and if that fails then request conversion.
// If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority.
if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING)
{
if (shared_ptr<CTexture> self = m_Self.lock())
{
if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self))
m_State = LOADED;
else
m_State = HIGH_NEEDS_CONVERTING;
}
}
return (m_State == LOADED);
}
void CTexture::Prefetch()
{
if (m_State == UNLOADED)
{
if (shared_ptr<CTexture> self = m_Self.lock())
{
m_State = PREFETCH_NEEDS_LOADING;
}
}
}
bool CTexture::IsLoaded()
{
return (m_State == LOADED);
}
void CTexture::SetHandle(Handle handle, bool takeOwnership)
{
if (handle == m_Handle)
return;
if (!takeOwnership)
h_add_ref(handle);
ogl_tex_free(m_Handle);
m_Handle = handle;
}
size_t CTexture::GetWidth() const
{
size_t w = 0;
(void)ogl_tex_get_size(m_Handle, &w, 0, 0);
return w;
}
size_t CTexture::GetHeight() const
{
size_t h = 0;
(void)ogl_tex_get_size(m_Handle, 0, &h, 0);
return h;
}
bool CTexture::HasAlpha() const
{
size_t flags = 0;
(void)ogl_tex_get_format(m_Handle, &flags, 0);
return (flags & TEX_ALPHA) != 0;
}
u32 CTexture::GetBaseColour() const
{
return m_BaseColour;
}
// CTextureManager: forward all calls to impl:
CTextureManager::CTextureManager(PIVFS vfs, bool disableGL) :
m(new CTextureManagerImpl(vfs, disableGL))
{
}
CTextureManager::~CTextureManager()
{
delete m;
}
CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props)
{
return m->CreateTexture(props);
}
CTexturePtr CTextureManager::GetErrorTexture()
{
return m->GetErrorTexture();
}
bool CTextureManager::MakeProgress()
{
return m->MakeProgress();
}

View File

@ -0,0 +1,262 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_TEXTUREMANAGER
#define INCLUDED_TEXTUREMANAGER
#include "Texture.h"
#include "lib/ogl.h"
#include "lib/file/vfs/vfs.h"
#include "lib/res/handle.h"
#include <boost/weak_ptr.hpp>
class CTextureProperties;
class CTextureManagerImpl;
/**
* Texture manager with asynchronous loading and automatic DDS conversion/compression.
*
* Input textures can be any format. They will be converted to DDS using settings defined
* in files named "texture.xml", in the same directory as the texture and in its parent
* directories. See CTextureConverter for the XML syntax. The DDS file will be cached
* for faster loading in the future.
*
* Typically the graphics code will initialise many textures at the start of the game,
* mostly for off-screen objects, by calling CreateTexture().
* Loading texture data may be very slow (especially if it needs to be converted
* to DDS), and we don't want the game to become unresponsive.
* CreateTexture therefore returns an object immediately, without loading the
* texture. If the object is never used then the data will never be loaded.
*
* Typically, the renderer will call CTexture::Bind() when it wants to use the
* texture. This will trigger the loading of the texture data. If it can be loaded
* quickly (i.e. there is already a cached DDS version), then it will be loaded before
* the function returns, and the texture can be rendered as normal.
*
* If loading will take a long time, then Bind() binds a default placeholder texture
* and starts loading the texture in the background. It will use the correct texture
* when the renderer next calls Bind() after the load has finished.
*
* It is also possible to prefetch textures which are not being rendered yet, but
* are expected to be rendered soon (e.g. for off-screen terrain tiles).
* These will be loaded in the background, when there are no higher-priority textures
* to load.
*
* The same texture file can be safely loaded multiple times with different GL parameters
* (but this should be avoided whenever possible, as it wastes VRAM).
*
* For release packages, DDS files can be precached by appending ".dds" to their name,
* which will be used instead of doing runtime conversion. This means most players should
* never experience the slow asynchronous conversion behaviour.
* These cache files will typically be packed into an archive for faster loading;
* if no archive cache is available then the source file will be converted and stored
* as a loose cache file on the user's disk.
*/
class CTextureManager
{
public:
/**
* Construct texture manager. vfs must be the VFS instance used for all textures
* loaded from this object.
* disableGL is intended for tests, and will disable all GL uploads.
*/
CTextureManager(PIVFS vfs, bool disableGL = false);
~CTextureManager();
/**
* Create a texture with the given GL properties.
* The texture data will not be loaded immediately.
*/
CTexturePtr CreateTexture(const CTextureProperties& props);
/**
* Returns a magenta texture. Use this for highlighting errors
* (e.g. missing terrain textures).
*/
CTexturePtr GetErrorTexture();
/**
* Work on asynchronous texture loading operations, if any.
* Returns true if it did any work.
* The caller should typically loop this per frame until it returns
* false or exceeds the allocated time for this frame.
*/
bool MakeProgress();
private:
CTextureManagerImpl* m;
};
/**
* Represents the filename and GL parameters of a texture,
* for passing to CTextureManager::CreateTexture.
*/
class CTextureProperties
{
friend class CTextureManagerImpl;
friend struct TextureCacheCmp;
public:
/**
* Use the given texture name, and default GL parameters.
*/
explicit CTextureProperties(const VfsPath& path) :
m_Path(path), m_Filter(GL_LINEAR_MIPMAP_LINEAR), m_Wrap(GL_REPEAT), m_Aniso(1.0f)
{
}
/**
* Set min/mag filter mode (typically GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST, etc).
*/
void SetFilter(GLint filter) { m_Filter = filter; }
/**
* Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc).
*/
void SetWrap(GLint wrap) { m_Wrap = wrap; }
/**
* Set maximum anisotropy value. Must be >= 1.0. Should be a power of 2.
*/
void SetMaxAnisotropy(float aniso) { m_Aniso = aniso; }
// TODO: rather than this static definition of texture properties
// (especially anisotropy), maybe we want something that can be more
// easily tweaked in an Options menu? e.g. the caller just specifies
// "terrain texture mode" and we combine it with the user's options.
// That'd let us dynamically change texture properties easily.
//
// enum EQualityMode
// {
// NONE,
// TERRAIN,
// MODEL,
// GUI
// }
// void SetQuality(EQualityMode mode, float anisotropy, float lodbias, int reducemipmaps, ...);
//
// or something a bit like that.
private:
VfsPath m_Path;
GLint m_Filter;
GLint m_Wrap;
float m_Aniso;
};
/**
* Represents a texture object.
* The texture data may or may not have been loaded yet.
* Before it has been loaded, all operations will act on a default
* 1x1-pixel grey texture instead.
*/
class CTexture
{
friend class CTextureManagerImpl;
friend struct TextureCacheCmp;
// Only the texture manager can create these
explicit CTexture(Handle handle, const CTextureProperties& props, CTextureManagerImpl* textureManager);
NONCOPYABLE(CTexture);
public:
~CTexture();
/**
* Returns the width (in pixels) of the current texture.
*/
size_t GetWidth() const;
/**
* Returns the height (in pixels) of the current texture.
*/
size_t GetHeight() const;
/**
* Returns whether the current texture has an alpha channel.
*/
bool HasAlpha() const;
/**
* Returns the ARGB value of the lowest mipmap level (i.e. the
* average of the whole texture).
* Returns 0 if the texture has no mipmaps.
*/
u32 GetBaseColour() const;
/**
* Bind the texture to the given GL texture unit.
* If the texture data hasn't been loaded yet, this may wait a short while to
* load it. If loading takes too long then it will return sooner and the data will
* be loaded in a background thread, so this does not guarantee the texture really
* will be loaded.
*/
void Bind(size_t unit = 0);
/**
* Attempt to load the texture data quickly, as with Bind().
* Returns whether the texture data is currently loaded.
*/
bool TryLoad();
/**
* Returns whether the texture data is currently loaded.
*/
bool IsLoaded();
/**
* Activate the prefetching optimisation for this texture.
* Use this if it is likely the texture will be needed in the near future.
* It will be loaded in the background so that it is likely to be ready when
* it is used by Bind().
*/
void Prefetch();
private:
/**
* Replace the Handle stored by this object.
* If takeOwnership is true, it will not increment the Handle's reference count.
*/
void SetHandle(Handle handle, bool takeOwnership = false);
const CTextureProperties m_Properties;
Handle m_Handle;
u32 m_BaseColour;
enum {
UNLOADED, // loading has not started
PREFETCH_NEEDS_LOADING, // was prefetched; currently waiting to try loading from cache
PREFETCH_NEEDS_CONVERTING, // was prefetched; currently waiting to be sent to the texture converter
PREFETCH_IS_CONVERTING, // was prefetched; currently being processed by the texture converter
HIGH_NEEDS_CONVERTING, // high-priority; currently waiting to be sent to the texture converter
HIGH_IS_CONVERTING, // high-priority; currently being processed by the texture converter
LOADED // loading has completed (successfully or not)
} m_State;
CTextureManagerImpl* m_TextureManager;
// Self-reference to let us recover the CTexturePtr for this object.
// (weak pointer to avoid cycles)
boost::weak_ptr<CTexture> m_Self;
};
#endif // INCLUDED_TEXTUREMANAGER

View File

@ -0,0 +1,126 @@
/* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lib/self_test.h"
#include "graphics/TextureManager.h"
#include "lib/external_libraries/sdl.h"
#include "lib/file/vfs/vfs.h"
#include "lib/res/h_mgr.h"
#include "lib/tex/tex.h"
#include "lib/ogl.h"
#include "ps/XML/Xeromyces.h"
class TestTextureManager : public CxxTest::TestSuite
{
PIVFS m_VFS;
public:
void setUp()
{
DeleteDirectory(DataDir()/L"_testcache"); // clean up in case the last test run failed
m_VFS = CreateVfs(20*MiB);
TS_ASSERT_OK(m_VFS->Mount(L"", DataDir()/L"mods/_test.tex", VFS_MOUNT_MUST_EXIST));
TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/L"_testcache"));
h_mgr_init();
tex_codec_register_all();
CXeromyces::Startup();
}
void tearDown()
{
CXeromyces::Terminate();
tex_codec_unregister_all();
h_mgr_shutdown();
m_VFS.reset();
DeleteDirectory(DataDir()/L"_testcache");
}
void test_load_basic()
{
{
CTextureManager texman(m_VFS, true);
CTexturePtr t1 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo.png"));
TS_ASSERT(!t1->IsLoaded());
TS_ASSERT(!t1->TryLoad());
TS_ASSERT(!t1->IsLoaded());
TS_ASSERT(texman.MakeProgress());
for (size_t i = 0; i < 100; ++i)
{
if (texman.MakeProgress())
break;
SDL_Delay(10);
}
TS_ASSERT(t1->IsLoaded());
TS_ASSERT_EQUALS(t1->GetWidth(), (size_t)64);
TS_ASSERT_EQUALS(t1->GetHeight(), (size_t)64);
// CreateTexture should return the same object
CTexturePtr t2 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo.png"));
TS_ASSERT(t1 == t2);
}
// New texture manager - should use the cached file
{
CTextureManager texman(m_VFS, true);
CTexturePtr t1 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo.png"));
TS_ASSERT(!t1->IsLoaded());
TS_ASSERT(t1->TryLoad());
TS_ASSERT(t1->IsLoaded());
}
}
void test_load_formats()
{
CTextureManager texman(m_VFS, true);
CTexturePtr t1 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo.tga"));
CTexturePtr t2 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo-abgr.dds"));
CTexturePtr t3 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo-dxt1.dds"));
CTexturePtr t4 = texman.CreateTexture(CTextureProperties(L"art/textures/a/demo-dxt5.dds"));
t1->TryLoad();
t2->TryLoad();
t3->TryLoad();
t4->TryLoad();
size_t done = 0;
for (size_t i = 0; i < 100; ++i)
{
if (texman.MakeProgress())
++done;
if (done == 8) // 4 loads, 4 conversions
break;
SDL_Delay(10);
}
TS_ASSERT(t1->IsLoaded());
TS_ASSERT(t2->IsLoaded());
TS_ASSERT(t3->IsLoaded());
TS_ASSERT(t4->IsLoaded());
}
};

View File

@ -19,13 +19,15 @@
#include "GUIRenderer.h" #include "GUIRenderer.h"
#include "graphics/TextureManager.h"
#include "lib/ogl.h" #include "lib/ogl.h"
#include "lib/utf8.h" #include "lib/utf8.h"
#include "lib/res/h_mgr.h" #include "lib/res/h_mgr.h"
#include "lib/tex/tex.h" #include "lib/tex/tex.h"
#include "ps/Filesystem.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "renderer/Renderer.h"
#define LOG_CATEGORY L"gui" #define LOG_CATEGORY L"gui"
using namespace GUIRenderer; using namespace GUIRenderer;
@ -36,7 +38,6 @@ void DrawCalls::clear()
for (iterator it = begin(); it != end(); ++it) for (iterator it = begin(); it != end(); ++it)
{ {
delete it->m_Effects; delete it->m_Effects;
ogl_tex_free(it->m_TexHandle);
} }
std::vector<SDrawCall>::clear(); std::vector<SDrawCall>::clear();
} }
@ -114,7 +115,7 @@ public:
~Effect_AddColor() {} ~Effect_AddColor() {}
void Set(Handle tex) void Set(const CTexturePtr& tex)
{ {
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
@ -149,11 +150,12 @@ public:
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
ogl_tex_bind(tex); tex->Bind();
} }
void Unset() void Unset()
{ {
glColor4f(1.f, 1.f, 1.f, 1.f);
} }
private: private:
@ -192,7 +194,7 @@ public:
} }
} }
~Effect_MultiplyColor() {} ~Effect_MultiplyColor() {}
void Set(Handle tex) void Set(const CTexturePtr& tex)
{ {
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
@ -226,12 +228,14 @@ public:
glTexEnviv(GL_TEXTURE_ENV, GL_RGB_SCALE, TexScale4); glTexEnviv(GL_TEXTURE_ENV, GL_RGB_SCALE, TexScale4);
} }
ogl_tex_bind(tex); tex->Bind();
} }
void Unset() void Unset()
{ {
if (m_Scale != 1) if (m_Scale != 1)
glTexEnviv(GL_TEXTURE_ENV, GL_RGB_SCALE, TexScale1); glTexEnviv(GL_TEXTURE_ENV, GL_RGB_SCALE, TexScale1);
glColor4f(1.f, 1.f, 1.f, 1.f);
} }
private: private:
CColor m_Color; CColor m_Color;
@ -248,7 +252,7 @@ class Effect_Greyscale : public IGLState
{ {
public: public:
~Effect_Greyscale() {} ~Effect_Greyscale() {}
void Set(Handle tex) void Set(const CTexturePtr& tex)
{ {
/* /*
@ -288,7 +292,7 @@ public:
// Texture unit 0: // Texture unit 0:
ogl_tex_bind(tex, 0); tex->Bind(0);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
@ -308,7 +312,7 @@ public:
// Texture unit 1: // Texture unit 1:
ogl_tex_bind(tex, 1); tex->Bind(1);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
@ -339,6 +343,8 @@ public:
{ {
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
pglActiveTextureARB(GL_TEXTURE0); pglActiveTextureARB(GL_TEXTURE0);
glColor4f(1.f, 1.f, 1.f, 1.f);
} }
}; };
@ -399,7 +405,7 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName,
std::vector<SGUIImage>::const_iterator cit; std::vector<SGUIImage>::const_iterator cit;
for (cit = it->second.m_Images.begin(); cit != it->second.m_Images.end(); ++cit) for (cit = it->second.m_Images.begin(); cit != it->second.m_Images.end(); ++cit)
{ {
SDrawCall Call; SDrawCall Call(&*cit); // pointers are safe since we never modify sprites/images after startup
CRect ObjectSize = cit->m_Size.GetClientArea(Size); CRect ObjectSize = cit->m_Size.GetClientArea(Size);
@ -418,94 +424,20 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName,
if (! cit->m_TextureName.empty()) if (! cit->m_TextureName.empty())
{ {
Handle h = ogl_tex_load(g_VFS, cit->m_TextureName); CTextureProperties textureProps(cit->m_TextureName);
if (h <= 0) CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
{ texture->Prefetch();
LOG(CLogger::Error, LOG_CATEGORY, L"Error reading texture '%ls': %ld", cit->m_TextureName.string().c_str(), (long)h); Call.m_HasTexture = true;
return; Call.m_Texture = texture;
}
(void)ogl_tex_set_filter(h, GL_LINEAR); Call.m_EnableBlending = false; // will be overridden if the texture has an alpha channel
int err = ogl_tex_upload(h); Call.m_ObjectSize = ObjectSize;
if (err < 0) Call.m_CellID = CellID;
{
LOG(CLogger::Error, LOG_CATEGORY, L"Error uploading texture '%ls': %d", cit->m_TextureName.string().c_str(), err);
return;
}
Call.m_TexHandle = h;
size_t t_w = 0, t_h = 0;
(void)ogl_tex_get_size(h, &t_w, &t_h, 0);
float TexWidth = t_w, TexHeight = t_h;
size_t flags = 0; // assume no alpha on failure
(void)ogl_tex_get_format(h, &flags, 0);
Call.m_EnableBlending = (flags & TEX_ALPHA) != 0;
// Textures are positioned by defining a rectangular block of the
// texture (usually the whole texture), and a rectangular block on
// the screen. The texture is positioned to make those blocks line up.
// Get the screen's position/size for the block
CRect BlockScreen = cit->m_TextureSize.GetClientArea(ObjectSize);
// Get the texture's position/size for the block:
CRect BlockTex;
// "real_texture_placement" overrides everything
if (cit->m_TexturePlacementInFile != CRect())
{
BlockTex = cit->m_TexturePlacementInFile;
}
// Check whether this sprite has "cell_size" set
else if (cit->m_CellSize != CSize())
{
int cols = (int)t_w / (int)cit->m_CellSize.cx;
int col = CellID % cols;
int row = CellID / cols;
BlockTex = CRect(cit->m_CellSize.cx*col, cit->m_CellSize.cy*row,
cit->m_CellSize.cx*(col+1), cit->m_CellSize.cy*(row+1));
}
// Use the whole texture
else
BlockTex = CRect(0, 0, TexWidth, TexHeight);
// When rendering, BlockTex will be transformed onto BlockScreen.
// Also, TexCoords will be transformed onto ObjectSize (giving the
// UV coords at each vertex of the object). We know everything
// except for TexCoords, so calculate it:
CPos translation (BlockTex.TopLeft()-BlockScreen.TopLeft());
float ScaleW = BlockTex.GetWidth()/BlockScreen.GetWidth();
float ScaleH = BlockTex.GetHeight()/BlockScreen.GetHeight();
CRect TexCoords (
// Resize (translating to/from the origin, so the
// topleft corner stays in the same place)
(ObjectSize-ObjectSize.TopLeft())
.Scale(ScaleW, ScaleH)
+ ObjectSize.TopLeft()
// Translate from BlockTex to BlockScreen
+ translation
);
// The tex coords need to be scaled so that (texwidth,texheight) is
// mapped onto (1,1)
TexCoords.left /= TexWidth;
TexCoords.right /= TexWidth;
TexCoords.top /= TexHeight;
TexCoords.bottom /= TexHeight;
Call.m_TexCoords = TexCoords;
} }
else else
{ {
Call.m_TexHandle = 0; Call.m_HasTexture = false;
// Enable blending if it's transparent (allowing a little error in the calculations) // Enable blending if it's transparent (allowing a little error in the calculations)
Call.m_EnableBlending = !(fabs(cit->m_BackColor.a - 1.0f) < 0.0000001f); Call.m_EnableBlending = !(fabs(cit->m_BackColor.a - 1.0f) < 0.0000001f);
} }
@ -550,6 +482,75 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName,
} }
} }
CRect SDrawCall::ComputeTexCoords() const
{
float TexWidth = m_Texture->GetWidth();
float TexHeight = m_Texture->GetHeight();
if (!TexWidth || !TexHeight)
{
return CRect(0, 0, 1, 1);
}
// Textures are positioned by defining a rectangular block of the
// texture (usually the whole texture), and a rectangular block on
// the screen. The texture is positioned to make those blocks line up.
// Get the screen's position/size for the block
CRect BlockScreen = m_Image->m_TextureSize.GetClientArea(m_ObjectSize);
// Get the texture's position/size for the block:
CRect BlockTex;
// "real_texture_placement" overrides everything
if (m_Image->m_TexturePlacementInFile != CRect())
{
BlockTex = m_Image->m_TexturePlacementInFile;
}
// Check whether this sprite has "cell_size" set (and non-zero)
else if ((int)m_Image->m_CellSize.cx)
{
int cols = (int)TexWidth / (int)m_Image->m_CellSize.cx;
if (cols == 0)
cols = 1; // avoid divide-by-zero
int col = m_CellID % cols;
int row = m_CellID / cols;
BlockTex = CRect(m_Image->m_CellSize.cx*col, m_Image->m_CellSize.cy*row,
m_Image->m_CellSize.cx*(col+1), m_Image->m_CellSize.cy*(row+1));
}
// Use the whole texture
else
BlockTex = CRect(0, 0, TexWidth, TexHeight);
// When rendering, BlockTex will be transformed onto BlockScreen.
// Also, TexCoords will be transformed onto ObjectSize (giving the
// UV coords at each vertex of the object). We know everything
// except for TexCoords, so calculate it:
CPos translation (BlockTex.TopLeft()-BlockScreen.TopLeft());
float ScaleW = BlockTex.GetWidth()/BlockScreen.GetWidth();
float ScaleH = BlockTex.GetHeight()/BlockScreen.GetHeight();
CRect TexCoords (
// Resize (translating to/from the origin, so the
// topleft corner stays in the same place)
(m_ObjectSize-m_ObjectSize.TopLeft())
.Scale(ScaleW, ScaleH)
+ m_ObjectSize.TopLeft()
// Translate from BlockTex to BlockScreen
+ translation
);
// The tex coords need to be scaled so that (texwidth,texheight) is
// mapped onto (1,1)
TexCoords.left /= TexWidth;
TexCoords.right /= TexWidth;
TexCoords.top /= TexHeight;
TexCoords.bottom /= TexHeight;
return TexCoords;
}
void GUIRenderer::Draw(DrawCalls &Calls) void GUIRenderer::Draw(DrawCalls &Calls)
{ {
// Called every frame, to draw the object (based on cached calculations) // Called every frame, to draw the object (based on cached calculations)
@ -559,38 +560,40 @@ void GUIRenderer::Draw(DrawCalls &Calls)
// Iterate through each DrawCall, and execute whatever drawing code is being called // Iterate through each DrawCall, and execute whatever drawing code is being called
for (DrawCalls::const_iterator cit = Calls.begin(); cit != Calls.end(); ++cit) for (DrawCalls::const_iterator cit = Calls.begin(); cit != Calls.end(); ++cit)
{ {
if (cit->m_EnableBlending) if (cit->m_HasTexture)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
if (cit->m_TexHandle)
{ {
// TODO: Handle the GL state in a nicer way // TODO: Handle the GL state in a nicer way
if (cit->m_Effects) if (cit->m_Effects)
cit->m_Effects->Set(cit->m_TexHandle); cit->m_Effects->Set(cit->m_Texture);
else else
{ {
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
ogl_tex_bind(cit->m_TexHandle); cit->m_Texture->Bind();
} }
if (cit->m_EnableBlending || cit->m_Texture->HasAlpha()) // (shouldn't call HasAlpha before Bind)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
CRect TexCoords = cit->ComputeTexCoords();
glBegin(GL_QUADS); glBegin(GL_QUADS);
glTexCoord2f(cit->m_TexCoords.right,cit->m_TexCoords.bottom); glTexCoord2f(TexCoords.right, TexCoords.bottom);
glVertex3f(cit->m_Vertices.right, cit->m_Vertices.bottom, cit->m_DeltaZ); glVertex3f(cit->m_Vertices.right, cit->m_Vertices.bottom, cit->m_DeltaZ);
glTexCoord2f(cit->m_TexCoords.left, cit->m_TexCoords.bottom); glTexCoord2f(TexCoords.left, TexCoords.bottom);
glVertex3f(cit->m_Vertices.left, cit->m_Vertices.bottom, cit->m_DeltaZ); glVertex3f(cit->m_Vertices.left, cit->m_Vertices.bottom, cit->m_DeltaZ);
glTexCoord2f(cit->m_TexCoords.left, cit->m_TexCoords.top); glTexCoord2f(TexCoords.left, TexCoords.top);
glVertex3f(cit->m_Vertices.left, cit->m_Vertices.top, cit->m_DeltaZ); glVertex3f(cit->m_Vertices.left, cit->m_Vertices.top, cit->m_DeltaZ);
glTexCoord2f(cit->m_TexCoords.right,cit->m_TexCoords.top); glTexCoord2f(TexCoords.right, TexCoords.top);
glVertex3f(cit->m_Vertices.right, cit->m_Vertices.top, cit->m_DeltaZ); glVertex3f(cit->m_Vertices.right, cit->m_Vertices.top, cit->m_DeltaZ);
glEnd(); glEnd();
@ -601,6 +604,12 @@ void GUIRenderer::Draw(DrawCalls &Calls)
{ {
glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_2D);
if (cit->m_EnableBlending)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
glColor4fv(cit->m_BackColor.FloatArray()); glColor4fv(cit->m_BackColor.FloatArray());
glBegin(GL_QUADS); glBegin(GL_QUADS);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -18,12 +18,14 @@
#ifndef GUIRenderer_h #ifndef GUIRenderer_h
#define GUIRenderer_h #define GUIRenderer_h
#include "graphics/Texture.h"
#include "lib/res/handle.h" #include "lib/res/handle.h"
#include "ps/Overlay.h" #include "ps/Overlay.h"
#include "ps/CStr.h" #include "ps/CStr.h"
#include <vector> #include <vector>
struct SGUIImageEffects; struct SGUIImageEffects;
struct SGUIImage;
namespace GUIRenderer namespace GUIRenderer
{ {
@ -31,22 +33,28 @@ namespace GUIRenderer
{ {
public: public:
virtual ~IGLState() {}; virtual ~IGLState() {};
virtual void Set(Handle tex)=0; virtual void Set(const CTexturePtr& tex)=0;
virtual void Unset()=0; virtual void Unset()=0;
}; };
struct SDrawCall struct SDrawCall
{ {
SDrawCall() : m_TexHandle(0), m_Effects(NULL) {} SDrawCall(const SGUIImage* image) : m_Image(image), m_Effects(NULL) {}
CRect ComputeTexCoords() const;
Handle m_TexHandle; const SGUIImage* m_Image;
bool m_HasTexture;
CTexturePtr m_Texture;
CRect m_ObjectSize;
int m_CellID;
bool m_EnableBlending; bool m_EnableBlending;
IGLState* m_Effects; IGLState* m_Effects;
CRect m_Vertices; CRect m_Vertices;
CRect m_TexCoords;
float m_DeltaZ; float m_DeltaZ;
CColor m_BorderColor; // == CColor() for no border CColor m_BorderColor; // == CColor() for no border

View File

@ -39,7 +39,6 @@
#include "simulation2/Simulation2.h" #include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h" #include "simulation2/components/ICmpMinimap.h"
bool g_TerrainModified = false;
bool g_GameRestarted = false; bool g_GameRestarted = false;
static unsigned int ScaleColor(unsigned int color, float x) static unsigned int ScaleColor(unsigned int color, float x)
@ -52,7 +51,7 @@ static unsigned int ScaleColor(unsigned int color, float x)
CMiniMap::CMiniMap() CMiniMap::CMiniMap()
: m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), : m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0),
m_LOSTexture(0), m_LOSData(0) m_LOSTexture(0), m_LOSData(0), m_TerrainDirty(true)
{ {
AddSetting(GUIST_CColor, "fov_wedge_color"); AddSetting(GUIST_CColor, "fov_wedge_color");
AddSetting(GUIST_CStr, "tooltip"); AddSetting(GUIST_CStr, "tooltip");
@ -232,21 +231,21 @@ void CMiniMap::Draw()
if(!m_TerrainTexture || g_GameRestarted) if(!m_TerrainTexture || g_GameRestarted)
CreateTextures(); CreateTextures();
// do not limit this as with LOS updates below - we must update
// immediately after changes are reported because this flag will be
// reset at the end of the frame.
if(g_TerrainModified)
RebuildTerrainTexture();
// only update 10x / second // only update 10x / second
// (note: since units only move a few pixels per second on the minimap, // (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow, ~20ms) // we can get away with infrequent updates; this is slow, ~20ms)
// TODO: we don't need to do it this frequently, it only needs to be once
// per simulation turn at most
static double last_time; static double last_time;
const double cur_time = timer_Time(); const double cur_time = timer_Time();
if(cur_time - last_time > 100e-3) // 10 updates/sec if(cur_time - last_time > 100e-3) // 10 updates/sec
{ {
last_time = cur_time; last_time = cur_time;
if(m_TerrainDirty)
RebuildTerrainTexture();
// CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); // CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
// if(losMgr->m_LOSSetting != LOS_SETTING_ALL_VISIBLE) // if(losMgr->m_LOSSetting != LOS_SETTING_ALL_VISIBLE)
// RebuildLOSTexture(); // RebuildLOSTexture();
@ -442,6 +441,8 @@ void CMiniMap::RebuildTerrainTexture()
u32 h = m_MapSize - 1; u32 h = m_MapSize - 1;
float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
m_TerrainDirty = false;
for(u32 j = 0; j < h; j++) for(u32 j = 0; j < h; j++)
{ {
u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
@ -469,7 +470,14 @@ void CMiniMap::RebuildTerrainTexture()
{ {
CTerrainTextureEntry *tex = mp->GetTextureEntry(); CTerrainTextureEntry *tex = mp->GetTextureEntry();
if(tex) if(tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if(!tex->GetTexture()->TryLoad())
m_TerrainDirty = true;
color = tex->GetBaseColor(); color = tex->GetBaseColor();
}
} }
*dataPtr++ = ScaleColor(color, float(val) / 255.0f); *dataPtr++ = ScaleColor(color, float(val) / 255.0f);

View File

@ -23,8 +23,6 @@
class CCamera; class CCamera;
class CTerrain; class CTerrain;
extern bool g_TerrainModified;
class CMiniMap : public IGUIObject class CMiniMap : public IGUIObject
{ {
GUI_OBJECT(CMiniMap) GUI_OBJECT(CMiniMap)
@ -68,6 +66,9 @@ protected:
u32* m_TerrainData; u32* m_TerrainData;
u8* m_LOSData; u8* m_LOSData;
// whether we need to regenerate the terrain texture
bool m_TerrainDirty;
ssize_t m_Width, m_Height; ssize_t m_Width, m_Height;
// map size // map size

View File

@ -63,13 +63,14 @@ that of Atlas depending on commandline parameters.
#include "network/NetSession.h" #include "network/NetSession.h"
#include "graphics/Camera.h" #include "graphics/Camera.h"
#include "graphics/GameView.h" #include "graphics/GameView.h"
#include "graphics/TextureManager.h"
#include "gui/GUIManager.h"
#include "renderer/Renderer.h"
#include "scripting/ScriptingHost.h" #include "scripting/ScriptingHost.h"
#include "simulation2/Simulation2.h" #include "simulation2/Simulation2.h"
#include "gui/GUIManager.h"
#define LOG_CATEGORY L"main" #define LOG_CATEGORY L"main"
extern bool g_TerrainModified;
extern bool g_GameRestarted; extern bool g_GameRestarted;
void kill_mainloop(); void kill_mainloop();
@ -200,6 +201,19 @@ static int ProgressiveLoad()
} }
static void RendererIncrementalLoad()
{
const double maxTime = 0.1f;
double startTime = timer_Time();
bool more;
do {
more = g_Renderer.GetTextureManager().MakeProgress();
}
while (more && timer_Time() - startTime < maxTime);
}
static bool quit = false; // break out of main loop static bool quit = false; // break out of main loop
static void Frame() static void Frame()
@ -266,6 +280,10 @@ static void Frame()
ProgressiveLoad(); ProgressiveLoad();
PROFILE_END("progressive load"); PROFILE_END("progressive load");
PROFILE_START("renderer incremental load");
RendererIncrementalLoad();
PROFILE_END("renderer incremental load");
PROFILE_START("input"); PROFILE_START("input");
MICROLOG(L"input"); MICROLOG(L"input");
PumpEvents(); PumpEvents();
@ -360,7 +378,6 @@ static void Frame()
g_Profiler.Frame(); g_Profiler.Frame();
g_TerrainModified = false;
g_GameRestarted = false; g_GameRestarted = false;
} }

View File

@ -32,6 +32,7 @@
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "graphics/Model.h" #include "graphics/Model.h"
#include "graphics/ModelDef.h" #include "graphics/ModelDef.h"
#include "graphics/TextureManager.h"
#include "renderer/ModelRenderer.h" #include "renderer/ModelRenderer.h"
#include "renderer/ModelVertexRenderer.h" #include "renderer/ModelVertexRenderer.h"
@ -324,7 +325,6 @@ void BatchModelRenderer::Submit(CModel* model)
rdata = bmrdata; rdata = bmrdata;
model->SetRenderData(bmrdata); model->SetRenderData(bmrdata);
model->SetDirty(~0u); model->SetDirty(~0u);
g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE);
} }
// Add the model def tracker to the submission list if necessary // Add the model def tracker to the submission list if necessary
@ -335,14 +335,14 @@ void BatchModelRenderer::Submit(CModel* model)
} }
// Add the bmrdata to the modeldef list // Add the bmrdata to the modeldef list
Handle htex = model->GetTexture()->GetHandle(); CTexturePtr tex = model->GetTexture();
size_t idx; size_t idx;
for(idx = 0; idx < mdeftracker->m_Slots; ++idx) for(idx = 0; idx < mdeftracker->m_Slots; ++idx)
{ {
BMRModelData* in = mdeftracker->m_ModelSlots[idx]; BMRModelData* in = mdeftracker->m_ModelSlots[idx];
if (in->GetModel()->GetTexture()->GetHandle() == htex) if (in->GetModel()->GetTexture() == tex)
break; break;
} }

View File

@ -21,7 +21,6 @@
#include <set> #include <set>
#include <algorithm> #include <algorithm>
#include "ps/Pyrogenesis.h" #include "ps/Pyrogenesis.h"
#include "lib/res/graphics/ogl_tex.h"
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "Renderer.h" #include "Renderer.h"
#include "renderer/PatchRData.h" #include "renderer/PatchRData.h"
@ -70,12 +69,12 @@ CPatchRData::~CPatchRData()
} }
static Handle GetTerrainTileTexture(CTerrain* terrain,ssize_t gx,ssize_t gz) static CTerrainTextureEntry* GetTerrainTileTexture(CTerrain* terrain, ssize_t gx, ssize_t gz)
{ {
CMiniPatch* mp=terrain->GetTile(gx,gz); CMiniPatch* mp = terrain->GetTile(gx, gz);
if (!mp) if (!mp)
return 0; return 0;
return mp->GetHandle(); return mp->GetTextureEntry();
} }
const float uvFactor = 0.125f / sqrt(2.f); const float uvFactor = 0.125f / sqrt(2.f);
@ -87,7 +86,7 @@ static void CalculateUV(float uv[2], ssize_t x, ssize_t z)
} }
struct STmpSplat { struct STmpSplat {
Handle m_Texture; CTerrainTextureEntry* m_Texture;
u16 m_Indices[4]; u16 m_Indices[4];
}; };
@ -103,7 +102,7 @@ void CPatchRData::BuildBlends()
// temporary list of splats // temporary list of splats
std::vector<STmpSplat> splats; std::vector<STmpSplat> splats;
// set of textures used for splats // set of textures used for splats
std::set<Handle> splatTextures; std::set<CTerrainTextureEntry*> splatTextures;
// for each tile in patch .. // for each tile in patch ..
for (ssize_t j=0;j<PATCH_SIZE;j++) { for (ssize_t j=0;j<PATCH_SIZE;j++) {
@ -120,7 +119,7 @@ void CPatchRData::BuildBlends()
if (nmp && nmp->GetTextureEntry() != mp->GetTextureEntry()) { if (nmp && nmp->GetTextureEntry() != mp->GetTextureEntry()) {
if (nmp->GetPriority() > mp->GetPriority() || (nmp->GetPriority() == mp->GetPriority() && nmp->GetTextureEntry() > mp->GetTextureEntry())) { if (nmp->GetPriority() > mp->GetPriority() || (nmp->GetPriority() == mp->GetPriority() && nmp->GetTextureEntry() > mp->GetTextureEntry())) {
STex tex; STex tex;
tex.m_Handle=nmp->GetHandle(); tex.m_Texture=nmp->GetTextureEntry();
tex.m_Priority=nmp->GetPriority(); tex.m_Priority=nmp->GetPriority();
if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) { if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) {
neighbourTextures.push_back(tex); neighbourTextures.push_back(tex);
@ -145,9 +144,9 @@ void CPatchRData::BuildBlends()
ssize_t oz=gz+BlendOffsets[m][0]; ssize_t oz=gz+BlendOffsets[m][0];
// get texture on adjacent tile // get texture on adjacent tile
Handle atex=GetTerrainTileTexture(terrain,ox,oz); CTerrainTextureEntry* atex=GetTerrainTileTexture(terrain,ox,oz);
// fill 0/1 into shape array // fill 0/1 into shape array
shape[m]=(atex==neighbourTextures[k].m_Handle) ? 0 : 1; shape[m]=(atex==neighbourTextures[k].m_Texture) ? 0 : 1;
} }
// calculate the required alphamap and the required rotation of the alphamap from blendshape // calculate the required alphamap and the required rotation of the alphamap from blendshape
@ -239,7 +238,7 @@ void CPatchRData::BuildBlends()
// build a splat for this quad // build a splat for this quad
STmpSplat splat; STmpSplat splat;
splat.m_Texture=neighbourTextures[k].m_Handle; splat.m_Texture=neighbourTextures[k].m_Texture;
splat.m_Indices[0]=(u16)(vindex); splat.m_Indices[0]=(u16)(vindex);
splat.m_Indices[1]=(u16)(vindex+1); splat.m_Indices[1]=(u16)(vindex+1);
splat.m_Indices[2]=(u16)(vindex+2); splat.m_Indices[2]=(u16)(vindex+2);
@ -270,9 +269,9 @@ void CPatchRData::BuildBlends()
debug_assert(m_VBBlends->m_Index < 65536); debug_assert(m_VBBlends->m_Index < 65536);
unsigned short base = (unsigned short)m_VBBlends->m_Index; unsigned short base = (unsigned short)m_VBBlends->m_Index;
std::set<Handle>::iterator iter=splatTextures.begin(); std::set<CTerrainTextureEntry*>::iterator iter=splatTextures.begin();
for (;iter!=splatTextures.end();++iter) { for (;iter!=splatTextures.end();++iter) {
Handle tex=*iter; CTerrainTextureEntry* tex=*iter;
SSplat& splat=m_BlendSplats[splatCount]; SSplat& splat=m_BlendSplats[splatCount];
splat.m_IndexStart=m_BlendIndices.size(); splat.m_IndexStart=m_BlendIndices.size();
@ -306,14 +305,14 @@ void CPatchRData::BuildIndices()
m_Splats.clear(); m_Splats.clear();
// build grid of textures on this patch and boundaries of adjacent patches // build grid of textures on this patch and boundaries of adjacent patches
std::vector<Handle> textures; std::vector<CTerrainTextureEntry*> textures;
Handle texgrid[PATCH_SIZE][PATCH_SIZE]; CTerrainTextureEntry* texgrid[PATCH_SIZE][PATCH_SIZE];
for (ssize_t j=0;j<PATCH_SIZE;j++) { for (ssize_t j=0;j<PATCH_SIZE;j++) {
for (ssize_t i=0;i<PATCH_SIZE;i++) { for (ssize_t i=0;i<PATCH_SIZE;i++) {
Handle h=m_Patch->m_MiniPatches[j][i].GetHandle(); CTerrainTextureEntry* tex=m_Patch->m_MiniPatches[j][i].GetTextureEntry();
texgrid[j][i]=h; texgrid[j][i]=tex;
if (std::find(textures.begin(),textures.end(),h)==textures.end()) { if (std::find(textures.begin(),textures.end(),tex)==textures.end()) {
textures.push_back(h); textures.push_back(tex);
} }
} }
} }
@ -323,15 +322,15 @@ void CPatchRData::BuildIndices()
// build indices for base splats // build indices for base splats
size_t base=m_VBBase->m_Index; size_t base=m_VBBase->m_Index;
for (size_t i=0;i<m_Splats.size();i++) { for (size_t i=0;i<m_Splats.size();i++) {
Handle h=textures[i]; CTerrainTextureEntry* tex=textures[i];
SSplat& splat=m_Splats[i]; SSplat& splat=m_Splats[i];
splat.m_Texture=h; splat.m_Texture=tex;
splat.m_IndexStart=m_Indices.size(); splat.m_IndexStart=m_Indices.size();
for (ssize_t j=0;j<PATCH_SIZE;j++) { for (ssize_t j=0;j<PATCH_SIZE;j++) {
for (ssize_t i=0;i<PATCH_SIZE;i++) { for (ssize_t i=0;i<PATCH_SIZE;i++) {
if (texgrid[j][i]==h){ if (texgrid[j][i]==tex){
m_Indices.push_back(u16(((j+0)*vsize+(i+0))+base)); m_Indices.push_back(u16(((j+0)*vsize+(i+0))+base));
m_Indices.push_back(u16(((j+0)*vsize+(i+1))+base)); m_Indices.push_back(u16(((j+0)*vsize+(i+1))+base));
m_Indices.push_back(u16(((j+1)*vsize+(i+1))+base)); m_Indices.push_back(u16(((j+1)*vsize+(i+1))+base));
@ -516,7 +515,11 @@ void CPatchRData::RenderBase(bool losColor)
// render each splat // render each splat
for (size_t i=0;i<m_Splats.size();i++) { for (size_t i=0;i<m_Splats.size();i++) {
SSplat& splat=m_Splats[i]; SSplat& splat=m_Splats[i];
ogl_tex_bind(splat.m_Texture);
if (splat.m_Texture)
splat.m_Texture->GetTexture()->Bind();
else
g_Renderer.GetTextureManager().GetErrorTexture()->Bind();
if (!g_Renderer.m_SkipSubmit) { if (!g_Renderer.m_SkipSubmit) {
glDrawElements(GL_QUADS, (GLsizei)splat.m_IndexCount, glDrawElements(GL_QUADS, (GLsizei)splat.m_IndexCount,
@ -588,7 +591,11 @@ void CPatchRData::RenderBlends()
for (size_t i=0;i<m_BlendSplats.size();i++) { for (size_t i=0;i<m_BlendSplats.size();i++) {
SSplat& splat=m_BlendSplats[i]; SSplat& splat=m_BlendSplats[i];
ogl_tex_bind(splat.m_Texture);
if (splat.m_Texture)
splat.m_Texture->GetTexture()->Bind();
else
g_Renderer.GetTextureManager().GetErrorTexture()->Bind();
if (!g_Renderer.m_SkipSubmit) { if (!g_Renderer.m_SkipSubmit) {
glDrawElements(GL_QUADS, (GLsizei)splat.m_IndexCount, glDrawElements(GL_QUADS, (GLsizei)splat.m_IndexCount,

View File

@ -25,6 +25,7 @@
#include "VertexBufferManager.h" #include "VertexBufferManager.h"
class CPatch; class CPatch;
class CTerrainTextureEntry;
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
// CPatchRData: class encapsulating logic for rendering terrain patches; holds per // CPatchRData: class encapsulating logic for rendering terrain patches; holds per
@ -45,8 +46,8 @@ private:
struct SSplat { struct SSplat {
SSplat() : m_Texture(0), m_IndexCount(0) {} SSplat() : m_Texture(0), m_IndexCount(0) {}
// handle of texture to apply during splat // texture to apply during splat
Handle m_Texture; CTerrainTextureEntry* m_Texture;
// offset into the index array for this patch where splat starts // offset into the index array for this patch where splat starts
size_t m_IndexStart; size_t m_IndexStart;
// number of indices used by splat // number of indices used by splat
@ -76,9 +77,9 @@ private:
}; };
struct STex { struct STex {
bool operator==(const STex& rhs) const { return m_Handle==rhs.m_Handle; } bool operator==(const STex& rhs) const { return m_Texture==rhs.m_Texture; }
bool operator<(const STex& rhs) const { return m_Priority<rhs.m_Priority; } bool operator<(const STex& rhs) const { return m_Priority<rhs.m_Priority; }
Handle m_Handle; CTerrainTextureEntry* m_Texture;
int m_Priority; int m_Priority;
}; };

View File

@ -27,10 +27,8 @@
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "graphics/Model.h" #include "graphics/Model.h"
#include "graphics/TextureManager.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY L"graphics"
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -123,11 +121,11 @@ bool FastPlayerColorRender::EndPass(int UNUSED(pass))
return true; return true;
} }
void FastPlayerColorRender::PrepareTexture(int UNUSED(pass), CTexture* texture) void FastPlayerColorRender::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(2, texture); texture->Bind(2);
g_Renderer.SetTexture(1, texture); texture->Bind(1);
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
void FastPlayerColorRender::PrepareModel(int UNUSED(pass), CModel* model) void FastPlayerColorRender::PrepareModel(int UNUSED(pass), CModel* model)
@ -234,11 +232,11 @@ bool SlowPlayerColorRender::EndPass(int pass)
return true; return true;
} }
void SlowPlayerColorRender::PrepareTexture(int pass, CTexture* texture) void SlowPlayerColorRender::PrepareTexture(int pass, CTexturePtr& texture)
{ {
if (pass == 1) if (pass == 1)
g_Renderer.SetTexture(1, texture); texture->Bind(1);
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
@ -393,10 +391,10 @@ const CMatrix3D* LitPlayerColorRender::GetTexGenMatrix(int UNUSED(pass))
return &GetShadowMap()->GetTextureMatrix(); return &GetShadowMap()->GetTextureMatrix();
} }
void LitPlayerColorRender::PrepareTexture(int pass, CTexture* texture) void LitPlayerColorRender::PrepareTexture(int pass, CTexturePtr& texture)
{ {
if (pass == 0) if (pass == 0)
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
void LitPlayerColorRender::PrepareModel(int pass, CModel* model) void LitPlayerColorRender::PrepareModel(int pass, CModel* model)
@ -410,6 +408,3 @@ void LitPlayerColorRender::PrepareModel(int pass, CModel* model)
glColor3f(colour.r, colour.g, colour.b); glColor3f(colour.r, colour.g, colour.b);
} }
} }

View File

@ -40,7 +40,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
/** /**
@ -73,7 +73,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };
@ -95,7 +95,7 @@ public:
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
const CMatrix3D* GetTexGenMatrix(int pass); const CMatrix3D* GetTexGenMatrix(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };

View File

@ -27,17 +27,14 @@
#include "maths/Matrix3D.h" #include "maths/Matrix3D.h"
#include "ps/CLogger.h"
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "graphics/Model.h" #include "graphics/Model.h"
#include "graphics/TextureManager.h"
#include "renderer/RenderModifiers.h" #include "renderer/RenderModifiers.h"
#include "renderer/Renderer.h" #include "renderer/Renderer.h"
#include "renderer/ShadowMap.h" #include "renderer/ShadowMap.h"
#define LOG_CATEGORY L"graphics"
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
@ -126,9 +123,9 @@ bool PlainRenderModifier::EndPass(int UNUSED(pass))
return true; return true;
} }
void PlainRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* texture) void PlainRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
@ -209,9 +206,9 @@ bool PlainLitRenderModifier::EndPass(int UNUSED(pass))
return true; return true;
} }
void PlainLitRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* texture) void PlainLitRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
@ -237,7 +234,7 @@ int WireframeRenderModifier::BeginPass(int pass)
// setup some renderstate .. // setup some renderstate ..
glDepthMask(0); glDepthMask(0);
g_Renderer.SetTexture(0,0); ogl_tex_bind(0, 0);
glColor4f(1,1,1,0.75f); glColor4f(1,1,1,0.75f);
glLineWidth(1.0f); glLineWidth(1.0f);
@ -261,7 +258,7 @@ bool WireframeRenderModifier::EndPass(int UNUSED(pass))
} }
void WireframeRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* UNUSED(texture)) void WireframeRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& UNUSED(texture))
{ {
} }
@ -285,7 +282,7 @@ SolidColorRenderModifier::~SolidColorRenderModifier()
int SolidColorRenderModifier::BeginPass(int UNUSED(pass)) int SolidColorRenderModifier::BeginPass(int UNUSED(pass))
{ {
g_Renderer.SetTexture(0,0); ogl_tex_bind(0, 0);
return STREAM_POS; return STREAM_POS;
} }
@ -295,7 +292,7 @@ bool SolidColorRenderModifier::EndPass(int UNUSED(pass))
return true; return true;
} }
void SolidColorRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* UNUSED(texture)) void SolidColorRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& UNUSED(texture))
{ {
} }

View File

@ -25,12 +25,11 @@
#define INCLUDED_RENDERMODIFIERS #define INCLUDED_RENDERMODIFIERS
#include "ModelRenderer.h" #include "ModelRenderer.h"
#include "graphics/Texture.h"
class CLightEnv; class CLightEnv;
class CMatrix3D; class CMatrix3D;
class CModel; class CModel;
class CTexture;
class ShadowMap; class ShadowMap;
/** /**
@ -94,7 +93,7 @@ public:
* @param pass The current pass number (pass == 0 is the first pass) * @param pass The current pass number (pass == 0 is the first pass)
* @param texture The texture used by subsequent models * @param texture The texture used by subsequent models
*/ */
virtual void PrepareTexture(int pass, CTexture* texture) = 0; virtual void PrepareTexture(int pass, CTexturePtr& texture) = 0;
/** /**
* PrepareModel: Called before rendering the given model. * PrepareModel: Called before rendering the given model.
@ -172,7 +171,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
}; };
@ -191,7 +190,7 @@ public:
int BeginPass(int pass); int BeginPass(int pass);
const CMatrix3D* GetTexGenMatrix(int pass); const CMatrix3D* GetTexGenMatrix(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
}; };
@ -207,7 +206,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };
@ -227,7 +226,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -40,6 +40,7 @@
#include "ps/ProfileViewer.h" #include "ps/ProfileViewer.h"
#include "graphics/Camera.h" #include "graphics/Camera.h"
#include "graphics/Texture.h" #include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "graphics/Terrain.h" #include "graphics/Terrain.h"
#include "graphics/Model.h" #include "graphics/Model.h"
@ -214,6 +215,9 @@ public:
/// Sky manager /// Sky manager
SkyManager skyManager; SkyManager skyManager;
/// Texture manager
CTextureManager textureManager;
/// Terrain renderer /// Terrain renderer
TerrainRenderer* terrainRenderer; TerrainRenderer* terrainRenderer;
@ -273,7 +277,7 @@ public:
CRendererInternals() CRendererInternals()
: IsOpen(false), profileTable(g_Renderer.m_Stats) : IsOpen(false), profileTable(g_Renderer.m_Stats), textureManager(g_VFS)
{ {
terrainRenderer = new TerrainRenderer(); terrainRenderer = new TerrainRenderer();
shadow = new ShadowMap(); shadow = new ShadowMap();
@ -889,7 +893,7 @@ void CRenderer::RenderPatches()
// setup some renderstate .. // setup some renderstate ..
glDepthMask(0); glDepthMask(0);
SetTexture(0,0); ogl_tex_bind(0, 0);
glColor4f(1,1,1,0.35f); glColor4f(1,1,1,0.35f);
glLineWidth(2.0f); glLineWidth(2.0f);
@ -1326,7 +1330,7 @@ void CRenderer::RenderSubmissions()
// EndFrame: signal frame end // EndFrame: signal frame end
void CRenderer::EndFrame() void CRenderer::EndFrame()
{ {
g_Renderer.SetTexture(0,0); ogl_tex_bind(0, 0);
static bool once=false; static bool once=false;
if (!once && glGetError()) { if (!once && glGetError()) {
@ -1440,48 +1444,6 @@ void CRenderer::RenderScene(Scene *scene)
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// LoadTexture: try and load the given texture; set clamp/repeat flags on texture object if necessary
bool CRenderer::LoadTexture(CTexture* texture,int wrapflags)
{
const Handle errorhandle = -1;
Handle h = texture->GetHandle();
// already tried to load this texture
if (h)
{
// nothing to do here - just return success according to
// whether this is a valid handle or not
return h==errorhandle ? true : false;
}
h = ogl_tex_load(g_VFS, texture->GetName());
if (h <= 0)
{
LOG(CLogger::Error, LOG_CATEGORY, L"LoadTexture failed on \"%ls\"", texture->GetName().string().c_str());
texture->SetHandle(errorhandle);
return false;
}
if(!wrapflags)
wrapflags = GL_CLAMP_TO_EDGE;
(void)ogl_tex_set_wrap(h, wrapflags);
(void)ogl_tex_set_filter(h, GL_LINEAR_MIPMAP_LINEAR);
// (this also verifies that the texture is a power-of-two)
if(ogl_tex_upload(h) < 0)
{
LOG(CLogger::Error, LOG_CATEGORY, L"LoadTexture failed on \"%ls\" : upload failed", texture->GetName().string().c_str());
ogl_tex_free(h);
texture->SetHandle(errorhandle);
return false;
}
texture->SetHandle(h);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BindTexture: bind a GL texture object to current active unit // BindTexture: bind a GL texture object to current active unit
void CRenderer::BindTexture(int unit,GLuint tex) void CRenderer::BindTexture(int unit,GLuint tex)
@ -1497,32 +1459,6 @@ void CRenderer::BindTexture(int unit,GLuint tex)
m_ActiveTextures[unit]=tex; m_ActiveTextures[unit]=tex;
} }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetTexture: set the given unit to reference the given texture; pass a null texture to disable texturing on any unit
void CRenderer::SetTexture(int unit,CTexture* texture)
{
Handle h = texture? texture->GetHandle() : 0;
// Errored textures will give a handle of -1
if (h == -1)
h = 0;
ogl_tex_bind(h, unit);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IsTextureTransparent: return true if given texture is transparent, else false - note texture must be loaded
// beforehand
bool CRenderer::IsTextureTransparent(CTexture* texture)
{
if (!texture) return false;
Handle h=texture->GetHandle();
size_t flags = 0; // assume no alpha on failure
(void)ogl_tex_get_format(h, &flags, 0);
return (flags & TEX_ALPHA) != 0;
}
static inline void CopyTriple(unsigned char* dst,const unsigned char* src) static inline void CopyTriple(unsigned char* dst,const unsigned char* src)
{ {
dst[0]=src[0]; dst[0]=src[0];
@ -1765,4 +1701,7 @@ void CRenderer::ScriptingInit()
} }
CTextureManager& CRenderer::GetTextureManager()
{
return m->textureManager;
}

View File

@ -37,11 +37,11 @@ class CPatch;
class CMaterial; class CMaterial;
class CModel; class CModel;
class CLightEnv; class CLightEnv;
class CTexture;
class RenderPathVertexShader; class RenderPathVertexShader;
class WaterManager; class WaterManager;
class SkyManager; class SkyManager;
class CTextureManager;
// rendering modes // rendering modes
enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES };
@ -247,15 +247,8 @@ public:
// get the mode to render subsequent models // get the mode to render subsequent models
ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; } ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; }
// try and load the given texture
bool LoadTexture(CTexture* texture,int wrapflags);
// set the given unit to reference the given texture; pass a null texture to disable texturing on any unit;
// active texture unit always set to given unit on exit
void SetTexture(int unit,CTexture* texture);
// bind a GL texture object to active unit // bind a GL texture object to active unit
void BindTexture(int unit,GLuint tex); void BindTexture(int unit,GLuint tex);
// query transparency of given texture
bool IsTextureTransparent(CTexture* texture);
// load the default set of alphamaps. // load the default set of alphamaps.
// return a negative error code if anything along the way fails. // return a negative error code if anything along the way fails.
@ -291,6 +284,8 @@ public:
*/ */
SkyManager* GetSkyManager() { return m_SkyManager; } SkyManager* GetSkyManager() { return m_SkyManager; }
CTextureManager& GetTextureManager();
/** /**
* SetFastPlayerColor: Tell the renderer which path to take for * SetFastPlayerColor: Tell the renderer which path to take for
* player colored models. Both paths should provide the same visual * player colored models. Both paths should provide the same visual

View File

@ -39,6 +39,7 @@
#include "graphics/Camera.h" #include "graphics/Camera.h"
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "graphics/TextureManager.h"
#define LOG_CATEGORY L"graphics" #define LOG_CATEGORY L"graphics"
@ -64,74 +65,26 @@ SkyManager::SkyManager()
{ {
m_RenderSky = true; m_RenderSky = true;
// TODO: add a way to set the initial skyset before progressive load m_SkySet = L"";
m_SkySet = L"default";
m_HorizonHeight = -150.0f; m_HorizonHeight = -150.0f;
for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); i++)
m_SkyTexture[i] = 0;
cur_loading_tex = 0;
}
SkyManager::~SkyManager()
{
// Cleanup if the caller messed up
UnloadSkyTextures();
} }
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
// Load single sky textures // Load all sky textures
LibError SkyManager::LoadSkyTexture(size_t index) void SkyManager::LoadSkyTextures()
{ {
wchar_t filename[PATH_MAX]; for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i)
swprintf_s(filename, ARRAY_SIZE(filename), L"art/textures/skies/%ls/%ls.dds", m_SkySet.c_str(), s_imageNames[index]);
Handle ht = ogl_tex_load(g_VFS, filename);
if(ht <= 0)
{ {
LOG(CLogger::Error, LOG_CATEGORY, L"SkyManager::LoadSkyTexture failed on \"%ls\"", filename); VfsPath path = VfsPath(L"art/textures/skies") / std::wstring(m_SkySet) / (std::wstring(s_imageNames[i])+L".dds");
return ht;
CTextureProperties textureProps(path);
textureProps.SetWrap(GL_CLAMP_TO_EDGE);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_SkyTexture[i] = texture;
} }
ogl_tex_set_wrap(ht, GL_CLAMP_TO_EDGE);
m_SkyTexture[index] = ht;
RETURN_ERR(ogl_tex_upload(ht));
return INFO::OK;
}
///////////////////////////////////////////////////////////////////
// Progressive load of sky textures
int SkyManager::LoadSkyTextures()
{
const size_t num_textures = ARRAY_SIZE(m_SkyTexture);
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 100e-3;
while (cur_loading_tex < num_textures)
{
RETURN_ERR(LoadSkyTexture(cur_loading_tex));
cur_loading_tex++;
LDR_CHECK_TIMEOUT(cur_loading_tex, num_textures);
}
return 0;
}
///////////////////////////////////////////////////////////////////
// Unload sky textures
void SkyManager::UnloadSkyTextures()
{
for(size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); i++)
{
ogl_tex_free(m_SkyTexture[i]);
m_SkyTexture[i] = 0;
}
cur_loading_tex = 0;
} }
@ -143,10 +96,7 @@ void SkyManager::SetSkySet( const CStrW& newSet )
return; return;
m_SkySet = newSet; m_SkySet = newSet;
UnloadSkyTextures(); LoadSkyTextures();
for(size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); i++)
LoadSkyTexture(i);
} }
/////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////
@ -182,6 +132,10 @@ void SkyManager::RenderSky()
// Note: The coordinates for this were set up through a rather cumbersome trial-and-error // Note: The coordinates for this were set up through a rather cumbersome trial-and-error
// process - there might be a smarter way to do it, but this seems to work. // process - there might be a smarter way to do it, but this seems to work.
// Do nothing unless SetSkySet was called
if (m_SkySet.empty())
return;
glDepthMask( GL_FALSE ); glDepthMask( GL_FALSE );
pglActiveTextureARB(GL_TEXTURE0_ARB); pglActiveTextureARB(GL_TEXTURE0_ARB);
@ -204,7 +158,7 @@ void SkyManager::RenderSky()
const float D = 2000.0; const float D = 2000.0;
// Front face (positive Z) // Front face (positive Z)
ogl_tex_bind( m_SkyTexture[FRONT] ); m_SkyTexture[FRONT]->Bind();
glBegin( GL_QUADS ); glBegin( GL_QUADS );
glTexCoord2f( 0, 1 ); glTexCoord2f( 0, 1 );
glVertex3f( -D, -D, +D ); glVertex3f( -D, -D, +D );
@ -217,7 +171,7 @@ void SkyManager::RenderSky()
glEnd(); glEnd();
// Back face (negative Z) // Back face (negative Z)
ogl_tex_bind( m_SkyTexture[BACK] ); m_SkyTexture[BACK]->Bind();
glBegin( GL_QUADS ); glBegin( GL_QUADS );
glTexCoord2f( 1, 1 ); glTexCoord2f( 1, 1 );
glVertex3f( -D, -D, -D ); glVertex3f( -D, -D, -D );
@ -230,7 +184,7 @@ void SkyManager::RenderSky()
glEnd(); glEnd();
// Right face (negative X) // Right face (negative X)
ogl_tex_bind( m_SkyTexture[RIGHT] ); m_SkyTexture[RIGHT]->Bind();
glBegin( GL_QUADS ); glBegin( GL_QUADS );
glTexCoord2f( 0, 1 ); glTexCoord2f( 0, 1 );
glVertex3f( -D, -D, -D ); glVertex3f( -D, -D, -D );
@ -243,7 +197,7 @@ void SkyManager::RenderSky()
glEnd(); glEnd();
// Left face (positive X) // Left face (positive X)
ogl_tex_bind( m_SkyTexture[LEFT] ); m_SkyTexture[LEFT]->Bind();
glBegin( GL_QUADS ); glBegin( GL_QUADS );
glTexCoord2f( 1, 1 ); glTexCoord2f( 1, 1 );
glVertex3f( +D, -D, -D ); glVertex3f( +D, -D, -D );
@ -256,7 +210,7 @@ void SkyManager::RenderSky()
glEnd(); glEnd();
// Top face (positive Y) // Top face (positive Y)
ogl_tex_bind( m_SkyTexture[TOP] ); m_SkyTexture[TOP]->Bind();
glBegin( GL_QUADS ); glBegin( GL_QUADS );
glTexCoord2f( 1, 0 ); glTexCoord2f( 1, 0 );
glVertex3f( +D, +D, -D ); glVertex3f( +D, +D, -D );

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -22,7 +22,7 @@
#ifndef INCLUDED_SKYMANAGER #ifndef INCLUDED_SKYMANAGER
#define INCLUDED_SKYMANAGER #define INCLUDED_SKYMANAGER
#include "ps/Overlay.h" #include "graphics/Texture.h"
/** /**
* Class SkyManager: Maintain sky settings and textures, and render the sky. * Class SkyManager: Maintain sky settings and textures, and render the sky.
@ -35,22 +35,6 @@ public:
public: public:
SkyManager(); SkyManager();
~SkyManager();
/**
* LoadSkyTextures: Load sky textures from within the
* progressive load framework.
*
* @return 0 if loading has completed, a value from 1 to 100 (in percent of completion)
* if more textures need to be loaded and a negative error value on failure.
*/
int LoadSkyTextures();
/**
* UnloadSkyTextures: Free any loaded sky textures and reset the internal state
* so that another call to LoadSkyTextures will begin progressive loading.
*/
void UnloadSkyTextures();
/** /**
* RenderSky: Render the sky. * RenderSky: Render the sky.
@ -76,11 +60,7 @@ public:
std::vector<CStrW> GetSkySets() const; std::vector<CStrW> GetSkySets() const;
private: private:
/** void LoadSkyTextures();
* load texture file from skyset and store in m_SkyTexture
* @param index 0..numTextures-1
**/
LibError LoadSkyTexture(size_t index);
/// Name of current skyset (a directory within art/textures/skies) /// Name of current skyset (a directory within art/textures/skies)
CStrW m_SkySet; CStrW m_SkySet;
@ -97,13 +77,10 @@ private:
}; };
// Sky textures // Sky textures
Handle m_SkyTexture[numTextures]; CTexturePtr m_SkyTexture[numTextures];
// Array of image names (defined in SkyManager.cpp), in the order of the IMG_ id's // Array of image names (defined in SkyManager.cpp), in the order of the IMG_ id's
static const wchar_t* s_imageNames[numTextures]; static const wchar_t* s_imageNames[numTextures];
/// State of progressive loading (in # of loaded textures)
size_t cur_loading_tex;
}; };

View File

@ -450,11 +450,11 @@ void TerrainRenderer::RenderWater()
if(fancy) if(fancy)
{ {
ogl_tex_bind(WaterMgr->m_NormalMap[curTex], 0); WaterMgr->m_NormalMap[curTex]->Bind();
} }
else else
{ {
ogl_tex_bind(WaterMgr->m_WaterTexture[curTex], 0); WaterMgr->m_WaterTexture[curTex]->Bind();
} }
// Shift the texture coordinates by these amounts to make the water "flow" // Shift the texture coordinates by these amounts to make the water "flow"

View File

@ -33,6 +33,7 @@
#include "graphics/LightEnv.h" #include "graphics/LightEnv.h"
#include "graphics/Model.h" #include "graphics/Model.h"
#include "graphics/ModelDef.h" #include "graphics/ModelDef.h"
#include "graphics/TextureManager.h"
#include "ps/Profile.h" #include "ps/Profile.h"
@ -460,7 +461,6 @@ void SortModelRenderer::Submit(CModel* model)
rdata = smdl; rdata = smdl;
model->SetRenderData(rdata); model->SetRenderData(rdata);
model->SetDirty(~0u); model->SetDirty(~0u);
g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE);
} }
m->models.push_back(smdl); m->models.push_back(smdl);
@ -536,7 +536,7 @@ void SortModelRenderer::Render(const RenderModifierPtr& modifier, int flags)
int streamflags = modifier->BeginPass(pass); int streamflags = modifier->BeginPass(pass);
const CMatrix3D* texturematrix = 0; const CMatrix3D* texturematrix = 0;
CModelDefPtr lastmdef; CModelDefPtr lastmdef;
CTexture* lasttex = 0; CTexturePtr lasttex;
if (streamflags & STREAM_TEXGENTOUV1) if (streamflags & STREAM_TEXGENTOUV1)
texturematrix = modifier->GetTexGenMatrix(pass); texturematrix = modifier->GetTexGenMatrix(pass);
@ -554,7 +554,7 @@ void SortModelRenderer::Render(const RenderModifierPtr& modifier, int flags)
debug_assert(smdl->GetKey() == m); debug_assert(smdl->GetKey() == m);
CModelDefPtr mdef = mdl->GetModelDef(); CModelDefPtr mdef = mdl->GetModelDef();
CTexture* tex = mdl->GetTexture(); CTexturePtr tex = mdl->GetTexture();
// Prepare per-CModelDef data if changed // Prepare per-CModelDef data if changed
if (mdef != lastmdef) if (mdef != lastmdef)
@ -661,9 +661,9 @@ bool TransparentRenderModifier::EndPass(int pass)
return true; return true;
} }
void TransparentRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* texture) void TransparentRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
void TransparentRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model)) void TransparentRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model))
@ -798,9 +798,9 @@ const CMatrix3D* LitTransparentRenderModifier::GetTexGenMatrix(int UNUSED(pass))
return &GetShadowMap()->GetTextureMatrix(); return &GetShadowMap()->GetTextureMatrix();
} }
void LitTransparentRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* texture) void LitTransparentRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
void LitTransparentRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model)) void LitTransparentRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model))
@ -851,9 +851,9 @@ bool TransparentShadowRenderModifier::EndPass(int UNUSED(pass))
return true; return true;
} }
void TransparentShadowRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* texture) void TransparentShadowRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
void TransparentShadowRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model)) void TransparentShadowRenderModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model))
@ -901,9 +901,9 @@ bool TransparentDepthShadowModifier::EndPass(int UNUSED(pass))
return true; return true;
} }
void TransparentDepthShadowModifier::PrepareTexture(int UNUSED(pass), CTexture* texture) void TransparentDepthShadowModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& texture)
{ {
g_Renderer.SetTexture(0, texture); texture->Bind(0);
} }
void TransparentDepthShadowModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model)) void TransparentDepthShadowModifier::PrepareModel(int UNUSED(pass), CModel* UNUSED(model))

View File

@ -104,7 +104,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };
@ -127,7 +127,7 @@ public:
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
const CMatrix3D* GetTexGenMatrix(int pass); const CMatrix3D* GetTexGenMatrix(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };
@ -146,7 +146,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };
@ -164,7 +164,7 @@ public:
// Implementation // Implementation
int BeginPass(int pass); int BeginPass(int pass);
bool EndPass(int pass); bool EndPass(int pass);
void PrepareTexture(int pass, CTexture* texture); void PrepareTexture(int pass, CTexturePtr& texture);
void PrepareModel(int pass, CModel* model); void PrepareModel(int pass, CModel* model);
}; };

View File

@ -21,6 +21,8 @@
#include "precompiled.h" #include "precompiled.h"
#include "graphics/TextureManager.h"
#include "lib/bits.h" #include "lib/bits.h"
#include "lib/timer.h" #include "lib/timer.h"
#include "lib/tex/tex.h" #include "lib/tex/tex.h"
@ -28,15 +30,9 @@
#include "maths/MathUtil.h" #include "maths/MathUtil.h"
#include "ps/Filesystem.h"
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "renderer/WaterManager.h" #include "renderer/WaterManager.h"
#include "renderer/Renderer.h" #include "renderer/Renderer.h"
#define LOG_CATEGORY L"graphics"
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
// WaterManager implementation // WaterManager implementation
@ -73,15 +69,6 @@ WaterManager::WaterManager()
m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f); m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f);
m_Murkiness = 0.45f; m_Murkiness = 0.45f;
m_RepeatPeriod = 16.0f; m_RepeatPeriod = 16.0f;
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
m_WaterTexture[i] = 0;
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++)
m_NormalMap[i] = 0;
cur_loading_water_tex = 0;
cur_loading_normal_map = 0;
} }
WaterManager::~WaterManager() WaterManager::~WaterManager()
@ -95,53 +82,41 @@ WaterManager::~WaterManager()
// Progressive load of water textures // Progressive load of water textures
int WaterManager::LoadWaterTextures() int WaterManager::LoadWaterTextures()
{ {
const size_t num_textures = ARRAY_SIZE(m_WaterTexture); // TODO: this doesn't need to be progressive-loading any more
const size_t num_normal_maps = ARRAY_SIZE(m_NormalMap); // (since texture loading is async now)
// TODO: add a member variable and setter for this. (can't make this // TODO: add a member variable and setter for this. (can't make this
// a parameter because this function is called via delay-load code) // a parameter because this function is called via delay-load code)
static const wchar_t* const water_type = L"default"; static const wchar_t* const water_type = L"default";
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 100e-3;
wchar_t pathname[PATH_MAX]; wchar_t pathname[PATH_MAX];
// Load diffuse grayscale images (for non-fancy water) // Load diffuse grayscale images (for non-fancy water)
while (cur_loading_water_tex < num_textures) for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i)
{ {
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/diffuse%02d.dds", water_type, (int)cur_loading_water_tex+1); swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/diffuse%02d.dds", water_type, (int)i+1);
Handle ht = ogl_tex_load(g_VFS, pathname); CTextureProperties textureProps(pathname);
if (ht <= 0) textureProps.SetWrap(GL_REPEAT);
{
LOG(CLogger::Error, LOG_CATEGORY, L"LoadWaterTextures failed on \"%ls\"", pathname); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
return ht; texture->Prefetch();
} m_WaterTexture[i] = texture;
m_WaterTexture[cur_loading_water_tex] = ht;
RETURN_ERR(ogl_tex_upload(ht));
cur_loading_water_tex++;
LDR_CHECK_TIMEOUT(cur_loading_water_tex, num_textures + num_normal_maps);
} }
// Load normalmaps (for fancy water) // Load normalmaps (for fancy water)
while (cur_loading_normal_map < num_normal_maps) for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i)
{ {
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal%02d.dds", water_type, (int)cur_loading_normal_map+1); swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal%02d.dds", water_type, (int)i+1);
Handle ht = ogl_tex_load(g_VFS, pathname); CTextureProperties textureProps(pathname);
if (ht <= 0) textureProps.SetWrap(GL_REPEAT);
{
LOG(CLogger::Error, LOG_CATEGORY, L"LoadWaterTextures failed on \"%ls\"", pathname); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
return ht; texture->Prefetch();
} m_NormalMap[i] = texture;
m_NormalMap[cur_loading_normal_map] = ht;
RETURN_ERR(ogl_tex_upload(ht));
cur_loading_normal_map++;
LDR_CHECK_TIMEOUT(num_textures + cur_loading_normal_map, num_textures + num_normal_maps);
} }
// Set the size to the largest power of 2 that is <= to the window height, so // Set the size to the largest power of 2 that is <= to the window height, so
// the reflection/reflaction images will fit within the window // the reflection/refraction images will fit within the window
// (alternative: use FBO's, which can have arbitrary size - but do we need // (alternative: use FBO's, which can have arbitrary size - but do we need
// the reflection/refraction textures to be that large?) // the reflection/refraction textures to be that large?)
int size = (int)round_up_to_pow2((unsigned)g_Renderer.GetHeight()); int size = (int)round_up_to_pow2((unsigned)g_Renderer.GetHeight());
@ -184,19 +159,14 @@ void WaterManager::UnloadWaterTextures()
{ {
for(size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++) for(size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
{ {
ogl_tex_free(m_WaterTexture[i]); m_WaterTexture[i].reset();
m_WaterTexture[i] = 0;
} }
for(size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++) for(size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++)
{ {
ogl_tex_free(m_NormalMap[i]); m_NormalMap[i].reset();
m_NormalMap[i] = 0;
} }
cur_loading_water_tex = 0; // so they will be reloaded if LoadWaterTextures is called again
cur_loading_normal_map = 0;
} }

View File

@ -22,6 +22,7 @@
#ifndef INCLUDED_WATERMANAGER #ifndef INCLUDED_WATERMANAGER
#define INCLUDED_WATERMANAGER #define INCLUDED_WATERMANAGER
#include "graphics/Texture.h"
#include "ps/Overlay.h" #include "ps/Overlay.h"
#include "maths/Matrix3D.h" #include "maths/Matrix3D.h"
#include "lib/ogl.h" #include "lib/ogl.h"
@ -35,8 +36,8 @@
class WaterManager class WaterManager
{ {
public: public:
Handle m_WaterTexture[60]; CTexturePtr m_WaterTexture[60];
Handle m_NormalMap[60]; CTexturePtr m_NormalMap[60];
int m_WaterCurrentTex; int m_WaterCurrentTex;
CColor m_WaterColor; CColor m_WaterColor;
@ -100,11 +101,6 @@ public:
* and it hasn't been configured off) * and it hasn't been configured off)
*/ */
bool WillRenderFancyWater(); bool WillRenderFancyWater();
private:
/// State of progressive loading (in # of loaded textures)
size_t cur_loading_water_tex;
size_t cur_loading_normal_map;
}; };

View File

@ -60,7 +60,6 @@
#include "simulation2/Simulation2.h" #include "simulation2/Simulation2.h"
#define LOG_CATEGORY L"script" #define LOG_CATEGORY L"script"
extern bool g_TerrainModified;
// rationale: the function table is now at the end of the source file to // rationale: the function table is now at the end of the source file to
// avoid the need for forward declarations for every function. // avoid the need for forward declarations for every function.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games. /* Copyright (C) 2010 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -228,6 +228,14 @@ namespace
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// Primitive types: // Primitive types:
template<> struct ToJSVal<bool>
{
static jsval Convert(JSContext* cx, const bool& val)
{
return val ? JSVAL_TRUE : JSVAL_FALSE;
}
};
template<> struct ToJSVal<float> template<> struct ToJSVal<float>
{ {
static jsval Convert(JSContext* cx, const float& val) static jsval Convert(JSContext* cx, const float& val)
@ -377,7 +385,9 @@ namespace
JS_AddRoot(cx, &obj); JS_AddRoot(cx, &obj);
JS_DefineProperty(cx, obj, "name", ToJSVal<std::wstring>::Convert(cx, *val.name), NULL, NULL, JSPROP_ENUMERATE); JS_DefineProperty(cx, obj, "name", ToJSVal<std::wstring>::Convert(cx, *val.name), NULL, NULL, JSPROP_ENUMERATE);
JS_DefineProperty(cx, obj, "loaded", ToJSVal<bool>::Convert(cx, val.loaded), NULL, NULL, JSPROP_ENUMERATE);
unsigned char* buf = (unsigned char*)(malloc(val.imagedata.GetSize())); unsigned char* buf = (unsigned char*)(malloc(val.imagedata.GetSize()));
memcpy(buf, val.imagedata.GetBuffer(), val.imagedata.GetSize()); memcpy(buf, val.imagedata.GetBuffer(), val.imagedata.GetSize());
jsval bmp = wxjs::gui::Bitmap::CreateObject(cx, new wxBitmap (wxImage(val.imagewidth, val.imageheight, buf))); jsval bmp = wxjs::gui::Bitmap::CreateObject(cx, new wxBitmap (wxImage(val.imagewidth, val.imageheight, buf)));

View File

@ -28,6 +28,7 @@
#include "InputProcessor.h" #include "InputProcessor.h"
#include "graphics/TextureManager.h"
#include "gui/GUIManager.h" #include "gui/GUIManager.h"
#include "lib/app_hooks.h" #include "lib/app_hooks.h"
#include "lib/external_libraries/sdl.h" #include "lib/external_libraries/sdl.h"
@ -37,6 +38,7 @@
#include "ps/Filesystem.h" #include "ps/Filesystem.h"
#include "ps/Profile.h" #include "ps/Profile.h"
#include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Paths.h"
#include "renderer/Renderer.h"
#include "scripting/ScriptingHost.h" #include "scripting/ScriptingHost.h"
using namespace AtlasMessage; using namespace AtlasMessage;
@ -95,6 +97,23 @@ static ErrorReaction AtlasDisplayError(const wchar_t* text, size_t flags)
return ER_CONTINUE; return ER_CONTINUE;
} }
static void RendererIncrementalLoad()
{
// TODO: shouldn't duplicate this code from main.cpp
if (!CRenderer::IsInitialised())
return;
const double maxTime = 0.1f;
double startTime = timer_Time();
bool more;
do {
more = g_Renderer.GetTextureManager().MakeProgress();
}
while (more && timer_Time() - startTime < maxTime);
}
bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
{ {
// Load required symbols from the DLL // Load required symbols from the DLL
@ -232,6 +251,8 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
ReloadChangedFiles(); ReloadChangedFiles();
RendererIncrementalLoad();
// Pump SDL events (e.g. hotkeys) // Pump SDL events (e.g. hotkeys)
SDL_Event_ ev; SDL_Event_ ev;
while (SDL_PollEvent(&ev.ev)) while (SDL_PollEvent(&ev.ev))

View File

@ -74,7 +74,7 @@ QUERYHANDLER(GetTerrainGroupPreviews)
// disk, which is slow.) // disk, which is slow.)
GLint w, h; GLint w, h;
ssize_t level = 1; // level 0 is the original size ssize_t level = 1; // level 0 is the original size
ogl_tex_bind((*it)->GetHandle()); (*it)->GetTexture()->Bind();
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &w); glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &w);
glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &h); glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &h);
@ -109,6 +109,7 @@ QUERYHANDLER(GetTerrainGroupPreviews)
delete[] texdata; delete[] texdata;
} }
previews.back().loaded = (*it)->GetTexture()->IsLoaded();
previews.back().imagewidth = msg->imagewidth; previews.back().imagewidth = msg->imagewidth;
previews.back().imageheight = msg->imageheight; previews.back().imageheight = msg->imageheight;
previews.back().imagedata = buf; previews.back().imagedata = buf;

View File

@ -196,6 +196,7 @@ QUERY(GetTerrainGroups,
struct sTerrainGroupPreview struct sTerrainGroupPreview
{ {
Shareable<std::wstring> name; Shareable<std::wstring> name;
Shareable<bool> loaded;
Shareable<int> imagewidth; Shareable<int> imagewidth;
Shareable<int> imageheight; Shareable<int> imageheight;
Shareable<std::vector<unsigned char> > imagedata; // RGB*width*height Shareable<std::vector<unsigned char> > imagedata; // RGB*width*height