diff --git a/binaries/data/tools/atlas/scripts/section/terrain.js b/binaries/data/tools/atlas/scripts/section/terrain.js index 9d3e6b9d21..2be89f9949 100644 --- a/binaries/data/tools/atlas/scripts/section/terrain.js +++ b/binaries/data/tools/atlas/scripts/section/terrain.js @@ -69,14 +69,64 @@ function TerrainPreviewPage(panel, name) { this.panel = panel; this.name = name; + + // Size of texture preview images + this.w = 120 + this.h = 40; + + this.previewReloadTimer = null; } 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() { if (this.loaded) return; - - // Size of texture preview images - var w = 120, h = 40; this.panel.sizer = new wxBoxSizer(wxOrientation.VERTICAL); 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 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) itemSizer.cols = numCols; }; - // 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, w, h).previews; - 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); - } + this.scrolled = scrolled; + this.itemSizer = itemSizer; + this.reloadPreviews(); // TODO: fix keyboard navigation of the terrain previews - this.panel.layout(); - this.loaded = true; } }; diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index 5f4654367f..02a2a87144 100644 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -48,7 +48,6 @@ #include "ps/Pyrogenesis.h" #include "ps/World.h" #include "renderer/Renderer.h" -#include "renderer/SkyManager.h" #include "renderer/WaterManager.h" #include "scripting/ScriptableObject.h" #include "simulation/LOSManager.h" @@ -387,7 +386,6 @@ void CGameView::RegisterInit() RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5); RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80); - RegMemFun(g_Renderer.GetSingletonPtr()->GetSkyManager(), &SkyManager::LoadSkyTextures, L"LoadSkyTextures", 15); } diff --git a/source/graphics/MiniPatch.h b/source/graphics/MiniPatch.h index b8b6ed885b..4de53363d5 100644 --- a/source/graphics/MiniPatch.h +++ b/source/graphics/MiniPatch.h @@ -40,7 +40,6 @@ public: int Priority; CTerrainTextureEntry* GetTextureEntry() { return Tex; } - Handle GetHandle() { return Tex ? Tex->GetHandle() : 0; } int GetPriority() { return Priority; } }; diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 04a5c58559..a586751676 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -85,9 +85,7 @@ void CModel::ReleaseData() m_Props.clear(); m_pModelDef = CModelDefPtr(); - Handle h = m_Texture.GetHandle(); - ogl_tex_free(h); - m_Texture.SetHandle(0); + m_Texture.reset(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -452,8 +450,6 @@ CModel* CModel::Clone() const clone->m_ObjectBounds = m_ObjectBounds; clone->InitModel(m_pModelDef); clone->SetTexture(m_Texture); - if (m_Texture.GetHandle()) - h_add_ref(m_Texture.GetHandle()); clone->SetMaterial(m_Material); clone->SetAnimation(m_Anim); clone->SetFlags(m_Flags); diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 34b1fb71d0..f4907a00e7 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -75,7 +75,7 @@ public: CModelDefPtr GetModelDef() { return m_pModelDef; } // 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 void SetMaterial(const CMaterial &material); // set the model's player ID, recursively through props @@ -87,10 +87,10 @@ public: // set the models mod color void SetShadingColor(const CColor& colour); // get the model's texture - CTexture* GetTexture() { return &m_Texture; } - // get the models material - CMaterial &GetMaterial() { return m_Material; } - // get the model's texture + CTexturePtr& GetTexture() { return m_Texture; } + // get the model's material + CMaterial& GetMaterial() { return m_Material; } + // get the model's shading color CColor GetShadingColor() { return m_ShadingColor; } // set the given animation as the current animation on this model @@ -214,7 +214,7 @@ private: // object flags int m_Flags; // texture used by model - CTexture m_Texture; + CTexturePtr m_Texture; // model's material CMaterial m_Material; // pointer to the model's raw 3d data diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index 50ca41eac1..a190352b84 100644 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -26,6 +26,8 @@ #include "MaterialManager.h" #include "MeshManager.h" #include "SkeletonAnim.h" +#include "TextureManager.h" +#include "renderer/Renderer.h" #include "lib/rand.h" @@ -91,11 +93,16 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections // delete old model, create new delete m_Model; m_Model = new CModel(objectManager.GetSkeletonAnimManager()); - m_Model->SetTexture(CTexture(m_TextureName)); m_Model->SetMaterial(g_MaterialManager.LoadMaterial(m_Base->m_Material)); m_Model->InitModel(modeldef); 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 m_Model->CalcObjectBounds(); diff --git a/source/graphics/ParticleEmitter.cpp b/source/graphics/ParticleEmitter.cpp index 77df4f171e..2e5b60a18d 100644 --- a/source/graphics/ParticleEmitter.cpp +++ b/source/graphics/ParticleEmitter.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -20,12 +20,14 @@ */ #include "precompiled.h" + #include "ParticleEmitter.h" + #include "ParticleEngine.h" +#include "graphics/TextureManager.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include "ps/XML/Xeromyces.h" -#define LOG_CATEGORY L"particleSystem" //forward declaration 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_renderParticles = true; isFinished = false; - m_texture = NULL; // init the used/open list m_usedList = NULL; @@ -107,7 +108,7 @@ bool CEmitter::LoadXml(const VfsPath& pathname) 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 ); } @@ -151,11 +152,8 @@ bool CEmitter::LoadXml(const VfsPath& pathname) } else if( settingName == el_Texture ) { - stringValue = settingElement.GetText(); - m_texture = new CTexture(CStrW(stringValue)); - u32 flags = 0; - if(!(CRenderer::GetSingletonPtr()->LoadTexture(m_texture, flags))) - return false; + CTextureProperties textureProps(CStrW(settingElement.GetText())); + m_texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); } else if( settingName == el_Size ) { @@ -408,9 +406,7 @@ bool CEmitter::Render() break; } - // Bind the texture. Use the texture assigned to this emitter. - int unit = 0; - g_Renderer.SetTexture(unit, m_texture); + m_texture->Bind(); glBegin(GL_QUADS); { diff --git a/source/graphics/ParticleEmitter.h b/source/graphics/ParticleEmitter.h index e66ef7d587..46875be60d 100644 --- a/source/graphics/ParticleEmitter.h +++ b/source/graphics/ParticleEmitter.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -22,12 +22,11 @@ #ifndef INCLUDED_PARTICLEEMITTER #define INCLUDED_PARTICLEEMITTER +#include "graphics/Texture.h" #include "lib/file/vfs/vfs_path.h" #include "maths/Vector3D.h" #include "ps/CStr.h" -class CTexture; - class CEmitter { static const int HALF_RAND = (RAND_MAX / 2); @@ -89,7 +88,7 @@ protected: tParticle* m_usedList; // linked list of used particles //Particle appearence - CTexture* m_texture; // Texture + CTexturePtr m_texture; // Texture float m_size; // size of the particles (if point sprites is not enabled) tColor m_startColor, m_startColorVar; // Current color of particle tColor m_endColor, m_endColorVar; // End color of particle @@ -188,7 +187,7 @@ public: { m_pos = newPos; } - void SetTexture(CTexture* id) { m_texture = id; } + void SetTexture(CTexturePtr id) { m_texture = id; } void SetIsFinished(bool finished) { isFinished = finished; } void SetEmitterLife(int life) { m_emitterLife = life; } void SetLife(int newlife) { m_life = newlife; } diff --git a/source/graphics/ParticleEngine.cpp b/source/graphics/ParticleEngine.cpp index 2635a557de..ced8d59b23 100644 --- a/source/graphics/ParticleEngine.cpp +++ b/source/graphics/ParticleEngine.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -20,8 +20,11 @@ */ #include "precompiled.h" + #include "ParticleEngine.h" +#include "graphics/TextureManager.h" + CParticleEngine *CParticleEngine::m_pInstance = 0; CParticleEngine::CParticleEngine(void) { @@ -76,31 +79,17 @@ void CParticleEngine::DeleteInstance() bool CParticleEngine::InitParticleSystem() { // 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; - 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); + idTexture[DEFAULTTEXT] = texture; return true; } bool CParticleEngine::AddEmitter(CEmitter *emitter, int type, int ID) { - emitter->SetTexture(&idTexture[type]); + emitter->SetTexture(idTexture[type]); if(m_pHead == NULL) { tEmitterNode *temp = new tEmitterNode; diff --git a/source/graphics/ParticleEngine.h b/source/graphics/ParticleEngine.h index 423111447d..28fff2a453 100644 --- a/source/graphics/ParticleEngine.h +++ b/source/graphics/ParticleEngine.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -93,7 +93,7 @@ private: CParticleEngine(void); static CParticleEngine* m_pInstance; // The singleton instance - CTexture idTexture[MAX_TEXTURES]; + CTexturePtr idTexture[MAX_TEXTURES]; int totalParticles; // Total Amount of particles of all emitters. struct tEmitterNode diff --git a/source/graphics/Sprite.cpp b/source/graphics/Sprite.cpp index 894e783e71..828d586d99 100644 --- a/source/graphics/Sprite.cpp +++ b/source/graphics/Sprite.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -15,25 +15,17 @@ * along with 0 A.D. If not, see . */ -/* - * 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 "Sprite.h" + +#include "graphics/TextureManager.h" #include "renderer/Renderer.h" #include "lib/ogl.h" #include "lib/res/graphics/ogl_tex.h" -CSprite::CSprite() : -m_texture(NULL) +CSprite::CSprite() { - // default scale 1:1 m_scale.X = m_scale.Y = m_scale.Z = 1.0f; @@ -59,8 +51,8 @@ void CSprite::Render() BeginBillboard(); glDisable(GL_CULL_FACE); - if ( m_texture && m_texture->GetHandle() != 0 ) - (void)ogl_tex_bind(m_texture->GetHandle()); + if (m_texture) + m_texture->Bind(); glColor4fv(m_colour); @@ -87,12 +79,9 @@ void CSprite::Render() EndBillboard(); } -int CSprite::SetTexture(CTexture *texture) +void CSprite::SetTexture(const CTexturePtr& texture) { - if (texture == NULL) return -1; - m_texture = texture; - return 0; } void CSprite::SetSize(float width, float height) diff --git a/source/graphics/Sprite.h b/source/graphics/Sprite.h index 9f41b64e95..fb80a5d687 100644 --- a/source/graphics/Sprite.h +++ b/source/graphics/Sprite.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -21,7 +21,7 @@ */ // 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 #define INCLUDED_SPRITE @@ -45,7 +45,7 @@ public: void Render(); - int SetTexture(CTexture *texture); + void SetTexture(const CTexturePtr& texture); void SetSize(float width, float height); @@ -69,7 +69,7 @@ private: void BeginBillboard(); void EndBillboard(); - CTexture *m_texture; + CTexturePtr m_texture; CVector3D m_coords[4]; diff --git a/source/graphics/TerrainTextureEntry.cpp b/source/graphics/TerrainTextureEntry.cpp index 4a684baacc..02c7967305 100644 --- a/source/graphics/TerrainTextureEntry.cpp +++ b/source/graphics/TerrainTextureEntry.cpp @@ -30,11 +30,19 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path): m_pProperties(props), - m_Handle(-1), m_BaseColor(0), - m_BaseColorValid(false), - m_TexturePath(path) + m_BaseColorValid(false) { + 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) m_Groups = m_pProperties->GetGroups(); @@ -42,64 +50,31 @@ CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr props, const Vf for (;it!=m_Groups.end();++it) (*it)->AddTerrain(this); - m_Tag = fs::basename(m_TexturePath); + m_Tag = fs::basename(path); } CTerrainTextureEntry::~CTerrainTextureEntry() { - if (m_Handle > 0) - (void)ogl_tex_free(m_Handle); - for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it) (*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 // in m_BaseColor member void CTerrainTextureEntry::BuildBaseColor() { + // Use the explicit properties value if possible if (m_pProperties && m_pProperties->HasBaseColor()) { m_BaseColor=m_pProperties->GetBaseColor(); - m_BaseColorValid=true; + m_BaseColorValid = true; return; } - LibError ret = ogl_tex_bind(GetHandle()); - debug_assert(ret == INFO::OK); - - // get root colour for coloring minimap by querying root level of the texture - // (this should decompress any compressed textures for us), - // 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; + // Use the texture colour if available + if (m_Texture->TryLoad()) + { + m_BaseColor = m_Texture->GetBaseColour(); + m_BaseColorValid = true; + } } diff --git a/source/graphics/TerrainTextureEntry.h b/source/graphics/TerrainTextureEntry.h index 0b148f257d..c21cae77a6 100644 --- a/source/graphics/TerrainTextureEntry.h +++ b/source/graphics/TerrainTextureEntry.h @@ -15,17 +15,18 @@ * along with 0 A.D. If not, see . */ -#ifndef INCLUDED_TEXTUREENTRY -#define INCLUDED_TEXTUREENTRY +#ifndef INCLUDED_TERRAINTEXTUREENTRY +#define INCLUDED_TERRAINTEXTUREENTRY #include +#include "TerrainTextureManager.h" +#include "TextureManager.h" + #include "lib/res/handle.h" #include "lib/file/vfs/vfs_path.h" #include "ps/CStr.h" -#include "TerrainTextureManager.h" - class XMBElement; class CXeromyces; @@ -44,9 +45,7 @@ private: // The property sheet used by this texture CTerrainPropertiesPtr m_pProperties; - VfsPath m_TexturePath; - - Handle m_Handle; // handle to GL texture data + CTexturePtr m_Texture; // BGRA color of topmost mipmap level, for coloring minimap, or a color // specified by the terrain properties @@ -57,8 +56,6 @@ private: // All terrain type groups we're a member of GroupVector m_Groups; - // load texture from file - void LoadTexture(); // calculate the root color of the texture, used for coloring minimap void BuildBaseColor(); @@ -74,13 +71,9 @@ public: CTerrainPropertiesPtr GetProperties() const { return m_pProperties; } - VfsPath GetTexturePath() const - { return m_TexturePath; } - // Get texture handle, load texture if not loaded. - Handle GetHandle() { - if (m_Handle==-1) LoadTexture(); - return m_Handle; + const CTexturePtr& GetTexture() { + return m_Texture; } // Get mipmap color in BGRA format u32 GetBaseColor() { @@ -93,9 +86,6 @@ public: { return m_Groups[0]->GetIndex(); } const GroupVector &GetGroups() const { return m_Groups; } - - // returns whether this texture-entry has loaded any data yet - bool IsLoaded() { return (m_Handle!=-1); } }; #endif diff --git a/source/graphics/TerrainTextureManager.h b/source/graphics/TerrainTextureManager.h index e7602ec5cf..bb3a5fb22b 100644 --- a/source/graphics/TerrainTextureManager.h +++ b/source/graphics/TerrainTextureManager.h @@ -15,8 +15,8 @@ * along with 0 A.D. If not, see . */ -#ifndef INCLUDED_TEXTUREMANAGER -#define INCLUDED_TEXTUREMANAGER +#ifndef INCLUDED_TERRAINTEXTUREMANAGER +#define INCLUDED_TERRAINTEXTUREMANAGER #include #include diff --git a/source/graphics/Texture.h b/source/graphics/Texture.h index b43b4d71be..213205dfaf 100644 --- a/source/graphics/Texture.h +++ b/source/graphics/Texture.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -15,33 +15,11 @@ * along with 0 A.D. If not, see . */ -//----------------------------------------------------------- -// Name: Texture.h -// Description: Basic texture class -// -//----------------------------------------------------------- - #ifndef INCLUDED_TEXTURE #define INCLUDED_TEXTURE -#include "lib/res/handle.h" -#include "lib/file/vfs/vfs_path.h" - -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; -}; +// Forward-declare for CTextureManager handles +class CTexture; +typedef shared_ptr CTexturePtr; #endif diff --git a/source/graphics/TextureConverter.cpp b/source/graphics/TextureConverter.cpp new file mode 100644 index 0000000000..572a051512 --- /dev/null +++ b/source/graphics/TextureConverter.cpp @@ -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 . + */ + +#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 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 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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& 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 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 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 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 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(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 request = textureConverter->m_RequestQueue.front(); + textureConverter->m_RequestQueue.pop_front(); + pthread_mutex_unlock(&textureConverter->m_WorkerMutex); + + // Set up the result object + shared_ptr 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; +} diff --git a/source/graphics/TextureConverter.h b/source/graphics/TextureConverter.h new file mode 100644 index 0000000000..e1482f14cf --- /dev/null +++ b/source/graphics/TextureConverter.h @@ -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 . + */ + +#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 + * + * + * + * + * @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 \ line from settings XML file. + */ + struct Match + { + std::wstring pattern; + Settings settings; + }; + + /** + * Representation of settings XML file. + */ + struct SettingsFile + { + std::vector 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& 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 > m_RequestQueue; // protected by m_WorkerMutex + std::deque > m_ResultQueue; // protected by m_WorkerMutex + bool m_Shutdown; // protected by m_WorkerMutex +}; + +#endif // INCLUDED_TEXTURECONVERTER diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp new file mode 100644 index 0000000000..f9a2ffb149 --- /dev/null +++ b/source/graphics/TextureManager.cpp @@ -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 . + */ + +#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 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 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 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 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())); + return NULL; + } + } + + static LibError ReloadChangedFileCB(void* param, const VfsPath& path) + { + return static_cast(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 > >::iterator files = m_HotloadFiles.find(path); + if (files != m_HotloadFiles.end()) + { + // Flag all textures using this file as needing reloading + for (std::set >::iterator it = files->second.begin(); it != files->second.end(); ++it) + { + if (shared_ptr 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 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 > > m_HotloadFiles; + + // Cache for the conversion settings files + typedef std::map > 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 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 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(); +} diff --git a/source/graphics/TextureManager.h b/source/graphics/TextureManager.h new file mode 100644 index 0000000000..c314bb8775 --- /dev/null +++ b/source/graphics/TextureManager.h @@ -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 . + */ + +#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 + +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 m_Self; +}; + +#endif // INCLUDED_TEXTUREMANAGER diff --git a/source/graphics/tests/test_TextureManager.h b/source/graphics/tests/test_TextureManager.h new file mode 100644 index 0000000000..1b97e0ae65 --- /dev/null +++ b/source/graphics/tests/test_TextureManager.h @@ -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 . + */ + +#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()); + } +}; diff --git a/source/gui/GUIRenderer.cpp b/source/gui/GUIRenderer.cpp index 34925a141a..fd03a18642 100644 --- a/source/gui/GUIRenderer.cpp +++ b/source/gui/GUIRenderer.cpp @@ -19,13 +19,15 @@ #include "GUIRenderer.h" +#include "graphics/TextureManager.h" #include "lib/ogl.h" #include "lib/utf8.h" #include "lib/res/h_mgr.h" #include "lib/tex/tex.h" - -#include "ps/Filesystem.h" #include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "renderer/Renderer.h" + #define LOG_CATEGORY L"gui" using namespace GUIRenderer; @@ -36,7 +38,6 @@ void DrawCalls::clear() for (iterator it = begin(); it != end(); ++it) { delete it->m_Effects; - ogl_tex_free(it->m_TexHandle); } std::vector::clear(); } @@ -114,7 +115,7 @@ public: ~Effect_AddColor() {} - void Set(Handle tex) + void Set(const CTexturePtr& tex) { glEnable(GL_TEXTURE_2D); @@ -149,11 +150,12 @@ public: glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); - ogl_tex_bind(tex); + tex->Bind(); } void Unset() { + glColor4f(1.f, 1.f, 1.f, 1.f); } private: @@ -192,7 +194,7 @@ public: } } ~Effect_MultiplyColor() {} - void Set(Handle tex) + void Set(const CTexturePtr& tex) { glEnable(GL_TEXTURE_2D); @@ -226,12 +228,14 @@ public: glTexEnviv(GL_TEXTURE_ENV, GL_RGB_SCALE, TexScale4); } - ogl_tex_bind(tex); + tex->Bind(); } void Unset() { if (m_Scale != 1) glTexEnviv(GL_TEXTURE_ENV, GL_RGB_SCALE, TexScale1); + + glColor4f(1.f, 1.f, 1.f, 1.f); } private: CColor m_Color; @@ -248,7 +252,7 @@ class Effect_Greyscale : public IGLState { public: ~Effect_Greyscale() {} - void Set(Handle tex) + void Set(const CTexturePtr& tex) { /* @@ -288,7 +292,7 @@ public: // Texture unit 0: - ogl_tex_bind(tex, 0); + tex->Bind(0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); @@ -308,7 +312,7 @@ public: // Texture unit 1: - ogl_tex_bind(tex, 1); + tex->Bind(1); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); @@ -339,6 +343,8 @@ public: { glDisable(GL_TEXTURE_2D); 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::const_iterator 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); @@ -418,94 +424,20 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName, if (! cit->m_TextureName.empty()) { - Handle h = ogl_tex_load(g_VFS, cit->m_TextureName); - if (h <= 0) - { - LOG(CLogger::Error, LOG_CATEGORY, L"Error reading texture '%ls': %ld", cit->m_TextureName.string().c_str(), (long)h); - return; - } + CTextureProperties textureProps(cit->m_TextureName); + CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); + texture->Prefetch(); + Call.m_HasTexture = true; + 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); - if (err < 0) - { - 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; + Call.m_ObjectSize = ObjectSize; + Call.m_CellID = CellID; } else { - Call.m_TexHandle = 0; + Call.m_HasTexture = false; // 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); } @@ -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) { // 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 for (DrawCalls::const_iterator cit = Calls.begin(); cit != Calls.end(); ++cit) { - if (cit->m_EnableBlending) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - } - - if (cit->m_TexHandle) + if (cit->m_HasTexture) { // TODO: Handle the GL state in a nicer way if (cit->m_Effects) - cit->m_Effects->Set(cit->m_TexHandle); + cit->m_Effects->Set(cit->m_Texture); else { glEnable(GL_TEXTURE_2D); 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); - glTexCoord2f(cit->m_TexCoords.right,cit->m_TexCoords.bottom); - glVertex3f(cit->m_Vertices.right, cit->m_Vertices.bottom, cit->m_DeltaZ); + glTexCoord2f(TexCoords.right, TexCoords.bottom); + glVertex3f(cit->m_Vertices.right, cit->m_Vertices.bottom, cit->m_DeltaZ); - glTexCoord2f(cit->m_TexCoords.left, cit->m_TexCoords.bottom); - glVertex3f(cit->m_Vertices.left, cit->m_Vertices.bottom, cit->m_DeltaZ); + glTexCoord2f(TexCoords.left, TexCoords.bottom); + glVertex3f(cit->m_Vertices.left, cit->m_Vertices.bottom, cit->m_DeltaZ); - glTexCoord2f(cit->m_TexCoords.left, cit->m_TexCoords.top); - glVertex3f(cit->m_Vertices.left, cit->m_Vertices.top, cit->m_DeltaZ); + glTexCoord2f(TexCoords.left, TexCoords.top); + glVertex3f(cit->m_Vertices.left, cit->m_Vertices.top, cit->m_DeltaZ); - glTexCoord2f(cit->m_TexCoords.right,cit->m_TexCoords.top); - glVertex3f(cit->m_Vertices.right, cit->m_Vertices.top, cit->m_DeltaZ); + glTexCoord2f(TexCoords.right, TexCoords.top); + glVertex3f(cit->m_Vertices.right, cit->m_Vertices.top, cit->m_DeltaZ); glEnd(); @@ -601,6 +604,12 @@ void GUIRenderer::Draw(DrawCalls &Calls) { 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()); glBegin(GL_QUADS); diff --git a/source/gui/GUIRenderer.h b/source/gui/GUIRenderer.h index 9ea4a4f434..b871a4f5a0 100644 --- a/source/gui/GUIRenderer.h +++ b/source/gui/GUIRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -18,12 +18,14 @@ #ifndef GUIRenderer_h #define GUIRenderer_h +#include "graphics/Texture.h" #include "lib/res/handle.h" #include "ps/Overlay.h" #include "ps/CStr.h" #include struct SGUIImageEffects; +struct SGUIImage; namespace GUIRenderer { @@ -31,22 +33,28 @@ namespace GUIRenderer { public: virtual ~IGLState() {}; - virtual void Set(Handle tex)=0; + virtual void Set(const CTexturePtr& tex)=0; virtual void Unset()=0; }; 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; IGLState* m_Effects; CRect m_Vertices; - CRect m_TexCoords; float m_DeltaZ; CColor m_BorderColor; // == CColor() for no border diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp index 7d0167f025..a866a67bec 100644 --- a/source/gui/MiniMap.cpp +++ b/source/gui/MiniMap.cpp @@ -39,7 +39,6 @@ #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" -bool g_TerrainModified = false; bool g_GameRestarted = false; static unsigned int ScaleColor(unsigned int color, float x) @@ -52,7 +51,7 @@ static unsigned int ScaleColor(unsigned int color, float x) CMiniMap::CMiniMap() : 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_CStr, "tooltip"); @@ -232,21 +231,21 @@ void CMiniMap::Draw() if(!m_TerrainTexture || g_GameRestarted) 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 // (note: since units only move a few pixels per second on the minimap, // 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; const double cur_time = timer_Time(); if(cur_time - last_time > 100e-3) // 10 updates/sec { last_time = cur_time; + if(m_TerrainDirty) + RebuildTerrainTexture(); + // CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); // if(losMgr->m_LOSSetting != LOS_SETTING_ALL_VISIBLE) // RebuildLOSTexture(); @@ -442,6 +441,8 @@ void CMiniMap::RebuildTerrainTexture() u32 h = m_MapSize - 1; float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; + m_TerrainDirty = false; + for(u32 j = 0; j < h; j++) { u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; @@ -469,7 +470,14 @@ void CMiniMap::RebuildTerrainTexture() { CTerrainTextureEntry *tex = mp->GetTextureEntry(); 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(); + } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); diff --git a/source/gui/MiniMap.h b/source/gui/MiniMap.h index 42907a06f2..7960b64619 100644 --- a/source/gui/MiniMap.h +++ b/source/gui/MiniMap.h @@ -23,8 +23,6 @@ class CCamera; class CTerrain; -extern bool g_TerrainModified; - class CMiniMap : public IGUIObject { GUI_OBJECT(CMiniMap) @@ -68,6 +66,9 @@ protected: u32* m_TerrainData; u8* m_LOSData; + // whether we need to regenerate the terrain texture + bool m_TerrainDirty; + ssize_t m_Width, m_Height; // map size diff --git a/source/main.cpp b/source/main.cpp index 0c79a4b537..3780e01caa 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -63,13 +63,14 @@ that of Atlas depending on commandline parameters. #include "network/NetSession.h" #include "graphics/Camera.h" #include "graphics/GameView.h" +#include "graphics/TextureManager.h" +#include "gui/GUIManager.h" +#include "renderer/Renderer.h" #include "scripting/ScriptingHost.h" #include "simulation2/Simulation2.h" -#include "gui/GUIManager.h" #define LOG_CATEGORY L"main" -extern bool g_TerrainModified; extern bool g_GameRestarted; 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 void Frame() @@ -266,6 +280,10 @@ static void Frame() ProgressiveLoad(); PROFILE_END("progressive load"); + PROFILE_START("renderer incremental load"); + RendererIncrementalLoad(); + PROFILE_END("renderer incremental load"); + PROFILE_START("input"); MICROLOG(L"input"); PumpEvents(); @@ -360,7 +378,6 @@ static void Frame() g_Profiler.Frame(); - g_TerrainModified = false; g_GameRestarted = false; } diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index 6af805558b..dce9b14b69 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -32,6 +32,7 @@ #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" +#include "graphics/TextureManager.h" #include "renderer/ModelRenderer.h" #include "renderer/ModelVertexRenderer.h" @@ -324,7 +325,6 @@ void BatchModelRenderer::Submit(CModel* model) rdata = bmrdata; model->SetRenderData(bmrdata); model->SetDirty(~0u); - g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE); } // 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 - Handle htex = model->GetTexture()->GetHandle(); + CTexturePtr tex = model->GetTexture(); size_t idx; for(idx = 0; idx < mdeftracker->m_Slots; ++idx) { BMRModelData* in = mdeftracker->m_ModelSlots[idx]; - if (in->GetModel()->GetTexture()->GetHandle() == htex) + if (in->GetModel()->GetTexture() == tex) break; } diff --git a/source/renderer/PatchRData.cpp b/source/renderer/PatchRData.cpp index 1da36d03e9..4d1664bf5a 100644 --- a/source/renderer/PatchRData.cpp +++ b/source/renderer/PatchRData.cpp @@ -21,7 +21,6 @@ #include #include #include "ps/Pyrogenesis.h" -#include "lib/res/graphics/ogl_tex.h" #include "graphics/LightEnv.h" #include "Renderer.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) return 0; - return mp->GetHandle(); + return mp->GetTextureEntry(); } 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 { - Handle m_Texture; + CTerrainTextureEntry* m_Texture; u16 m_Indices[4]; }; @@ -103,7 +102,7 @@ void CPatchRData::BuildBlends() // temporary list of splats std::vector splats; // set of textures used for splats - std::set splatTextures; + std::set splatTextures; // for each tile in patch .. for (ssize_t j=0;jGetTextureEntry() != mp->GetTextureEntry()) { if (nmp->GetPriority() > mp->GetPriority() || (nmp->GetPriority() == mp->GetPriority() && nmp->GetTextureEntry() > mp->GetTextureEntry())) { STex tex; - tex.m_Handle=nmp->GetHandle(); + tex.m_Texture=nmp->GetTextureEntry(); tex.m_Priority=nmp->GetPriority(); if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) { neighbourTextures.push_back(tex); @@ -145,9 +144,9 @@ void CPatchRData::BuildBlends() ssize_t oz=gz+BlendOffsets[m][0]; // get texture on adjacent tile - Handle atex=GetTerrainTileTexture(terrain,ox,oz); + CTerrainTextureEntry* atex=GetTerrainTileTexture(terrain,ox,oz); // 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 @@ -239,7 +238,7 @@ void CPatchRData::BuildBlends() // build a splat for this quad 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[1]=(u16)(vindex+1); splat.m_Indices[2]=(u16)(vindex+2); @@ -270,9 +269,9 @@ void CPatchRData::BuildBlends() debug_assert(m_VBBlends->m_Index < 65536); unsigned short base = (unsigned short)m_VBBlends->m_Index; - std::set::iterator iter=splatTextures.begin(); + std::set::iterator iter=splatTextures.begin(); for (;iter!=splatTextures.end();++iter) { - Handle tex=*iter; + CTerrainTextureEntry* tex=*iter; SSplat& splat=m_BlendSplats[splatCount]; splat.m_IndexStart=m_BlendIndices.size(); @@ -306,14 +305,14 @@ void CPatchRData::BuildIndices() m_Splats.clear(); // build grid of textures on this patch and boundaries of adjacent patches - std::vector textures; - Handle texgrid[PATCH_SIZE][PATCH_SIZE]; + std::vector textures; + CTerrainTextureEntry* texgrid[PATCH_SIZE][PATCH_SIZE]; for (ssize_t j=0;jm_MiniPatches[j][i].GetHandle(); - texgrid[j][i]=h; - if (std::find(textures.begin(),textures.end(),h)==textures.end()) { - textures.push_back(h); + CTerrainTextureEntry* tex=m_Patch->m_MiniPatches[j][i].GetTextureEntry(); + texgrid[j][i]=tex; + if (std::find(textures.begin(),textures.end(),tex)==textures.end()) { + textures.push_back(tex); } } } @@ -323,15 +322,15 @@ void CPatchRData::BuildIndices() // build indices for base splats size_t base=m_VBBase->m_Index; for (size_t i=0;iGetTexture()->Bind(); + else + g_Renderer.GetTextureManager().GetErrorTexture()->Bind(); if (!g_Renderer.m_SkipSubmit) { glDrawElements(GL_QUADS, (GLsizei)splat.m_IndexCount, @@ -588,7 +591,11 @@ void CPatchRData::RenderBlends() for (size_t i=0;iGetTexture()->Bind(); + else + g_Renderer.GetTextureManager().GetErrorTexture()->Bind(); if (!g_Renderer.m_SkipSubmit) { glDrawElements(GL_QUADS, (GLsizei)splat.m_IndexCount, diff --git a/source/renderer/PatchRData.h b/source/renderer/PatchRData.h index 330531eb5d..53385bbf5c 100644 --- a/source/renderer/PatchRData.h +++ b/source/renderer/PatchRData.h @@ -25,6 +25,7 @@ #include "VertexBufferManager.h" class CPatch; +class CTerrainTextureEntry; ////////////////////////////////////////////////////////////////////////////////////////////////// // CPatchRData: class encapsulating logic for rendering terrain patches; holds per @@ -45,8 +46,8 @@ private: struct SSplat { SSplat() : m_Texture(0), m_IndexCount(0) {} - // handle of texture to apply during splat - Handle m_Texture; + // texture to apply during splat + CTerrainTextureEntry* m_Texture; // offset into the index array for this patch where splat starts size_t m_IndexStart; // number of indices used by splat @@ -76,9 +77,9 @@ private: }; 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_PriorityBind(2); + texture->Bind(1); + texture->Bind(0); } void FastPlayerColorRender::PrepareModel(int UNUSED(pass), CModel* model) @@ -234,11 +232,11 @@ bool SlowPlayerColorRender::EndPass(int pass) return true; } -void SlowPlayerColorRender::PrepareTexture(int pass, CTexture* texture) +void SlowPlayerColorRender::PrepareTexture(int pass, CTexturePtr& texture) { if (pass == 1) - g_Renderer.SetTexture(1, texture); - g_Renderer.SetTexture(0, texture); + texture->Bind(1); + texture->Bind(0); } @@ -393,10 +391,10 @@ const CMatrix3D* LitPlayerColorRender::GetTexGenMatrix(int UNUSED(pass)) return &GetShadowMap()->GetTextureMatrix(); } -void LitPlayerColorRender::PrepareTexture(int pass, CTexture* texture) +void LitPlayerColorRender::PrepareTexture(int pass, CTexturePtr& texture) { if (pass == 0) - g_Renderer.SetTexture(0, texture); + texture->Bind(0); } 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); } } - - - diff --git a/source/renderer/PlayerRenderer.h b/source/renderer/PlayerRenderer.h index ea329c83a3..60c98d35de 100644 --- a/source/renderer/PlayerRenderer.h +++ b/source/renderer/PlayerRenderer.h @@ -40,7 +40,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); /** @@ -73,7 +73,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); }; @@ -95,7 +95,7 @@ public: int BeginPass(int pass); bool EndPass(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); }; diff --git a/source/renderer/RenderModifiers.cpp b/source/renderer/RenderModifiers.cpp index 348f46f896..4d541e9457 100644 --- a/source/renderer/RenderModifiers.cpp +++ b/source/renderer/RenderModifiers.cpp @@ -27,17 +27,14 @@ #include "maths/Matrix3D.h" -#include "ps/CLogger.h" - #include "graphics/LightEnv.h" #include "graphics/Model.h" +#include "graphics/TextureManager.h" #include "renderer/RenderModifiers.h" #include "renderer/Renderer.h" #include "renderer/ShadowMap.h" -#define LOG_CATEGORY L"graphics" - /////////////////////////////////////////////////////////////////////////////////////////////// @@ -126,9 +123,9 @@ bool PlainRenderModifier::EndPass(int UNUSED(pass)) 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; } -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 .. glDepthMask(0); - g_Renderer.SetTexture(0,0); + ogl_tex_bind(0, 0); glColor4f(1,1,1,0.75f); 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)) { - g_Renderer.SetTexture(0,0); + ogl_tex_bind(0, 0); return STREAM_POS; } @@ -295,7 +292,7 @@ bool SolidColorRenderModifier::EndPass(int UNUSED(pass)) return true; } -void SolidColorRenderModifier::PrepareTexture(int UNUSED(pass), CTexture* UNUSED(texture)) +void SolidColorRenderModifier::PrepareTexture(int UNUSED(pass), CTexturePtr& UNUSED(texture)) { } diff --git a/source/renderer/RenderModifiers.h b/source/renderer/RenderModifiers.h index 2d4a310e34..1e94294c37 100644 --- a/source/renderer/RenderModifiers.h +++ b/source/renderer/RenderModifiers.h @@ -25,12 +25,11 @@ #define INCLUDED_RENDERMODIFIERS #include "ModelRenderer.h" - +#include "graphics/Texture.h" class CLightEnv; class CMatrix3D; class CModel; -class CTexture; class ShadowMap; /** @@ -94,7 +93,7 @@ public: * @param pass The current pass number (pass == 0 is the first pass) * @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. @@ -172,7 +171,7 @@ public: // Implementation int BeginPass(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); const CMatrix3D* GetTexGenMatrix(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); }; @@ -207,7 +206,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); }; @@ -227,7 +226,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); }; diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index 305f020ff6..adbc69ef0d 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -40,6 +40,7 @@ #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/Texture.h" +#include "graphics/TextureManager.h" #include "graphics/LightEnv.h" #include "graphics/Terrain.h" #include "graphics/Model.h" @@ -214,6 +215,9 @@ public: /// Sky manager SkyManager skyManager; + /// Texture manager + CTextureManager textureManager; + /// Terrain renderer TerrainRenderer* terrainRenderer; @@ -273,7 +277,7 @@ public: CRendererInternals() - : IsOpen(false), profileTable(g_Renderer.m_Stats) + : IsOpen(false), profileTable(g_Renderer.m_Stats), textureManager(g_VFS) { terrainRenderer = new TerrainRenderer(); shadow = new ShadowMap(); @@ -889,7 +893,7 @@ void CRenderer::RenderPatches() // setup some renderstate .. glDepthMask(0); - SetTexture(0,0); + ogl_tex_bind(0, 0); glColor4f(1,1,1,0.35f); glLineWidth(2.0f); @@ -1326,7 +1330,7 @@ void CRenderer::RenderSubmissions() // EndFrame: signal frame end void CRenderer::EndFrame() { - g_Renderer.SetTexture(0,0); + ogl_tex_bind(0, 0); static bool once=false; 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 void CRenderer::BindTexture(int unit,GLuint tex) @@ -1497,32 +1459,6 @@ void CRenderer::BindTexture(int unit,GLuint 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) { dst[0]=src[0]; @@ -1765,4 +1701,7 @@ void CRenderer::ScriptingInit() } - +CTextureManager& CRenderer::GetTextureManager() +{ + return m->textureManager; +} diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index 80edb417a7..7128d0924a 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -37,11 +37,11 @@ class CPatch; class CMaterial; class CModel; class CLightEnv; -class CTexture; class RenderPathVertexShader; class WaterManager; class SkyManager; +class CTextureManager; // rendering modes enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; @@ -247,15 +247,8 @@ public: // get the mode to render subsequent models 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 void BindTexture(int unit,GLuint tex); - // query transparency of given texture - bool IsTextureTransparent(CTexture* texture); // load the default set of alphamaps. // return a negative error code if anything along the way fails. @@ -291,6 +284,8 @@ public: */ SkyManager* GetSkyManager() { return m_SkyManager; } + CTextureManager& GetTextureManager(); + /** * SetFastPlayerColor: Tell the renderer which path to take for * player colored models. Both paths should provide the same visual diff --git a/source/renderer/SkyManager.cpp b/source/renderer/SkyManager.cpp index 317d82b604..89a4beed62 100644 --- a/source/renderer/SkyManager.cpp +++ b/source/renderer/SkyManager.cpp @@ -39,6 +39,7 @@ #include "graphics/Camera.h" #include "graphics/LightEnv.h" +#include "graphics/TextureManager.h" #define LOG_CATEGORY L"graphics" @@ -64,74 +65,26 @@ SkyManager::SkyManager() { m_RenderSky = true; - // TODO: add a way to set the initial skyset before progressive load - m_SkySet = L"default"; + m_SkySet = L""; 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 -LibError SkyManager::LoadSkyTexture(size_t index) +// Load all sky textures +void SkyManager::LoadSkyTextures() { - wchar_t filename[PATH_MAX]; - 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) + for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i) { - LOG(CLogger::Error, LOG_CATEGORY, L"SkyManager::LoadSkyTexture failed on \"%ls\"", filename); - return ht; + VfsPath path = VfsPath(L"art/textures/skies") / std::wstring(m_SkySet) / (std::wstring(s_imageNames[i])+L".dds"); + + 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; m_SkySet = newSet; - UnloadSkyTextures(); - - for(size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); i++) - LoadSkyTexture(i); + LoadSkyTextures(); } /////////////////////////////////////////////////////////////////// @@ -182,6 +132,10 @@ void SkyManager::RenderSky() // 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. + // Do nothing unless SetSkySet was called + if (m_SkySet.empty()) + return; + glDepthMask( GL_FALSE ); pglActiveTextureARB(GL_TEXTURE0_ARB); @@ -204,7 +158,7 @@ void SkyManager::RenderSky() const float D = 2000.0; // Front face (positive Z) - ogl_tex_bind( m_SkyTexture[FRONT] ); + m_SkyTexture[FRONT]->Bind(); glBegin( GL_QUADS ); glTexCoord2f( 0, 1 ); glVertex3f( -D, -D, +D ); @@ -217,7 +171,7 @@ void SkyManager::RenderSky() glEnd(); // Back face (negative Z) - ogl_tex_bind( m_SkyTexture[BACK] ); + m_SkyTexture[BACK]->Bind(); glBegin( GL_QUADS ); glTexCoord2f( 1, 1 ); glVertex3f( -D, -D, -D ); @@ -230,7 +184,7 @@ void SkyManager::RenderSky() glEnd(); // Right face (negative X) - ogl_tex_bind( m_SkyTexture[RIGHT] ); + m_SkyTexture[RIGHT]->Bind(); glBegin( GL_QUADS ); glTexCoord2f( 0, 1 ); glVertex3f( -D, -D, -D ); @@ -243,7 +197,7 @@ void SkyManager::RenderSky() glEnd(); // Left face (positive X) - ogl_tex_bind( m_SkyTexture[LEFT] ); + m_SkyTexture[LEFT]->Bind(); glBegin( GL_QUADS ); glTexCoord2f( 1, 1 ); glVertex3f( +D, -D, -D ); @@ -256,7 +210,7 @@ void SkyManager::RenderSky() glEnd(); // Top face (positive Y) - ogl_tex_bind( m_SkyTexture[TOP] ); + m_SkyTexture[TOP]->Bind(); glBegin( GL_QUADS ); glTexCoord2f( 1, 0 ); glVertex3f( +D, +D, -D ); diff --git a/source/renderer/SkyManager.h b/source/renderer/SkyManager.h index a57239728e..bcd64af087 100644 --- a/source/renderer/SkyManager.h +++ b/source/renderer/SkyManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -22,7 +22,7 @@ #ifndef INCLUDED_SKYMANAGER #define INCLUDED_SKYMANAGER -#include "ps/Overlay.h" +#include "graphics/Texture.h" /** * Class SkyManager: Maintain sky settings and textures, and render the sky. @@ -35,22 +35,6 @@ public: public: 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. @@ -76,11 +60,7 @@ public: std::vector GetSkySets() const; private: - /** - * load texture file from skyset and store in m_SkyTexture - * @param index 0..numTextures-1 - **/ - LibError LoadSkyTexture(size_t index); + void LoadSkyTextures(); /// Name of current skyset (a directory within art/textures/skies) CStrW m_SkySet; @@ -97,13 +77,10 @@ private: }; // 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 static const wchar_t* s_imageNames[numTextures]; - - /// State of progressive loading (in # of loaded textures) - size_t cur_loading_tex; }; diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp index 4a8cd8679a..fc7ab0fb67 100644 --- a/source/renderer/TerrainRenderer.cpp +++ b/source/renderer/TerrainRenderer.cpp @@ -450,11 +450,11 @@ void TerrainRenderer::RenderWater() if(fancy) { - ogl_tex_bind(WaterMgr->m_NormalMap[curTex], 0); + WaterMgr->m_NormalMap[curTex]->Bind(); } 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" diff --git a/source/renderer/TransparencyRenderer.cpp b/source/renderer/TransparencyRenderer.cpp index 4e09f97c07..82adf3c76a 100644 --- a/source/renderer/TransparencyRenderer.cpp +++ b/source/renderer/TransparencyRenderer.cpp @@ -33,6 +33,7 @@ #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" +#include "graphics/TextureManager.h" #include "ps/Profile.h" @@ -460,7 +461,6 @@ void SortModelRenderer::Submit(CModel* model) rdata = smdl; model->SetRenderData(rdata); model->SetDirty(~0u); - g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE); } m->models.push_back(smdl); @@ -536,7 +536,7 @@ void SortModelRenderer::Render(const RenderModifierPtr& modifier, int flags) int streamflags = modifier->BeginPass(pass); const CMatrix3D* texturematrix = 0; CModelDefPtr lastmdef; - CTexture* lasttex = 0; + CTexturePtr lasttex; if (streamflags & STREAM_TEXGENTOUV1) texturematrix = modifier->GetTexGenMatrix(pass); @@ -554,7 +554,7 @@ void SortModelRenderer::Render(const RenderModifierPtr& modifier, int flags) debug_assert(smdl->GetKey() == m); CModelDefPtr mdef = mdl->GetModelDef(); - CTexture* tex = mdl->GetTexture(); + CTexturePtr tex = mdl->GetTexture(); // Prepare per-CModelDef data if changed if (mdef != lastmdef) @@ -661,9 +661,9 @@ bool TransparentRenderModifier::EndPass(int pass) 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)) @@ -798,9 +798,9 @@ const CMatrix3D* LitTransparentRenderModifier::GetTexGenMatrix(int UNUSED(pass)) 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)) @@ -851,9 +851,9 @@ bool TransparentShadowRenderModifier::EndPass(int UNUSED(pass)) 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)) @@ -901,9 +901,9 @@ bool TransparentDepthShadowModifier::EndPass(int UNUSED(pass)) 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)) diff --git a/source/renderer/TransparencyRenderer.h b/source/renderer/TransparencyRenderer.h index 8a4d40ed56..eb2283c89c 100644 --- a/source/renderer/TransparencyRenderer.h +++ b/source/renderer/TransparencyRenderer.h @@ -104,7 +104,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); }; @@ -127,7 +127,7 @@ public: int BeginPass(int pass); bool EndPass(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); }; @@ -146,7 +146,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); }; @@ -164,7 +164,7 @@ public: // Implementation int BeginPass(int pass); bool EndPass(int pass); - void PrepareTexture(int pass, CTexture* texture); + void PrepareTexture(int pass, CTexturePtr& texture); void PrepareModel(int pass, CModel* model); }; diff --git a/source/renderer/WaterManager.cpp b/source/renderer/WaterManager.cpp index f119525409..eb46a50e0e 100644 --- a/source/renderer/WaterManager.cpp +++ b/source/renderer/WaterManager.cpp @@ -21,6 +21,8 @@ #include "precompiled.h" +#include "graphics/TextureManager.h" + #include "lib/bits.h" #include "lib/timer.h" #include "lib/tex/tex.h" @@ -28,15 +30,9 @@ #include "maths/MathUtil.h" -#include "ps/Filesystem.h" -#include "ps/CLogger.h" -#include "ps/Loader.h" - #include "renderer/WaterManager.h" #include "renderer/Renderer.h" -#define LOG_CATEGORY L"graphics" - /////////////////////////////////////////////////////////////////////////////////////////////// // WaterManager implementation @@ -73,15 +69,6 @@ WaterManager::WaterManager() m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f); m_Murkiness = 0.45f; 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() @@ -95,53 +82,41 @@ WaterManager::~WaterManager() // Progressive load of water textures int WaterManager::LoadWaterTextures() { - const size_t num_textures = ARRAY_SIZE(m_WaterTexture); - const size_t num_normal_maps = ARRAY_SIZE(m_NormalMap); + // TODO: this doesn't need to be progressive-loading any more + // (since texture loading is async now) // TODO: add a member variable and setter for this. (can't make this // a parameter because this function is called via delay-load code) 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]; // 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); - Handle ht = ogl_tex_load(g_VFS, pathname); - if (ht <= 0) - { - LOG(CLogger::Error, LOG_CATEGORY, L"LoadWaterTextures failed on \"%ls\"", pathname); - return ht; - } - 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); + swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/diffuse%02d.dds", water_type, (int)i+1); + CTextureProperties textureProps(pathname); + textureProps.SetWrap(GL_REPEAT); + + CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); + texture->Prefetch(); + m_WaterTexture[i] = texture; } // 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); - Handle ht = ogl_tex_load(g_VFS, pathname); - if (ht <= 0) - { - LOG(CLogger::Error, LOG_CATEGORY, L"LoadWaterTextures failed on \"%ls\"", pathname); - return ht; - } - 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); + swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal%02d.dds", water_type, (int)i+1); + CTextureProperties textureProps(pathname); + textureProps.SetWrap(GL_REPEAT); + + CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); + texture->Prefetch(); + m_NormalMap[i] = texture; } // 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 // the reflection/refraction textures to be that large?) 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++) { - ogl_tex_free(m_WaterTexture[i]); - m_WaterTexture[i] = 0; + m_WaterTexture[i].reset(); } for(size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++) { - ogl_tex_free(m_NormalMap[i]); - m_NormalMap[i] = 0; + m_NormalMap[i].reset(); } - - cur_loading_water_tex = 0; // so they will be reloaded if LoadWaterTextures is called again - cur_loading_normal_map = 0; } diff --git a/source/renderer/WaterManager.h b/source/renderer/WaterManager.h index 427b2abc05..049d43eca2 100644 --- a/source/renderer/WaterManager.h +++ b/source/renderer/WaterManager.h @@ -22,6 +22,7 @@ #ifndef INCLUDED_WATERMANAGER #define INCLUDED_WATERMANAGER +#include "graphics/Texture.h" #include "ps/Overlay.h" #include "maths/Matrix3D.h" #include "lib/ogl.h" @@ -35,8 +36,8 @@ class WaterManager { public: - Handle m_WaterTexture[60]; - Handle m_NormalMap[60]; + CTexturePtr m_WaterTexture[60]; + CTexturePtr m_NormalMap[60]; int m_WaterCurrentTex; CColor m_WaterColor; @@ -100,11 +101,6 @@ public: * and it hasn't been configured off) */ bool WillRenderFancyWater(); - -private: - /// State of progressive loading (in # of loaded textures) - size_t cur_loading_water_tex; - size_t cur_loading_normal_map; }; diff --git a/source/scripting/ScriptGlue.cpp b/source/scripting/ScriptGlue.cpp index 85b0438198..311f0ed851 100644 --- a/source/scripting/ScriptGlue.cpp +++ b/source/scripting/ScriptGlue.cpp @@ -60,7 +60,6 @@ #include "simulation2/Simulation2.h" #define LOG_CATEGORY L"script" -extern bool g_TerrainModified; // rationale: the function table is now at the end of the source file to // avoid the need for forward declarations for every function. diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.cpp b/source/tools/atlas/AtlasScript/ScriptInterface.cpp index 3f50f929b5..7b8a5e115e 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.cpp +++ b/source/tools/atlas/AtlasScript/ScriptInterface.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -228,6 +228,14 @@ namespace //////////////////////////////////////////////////////////////// // Primitive types: + template<> struct ToJSVal + { + static jsval Convert(JSContext* cx, const bool& val) + { + return val ? JSVAL_TRUE : JSVAL_FALSE; + } + }; + template<> struct ToJSVal { static jsval Convert(JSContext* cx, const float& val) @@ -377,7 +385,9 @@ namespace JS_AddRoot(cx, &obj); JS_DefineProperty(cx, obj, "name", ToJSVal::Convert(cx, *val.name), NULL, NULL, JSPROP_ENUMERATE); - + + JS_DefineProperty(cx, obj, "loaded", ToJSVal::Convert(cx, val.loaded), NULL, NULL, JSPROP_ENUMERATE); + unsigned char* buf = (unsigned char*)(malloc(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))); diff --git a/source/tools/atlas/GameInterface/GameLoop.cpp b/source/tools/atlas/GameInterface/GameLoop.cpp index 54f0f8981c..ee2fb8e39f 100644 --- a/source/tools/atlas/GameInterface/GameLoop.cpp +++ b/source/tools/atlas/GameInterface/GameLoop.cpp @@ -28,6 +28,7 @@ #include "InputProcessor.h" +#include "graphics/TextureManager.h" #include "gui/GUIManager.h" #include "lib/app_hooks.h" #include "lib/external_libraries/sdl.h" @@ -37,6 +38,7 @@ #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/GameSetup/Paths.h" +#include "renderer/Renderer.h" #include "scripting/ScriptingHost.h" using namespace AtlasMessage; @@ -95,6 +97,23 @@ static ErrorReaction AtlasDisplayError(const wchar_t* text, size_t flags) 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) { // Load required symbols from the DLL @@ -232,6 +251,8 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll) ReloadChangedFiles(); + RendererIncrementalLoad(); + // Pump SDL events (e.g. hotkeys) SDL_Event_ ev; while (SDL_PollEvent(&ev.ev)) diff --git a/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp index 7e8b530952..03c4253c2b 100644 --- a/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp @@ -74,7 +74,7 @@ QUERYHANDLER(GetTerrainGroupPreviews) // disk, which is slow.) GLint w, h; 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_HEIGHT, &h); @@ -109,6 +109,7 @@ QUERYHANDLER(GetTerrainGroupPreviews) delete[] texdata; } + previews.back().loaded = (*it)->GetTexture()->IsLoaded(); previews.back().imagewidth = msg->imagewidth; previews.back().imageheight = msg->imageheight; previews.back().imagedata = buf; diff --git a/source/tools/atlas/GameInterface/Messages.h b/source/tools/atlas/GameInterface/Messages.h index 398872fd3a..a52d9a9185 100644 --- a/source/tools/atlas/GameInterface/Messages.h +++ b/source/tools/atlas/GameInterface/Messages.h @@ -196,6 +196,7 @@ QUERY(GetTerrainGroups, struct sTerrainGroupPreview { Shareable name; + Shareable loaded; Shareable imagewidth; Shareable imageheight; Shareable > imagedata; // RGB*width*height