Index: ps/trunk/source/ps/CStrIntern.h =================================================================== --- ps/trunk/source/ps/CStrIntern.h (revision 23190) +++ ps/trunk/source/ps/CStrIntern.h (revision 23191) @@ -1,104 +1,111 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2019 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_CSTRINTERN #define INCLUDED_CSTRINTERN class CStrInternInternals; /** * Interned 8-bit strings. * Each instance with the same string content is a pointer to the same piece of * memory, allowing very fast string comparisons. * * Since a CStrIntern is just a dumb pointer, copying is very fast, * and pass-by-value should be preferred over pass-by-reference. * * Memory allocated for strings will never be freed, so don't use this for * unbounded numbers of strings (e.g. text rendered by gameplay scripts) - * it's intended for a small number of short frequently-used strings. * * Not thread-safe - only allocate these strings from the main thread. */ class CStrIntern { public: CStrIntern(); explicit CStrIntern(const char* str); explicit CStrIntern(const std::string& str); /** * Returns cached FNV1-A hash of the string. */ u32 GetHash() const; /** * Returns null-terminated string. */ const char* c_str() const; /** * Returns length of string in bytes. */ size_t length() const; bool empty() const; /** * Returns as std::string. */ const std::string& string() const; /** * String equality. */ bool operator==(const CStrIntern& b) const { return m == b.m; } bool operator!=(const CStrIntern& b) const { return m != b.m; } /** * Compare with some arbitrary total order. * (In particular, this is not alphabetic order, * and is not consistent between runs of the game.) */ bool operator<(const CStrIntern& b) const { return m < b.m; } private: CStrInternInternals* m; }; -static inline size_t hash_value(const CStrIntern& str) +namespace std { - return str.GetHash(); +template<> +struct hash +{ + std::size_t operator()(const CStrIntern& str) const + { + return str.GetHash(); + } +}; } #define X(id) extern CStrIntern str_##id; #define X2(id, str) extern CStrIntern str_##id; #include "CStrInternStatic.h" #undef X #undef X2 #endif // INCLUDED_CSTRINTERN Index: ps/trunk/source/renderer/OverlayRenderer.cpp =================================================================== --- ps/trunk/source/renderer/OverlayRenderer.cpp (revision 23190) +++ ps/trunk/source/renderer/OverlayRenderer.cpp (revision 23191) @@ -1,775 +1,780 @@ /* Copyright (C) 2019 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 "OverlayRenderer.h" -#include #include "graphics/LOSTexture.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" +#include "lib/hash.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Quaternion.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include "renderer/TexturedLineRData.h" #include "renderer/VertexArray.h" #include "renderer/VertexBuffer.h" #include "renderer/VertexBufferManager.h" -#include "simulation2/Simulation2.h" #include "simulation2/components/ICmpWaterManager.h" +#include "simulation2/Simulation2.h" #include "simulation2/system/SimContext.h" +#include + /** * Key used to group quads into batches for more efficient rendering. Currently groups by the combination * of the main texture and the texture mask, to minimize texture swapping during rendering. */ struct QuadBatchKey { QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask) : m_Texture(texture), m_TextureMask(textureMask) { } bool operator==(const QuadBatchKey& other) const { return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask); } CTexturePtr m_Texture; CTexturePtr m_TextureMask; }; +struct QuadBatchHash +{ + std::size_t operator()(const QuadBatchKey& d) const + { + size_t seed = 0; + hash_combine(seed, d.m_Texture); + hash_combine(seed, d.m_TextureMask); + return seed; + } +}; + /** * Holds information about a single quad rendering batch. */ class QuadBatchData : public CRenderData { public: QuadBatchData() : m_IndicesBase(0), m_NumRenderQuads(0) { } /// Holds the quad overlay structures requested to be rendered in this batch. Must be cleared /// after each frame. std::vector m_Quads; /// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals). size_t m_IndicesBase; /// Amount of quads to actually render in this batch. Potentially (although unlikely to be) /// different from m_Quads.size() due to restrictions on the total amount of quads that can be /// rendered. Must be reset after each frame. size_t m_NumRenderQuads; }; struct OverlayRendererInternals { - typedef boost::unordered_map QuadBatchMap; + using QuadBatchMap = std::unordered_map; OverlayRendererInternals(); ~OverlayRendererInternals(){ } std::vector lines; std::vector texlines; std::vector sprites; std::vector quads; std::vector spheres; QuadBatchMap quadBatchMap; // Dedicated vertex/index buffers for rendering all quads (to within the limits set by // MAX_QUAD_OVERLAYS). VertexArray quadVertices; VertexArray::Attribute quadAttributePos; VertexArray::Attribute quadAttributeColor; VertexArray::Attribute quadAttributeUV; VertexIndexArray quadIndices; /// Maximum amount of quad overlays we support for rendering. This limit is set to be able to /// render all quads from a single dedicated VB without having to reallocate it, which is much /// faster in the typical case of rendering only a handful of quads. When modifying this value, /// you must take care for the new amount of quads to fit in a single VBO (which is not likely /// to be a problem). static const size_t MAX_QUAD_OVERLAYS = 1024; // Sets of commonly-(re)used shader defines. CShaderDefines defsOverlayLineNormal; CShaderDefines defsOverlayLineAlwaysVisible; CShaderDefines defsQuadOverlay; // Geometry for a unit sphere std::vector sphereVertexes; std::vector sphereIndexes; void GenerateSphere(); /// Performs one-time setup. Called from CRenderer::Open, after graphics capabilities have /// been detected. Note that no VBOs must be created before this is called, since the shader /// path and graphics capabilities are not guaranteed to be stable before this point. void Initialize(); }; const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f; OverlayRendererInternals::OverlayRendererInternals() : quadVertices(GL_DYNAMIC_DRAW), quadIndices(GL_STATIC_DRAW) { quadAttributePos.elems = 3; quadAttributePos.type = GL_FLOAT; quadVertices.AddAttribute(&quadAttributePos); quadAttributeColor.elems = 4; quadAttributeColor.type = GL_FLOAT; quadVertices.AddAttribute(&quadAttributeColor); quadAttributeUV.elems = 2; quadAttributeUV.type = GL_SHORT; // don't use GL_UNSIGNED_SHORT here, TexCoordPointer won't accept it quadVertices.AddAttribute(&quadAttributeUV); // Note that we're reusing the textured overlay line shader for the quad overlay rendering. This // is because their code is almost identical; the only difference is that for the quad overlays // we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the // shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define. defsOverlayLineNormal.Add(str_USE_OBJECTCOLOR, str_1); defsOverlayLineAlwaysVisible.Add(str_USE_OBJECTCOLOR, str_1); defsOverlayLineAlwaysVisible.Add(str_IGNORE_LOS, str_1); } void OverlayRendererInternals::Initialize() { // Perform any initialization after graphics capabilities have been detected. Notably, // only at this point can we safely allocate VBOs (in contrast to e.g. in the constructor), // because their creation depends on the shader path, which is not reliably set before this point. quadVertices.SetNumVertices(MAX_QUAD_OVERLAYS * 4); quadVertices.Layout(); // allocate backing store quadIndices.SetNumVertices(MAX_QUAD_OVERLAYS * 6); quadIndices.Layout(); // allocate backing store // Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the // indices are always the same; we can therefore fill in all the indices once and pretty much forget about // them. We then also no longer need its backing store, since we never change any indices afterwards. VertexArrayIterator index = quadIndices.GetIterator(); for (size_t i = 0; i < MAX_QUAD_OVERLAYS; ++i) { *index++ = i*4 + 0; *index++ = i*4 + 1; *index++ = i*4 + 2; *index++ = i*4 + 2; *index++ = i*4 + 3; *index++ = i*4 + 0; } quadIndices.Upload(); quadIndices.FreeBackingStore(); } -static size_t hash_value(const QuadBatchKey& d) -{ - size_t seed = 0; - boost::hash_combine(seed, d.m_Texture); - boost::hash_combine(seed, d.m_TextureMask); - return seed; -} - OverlayRenderer::OverlayRenderer() { m = new OverlayRendererInternals(); } OverlayRenderer::~OverlayRenderer() { delete m; } void OverlayRenderer::Initialize() { m->Initialize(); } void OverlayRenderer::Submit(SOverlayLine* line) { ENSURE(line->m_Coords.size() % 3 == 0); m->lines.push_back(line); } void OverlayRenderer::Submit(SOverlayTexturedLine* line) { // Simplify the rest of the code by guaranteeing non-empty lines if (line->m_Coords.empty()) return; ENSURE(line->m_Coords.size() % 2 == 0); m->texlines.push_back(line); } void OverlayRenderer::Submit(SOverlaySprite* overlay) { m->sprites.push_back(overlay); } void OverlayRenderer::Submit(SOverlayQuad* overlay) { m->quads.push_back(overlay); } void OverlayRenderer::Submit(SOverlaySphere* overlay) { m->spheres.push_back(overlay); } void OverlayRenderer::EndFrame() { m->lines.clear(); m->texlines.clear(); m->sprites.clear(); m->quads.clear(); m->spheres.clear(); // this should leave the capacity unchanged, which is okay since it // won't be very large or very variable // Empty the batch rendering data structures, but keep their key mappings around for the next frames for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) { QuadBatchData& quadBatchData = (it->second); quadBatchData.m_Quads.clear(); quadBatchData.m_NumRenderQuads = 0; quadBatchData.m_IndicesBase = 0; } } void OverlayRenderer::PrepareForRendering() { PROFILE3("prepare overlays"); // This is where we should do something like sort the overlays by // color/sprite/etc for more efficient rendering for (size_t i = 0; i < m->texlines.size(); ++i) { SOverlayTexturedLine* line = m->texlines[i]; if (!line->m_RenderData) { line->m_RenderData = shared_ptr(new CTexturedLineRData()); line->m_RenderData->Update(*line); // We assume the overlay line will get replaced by the caller // if terrain changes, so we don't need to detect that here and // call Update again. Also we assume the caller won't change // any of the parameters after first submitting the line. } } // Group quad overlays by their texture/mask combination for efficient rendering // TODO: consider doing this directly in Submit() for (size_t i = 0; i < m->quads.size(); ++i) { SOverlayQuad* const quad = m->quads[i]; QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask); QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist // add overlay to list of quads batchRenderData.m_Quads.push_back(quad); } const CVector3D vOffset(0, OverlayRenderer::OVERLAY_VOFFSET, 0); // Write quad overlay vertices/indices to VA backing store VertexArrayIterator vertexPos = m->quadAttributePos.GetIterator(); VertexArrayIterator vertexColor = m->quadAttributeColor.GetIterator(); VertexArrayIterator vertexUV = m->quadAttributeUV.GetIterator(); size_t indicesIdx = 0; size_t totalNumQuads = 0; for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) { QuadBatchData& batchRenderData = (it->second); batchRenderData.m_NumRenderQuads = 0; if (batchRenderData.m_Quads.empty()) continue; // Remember the current index into the (entire) indices array as our base offset for this batch batchRenderData.m_IndicesBase = indicesIdx; // points to the index where each iteration's vertices will be appended for (size_t i = 0; i < batchRenderData.m_Quads.size() && totalNumQuads < OverlayRendererInternals::MAX_QUAD_OVERLAYS; i++) { const SOverlayQuad* quad = batchRenderData.m_Quads[i]; // TODO: this is kind of ugly, the iterator should use a type that can have quad->m_Color assigned // to it directly const CVector4D quadColor(quad->m_Color.r, quad->m_Color.g, quad->m_Color.b, quad->m_Color.a); *vertexPos++ = quad->m_Corners[0] + vOffset; *vertexPos++ = quad->m_Corners[1] + vOffset; *vertexPos++ = quad->m_Corners[2] + vOffset; *vertexPos++ = quad->m_Corners[3] + vOffset; (*vertexUV)[0] = 0; (*vertexUV)[1] = 0; ++vertexUV; (*vertexUV)[0] = 0; (*vertexUV)[1] = 1; ++vertexUV; (*vertexUV)[0] = 1; (*vertexUV)[1] = 1; ++vertexUV; (*vertexUV)[0] = 1; (*vertexUV)[1] = 0; ++vertexUV; *vertexColor++ = quadColor; *vertexColor++ = quadColor; *vertexColor++ = quadColor; *vertexColor++ = quadColor; indicesIdx += 6; totalNumQuads++; batchRenderData.m_NumRenderQuads++; } } m->quadVertices.Upload(); // don't free the backing store! we'll overwrite it on the next frame to save a reallocation. m->quadVertices.PrepareForRendering(); } void OverlayRenderer::RenderOverlaysBeforeWater() { PROFILE3_GPU("overlays (before)"); #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderOverlaysBeforeWater for GLES #else pglActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); // Ignore z so that we draw behind terrain (but don't disable GL_DEPTH_TEST // since we still want to write to the z buffer) glDepthFunc(GL_ALWAYS); for (size_t i = 0; i < m->lines.size(); ++i) { SOverlayLine* line = m->lines[i]; if (line->m_Coords.empty()) continue; ENSURE(line->m_Coords.size() % 3 == 0); glColor4fv(line->m_Color.FloatArray()); glLineWidth((float)line->m_Thickness); glInterleavedArrays(GL_V3F, sizeof(float)*3, &line->m_Coords[0]); glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)line->m_Coords.size()/3); } glDisableClientState(GL_VERTEX_ARRAY); glLineWidth(1.f); glDepthFunc(GL_LEQUAL); glDisable(GL_BLEND); #endif } void OverlayRenderer::RenderOverlaysAfterWater() { PROFILE3_GPU("overlays (after)"); RenderTexturedOverlayLines(); RenderQuadOverlays(); RenderSphereOverlays(); } void OverlayRenderer::RenderTexturedOverlayLines() { #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderTexturedOverlayLines for GLES return; #endif if (m->texlines.empty()) return; ogl_WarnIfError(); pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(0); const char* shaderName; if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) shaderName = "arb/overlayline"; else shaderName = "fixed:overlayline"; CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); CShaderManager& shaderManager = g_Renderer.GetShaderManager(); CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, m->defsOverlayLineNormal)); CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, m->defsOverlayLineAlwaysVisible)); // ---------------------------------------------------------------------------------------- if (shaderTexLineNormal) { shaderTexLineNormal->Bind(); shaderTexLineNormal->BindTexture(str_losTex, los.GetTexture()); shaderTexLineNormal->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); // batch render only the non-always-visible overlay lines using the normal shader RenderTexturedOverlayLines(shaderTexLineNormal, false); shaderTexLineNormal->Unbind(); } // ---------------------------------------------------------------------------------------- if (shaderTexLineAlwaysVisible) { shaderTexLineAlwaysVisible->Bind(); // TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted shaderTexLineAlwaysVisible->BindTexture(str_losTex, los.GetTexture()); shaderTexLineAlwaysVisible->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); // batch render only the always-visible overlay lines using the LoS-ignored shader RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true); shaderTexLineAlwaysVisible->Unbind(); } // ---------------------------------------------------------------------------------------- // TODO: the shaders should probably be responsible for unbinding their textures g_Renderer.BindTexture(1, 0); g_Renderer.BindTexture(0, 0); CVertexBuffer::Unbind(); glDepthMask(1); glDisable(GL_BLEND); } void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible) { for (size_t i = 0; i < m->texlines.size(); ++i) { SOverlayTexturedLine* line = m->texlines[i]; // render only those lines matching the requested alwaysVisible status if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible) continue; ENSURE(line->m_RenderData); line->m_RenderData->Render(*line, shader); } } void OverlayRenderer::RenderQuadOverlays() { #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderQuadOverlays for GLES return; #endif if (m->quadBatchMap.empty()) return; ogl_WarnIfError(); pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(0); const char* shaderName; if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) shaderName = "arb/overlayline"; else shaderName = "fixed:overlayline"; CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); CShaderManager& shaderManager = g_Renderer.GetShaderManager(); CShaderProgramPtr shader(shaderManager.LoadProgram(shaderName, m->defsQuadOverlay)); // ---------------------------------------------------------------------------------------- if (shader) { shader->Bind(); shader->BindTexture(str_losTex, los.GetTexture()); shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); // Base offsets (in bytes) of the two backing stores relative to their owner VBO u8* indexBase = m->quadIndices.Bind(); u8* vertexBase = m->quadVertices.Bind(); GLsizei indexStride = m->quadIndices.GetStride(); GLsizei vertexStride = m->quadVertices.GetStride(); for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) { QuadBatchData& batchRenderData = it->second; const size_t batchNumQuads = batchRenderData.m_NumRenderQuads; // Careful; some drivers don't like drawing calls with 0 stuff to draw. if (batchNumQuads == 0) continue; const QuadBatchKey& maskPair = it->first; shader->BindTexture(str_baseTex, maskPair.m_Texture->GetHandle()); shader->BindTexture(str_maskTex, maskPair.m_TextureMask->GetHandle()); int streamflags = shader->GetStreamFlags(); if (streamflags & STREAM_POS) shader->VertexPointer(m->quadAttributePos.elems, m->quadAttributePos.type, vertexStride, vertexBase + m->quadAttributePos.offset); if (streamflags & STREAM_UV0) shader->TexCoordPointer(GL_TEXTURE0, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset); if (streamflags & STREAM_UV1) shader->TexCoordPointer(GL_TEXTURE1, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset); if (streamflags & STREAM_COLOR) shader->ColorPointer(m->quadAttributeColor.elems, m->quadAttributeColor.type, vertexStride, vertexBase + m->quadAttributeColor.offset); shader->AssertPointersBound(); glDrawElements(GL_TRIANGLES, (GLsizei)(batchNumQuads * 6), GL_UNSIGNED_SHORT, indexBase + indexStride * batchRenderData.m_IndicesBase); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2; } shader->Unbind(); } // ---------------------------------------------------------------------------------------- // TODO: the shader should probably be responsible for unbinding its textures g_Renderer.BindTexture(1, 0); g_Renderer.BindTexture(0, 0); CVertexBuffer::Unbind(); glDepthMask(1); glDisable(GL_BLEND); } void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) { PROFILE3_GPU("overlays (fg)"); #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderForegroundOverlays for GLES #else pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); CVector3D right = -viewCamera.m_Orientation.GetLeft(); CVector3D up = viewCamera.m_Orientation.GetUp(); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); CShaderProgramPtr shader; CShaderTechniquePtr tech; if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) { tech = g_Renderer.GetShaderManager().LoadEffect(str_foreground_overlay); tech->BeginPass(); shader = tech->GetShader(); } float uvs[8] = { 0,1, 1,1, 1,0, 0,0 }; if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, sizeof(float)*2, &uvs[0]); else glTexCoordPointer(2, GL_FLOAT, sizeof(float)*2, &uvs); for (size_t i = 0; i < m->sprites.size(); ++i) { SOverlaySprite* sprite = m->sprites[i]; if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) shader->BindTexture(str_baseTex, sprite->m_Texture); else sprite->m_Texture->Bind(); shader->Uniform(str_colorMul, sprite->m_Color); CVector3D pos[4] = { sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0, sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y0, sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1, sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y1 }; if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) shader->VertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X); else glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X); glDrawArrays(GL_QUADS, 0, (GLsizei)4); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris += 2; } if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) tech->EndPass(); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); #endif } static void TessellateSphereFace(const CVector3D& a, u16 ai, const CVector3D& b, u16 bi, const CVector3D& c, u16 ci, std::vector& vertexes, std::vector& indexes, int level) { if (level == 0) { indexes.push_back(ai); indexes.push_back(bi); indexes.push_back(ci); } else { CVector3D d = (a + b).Normalized(); CVector3D e = (b + c).Normalized(); CVector3D f = (c + a).Normalized(); int di = vertexes.size() / 3; vertexes.push_back(d.X); vertexes.push_back(d.Y); vertexes.push_back(d.Z); int ei = vertexes.size() / 3; vertexes.push_back(e.X); vertexes.push_back(e.Y); vertexes.push_back(e.Z); int fi = vertexes.size() / 3; vertexes.push_back(f.X); vertexes.push_back(f.Y); vertexes.push_back(f.Z); TessellateSphereFace(a,ai, d,di, f,fi, vertexes, indexes, level-1); TessellateSphereFace(d,di, b,bi, e,ei, vertexes, indexes, level-1); TessellateSphereFace(f,fi, e,ei, c,ci, vertexes, indexes, level-1); TessellateSphereFace(d,di, e,ei, f,fi, vertexes, indexes, level-1); } } static void TessellateSphere(std::vector& vertexes, std::vector& indexes, int level) { /* Start with a tetrahedron, then tessellate */ float s = sqrtf(0.5f); #define VERT(a,b,c) vertexes.push_back(a); vertexes.push_back(b); vertexes.push_back(c); VERT(-s, 0, -s); VERT( s, 0, -s); VERT( s, 0, s); VERT(-s, 0, s); VERT( 0, -1, 0); VERT( 0, 1, 0); #define FACE(a,b,c) \ TessellateSphereFace( \ CVector3D(vertexes[a*3], vertexes[a*3+1], vertexes[a*3+2]), a, \ CVector3D(vertexes[b*3], vertexes[b*3+1], vertexes[b*3+2]), b, \ CVector3D(vertexes[c*3], vertexes[c*3+1], vertexes[c*3+2]), c, \ vertexes, indexes, level); FACE(0,4,1); FACE(1,4,2); FACE(2,4,3); FACE(3,4,0); FACE(1,5,0); FACE(2,5,1); FACE(3,5,2); FACE(0,5,3); #undef FACE #undef VERT } void OverlayRendererInternals::GenerateSphere() { if (sphereVertexes.empty()) TessellateSphere(sphereVertexes, sphereIndexes, 3); } void OverlayRenderer::RenderSphereOverlays() { PROFILE3_GPU("overlays (spheres)"); #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderSphereOverlays for GLES #else if (g_RenderingOptions.GetRenderPath() != RenderPath::SHADER) return; if (m->spheres.empty()) return; glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(0); glEnableClientState(GL_VERTEX_ARRAY); CShaderProgramPtr shader; CShaderTechniquePtr tech; tech = g_Renderer.GetShaderManager().LoadEffect(str_overlay_solid); tech->BeginPass(); shader = tech->GetShader(); m->GenerateSphere(); shader->VertexPointer(3, GL_FLOAT, 0, &m->sphereVertexes[0]); for (size_t i = 0; i < m->spheres.size(); ++i) { SOverlaySphere* sphere = m->spheres[i]; CMatrix3D transform; transform.SetIdentity(); transform.Scale(sphere->m_Radius, sphere->m_Radius, sphere->m_Radius); transform.Translate(sphere->m_Center); shader->Uniform(str_transform, transform); shader->Uniform(str_color, sphere->m_Color); glDrawElements(GL_TRIANGLES, m->sphereIndexes.size(), GL_UNSIGNED_SHORT, &m->sphereIndexes[0]); g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3; } tech->EndPass(); glDisableClientState(GL_VERTEX_ARRAY); glDepthMask(1); glDisable(GL_BLEND); #endif } Index: ps/trunk/source/simulation2/serialization/SerializeTemplates.h =================================================================== --- ps/trunk/source/simulation2/serialization/SerializeTemplates.h (revision 23190) +++ ps/trunk/source/simulation2/serialization/SerializeTemplates.h (revision 23191) @@ -1,343 +1,343 @@ /* Copyright (C) 2019 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_SERIALIZETEMPLATES #define INCLUDED_SERIALIZETEMPLATES /** * @file * Helper templates for serializing/deserializing common objects. */ #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/serialization/IDeserializer.h" #include "simulation2/serialization/ISerializer.h" -#include +#include #include template struct SerializeVector { template void operator()(ISerializer& serialize, const char* name, std::vector& value) { size_t len = value.size(); serialize.NumberU32_Unbounded("length", (u32)len); for (size_t i = 0; i < len; ++i) ELEM()(serialize, name, value[i]); } template void operator()(IDeserializer& deserialize, const char* name, std::vector& value) { value.clear(); u32 len; deserialize.NumberU32_Unbounded("length", len); value.reserve(len); // TODO: watch out for out-of-memory for (size_t i = 0; i < len; ++i) { T el; ELEM()(deserialize, name, el); value.push_back(el); } } }; template struct SerializeRepetitiveVector { template void operator()(ISerializer& serialize, const char* name, std::vector& value) { size_t len = value.size(); serialize.NumberU32_Unbounded("length", (u32)len); if (len == 0) return; u32 count = 1; T prevVal = value[0]; for (size_t i = 1; i < len; ++i) { if (prevVal == value[i]) { count++; continue; } serialize.NumberU32_Unbounded("#", count); ELEM()(serialize, name, prevVal); count = 1; prevVal = value[i]; } serialize.NumberU32_Unbounded("#", count); ELEM()(serialize, name, prevVal); } template void operator()(IDeserializer& deserialize, const char* name, std::vector& value) { value.clear(); u32 len; deserialize.NumberU32_Unbounded("length", len); value.reserve(len); // TODO: watch out for out-of-memory for (size_t i = 0; i < len;) { u32 count; deserialize.NumberU32_Unbounded("#", count); T el; ELEM()(deserialize, name, el); i += count; value.insert(value.end(), count, el); } } }; template struct SerializeSet { template void operator()(ISerializer& serialize, const char* name, const std::set& value) { serialize.NumberU32_Unbounded("size", static_cast(value.size())); for (const T& elem : value) ELEM()(serialize, name, elem); } template void operator()(IDeserializer& deserialize, const char* name, std::set& value) { value.clear(); u32 size; deserialize.NumberU32_Unbounded("size", size); for (size_t i = 0; i < size; ++i) { T el; ELEM()(deserialize, name, el); value.emplace(std::move(el)); } } }; template struct SerializeMap { template void operator()(ISerializer& serialize, const char* UNUSED(name), std::map& value) { size_t len = value.size(); serialize.NumberU32_Unbounded("length", (u32)len); for (typename std::map::iterator it = value.begin(); it != value.end(); ++it) { KS()(serialize, "key", it->first); VS()(serialize, "value", it->second); } } template void operator()(ISerializer& serialize, const char* UNUSED(name), std::map& value, C& context) { size_t len = value.size(); serialize.NumberU32_Unbounded("length", (u32)len); for (typename std::map::iterator it = value.begin(); it != value.end(); ++it) { KS()(serialize, "key", it->first); VS()(serialize, "value", it->second, context); } } template void operator()(IDeserializer& deserialize, const char* UNUSED(name), M& value) { typedef typename M::key_type K; typedef typename M::value_type::second_type V; // M::data_type gives errors with gcc value.clear(); u32 len; deserialize.NumberU32_Unbounded("length", len); for (size_t i = 0; i < len; ++i) { K k; V v; KS()(deserialize, "key", k); VS()(deserialize, "value", v); value.emplace(std::move(k), std::move(v)); } } template void operator()(IDeserializer& deserialize, const char* UNUSED(name), M& value, C& context) { typedef typename M::key_type K; typedef typename M::value_type::second_type V; // M::data_type gives errors with gcc value.clear(); u32 len; deserialize.NumberU32_Unbounded("length", len); for (size_t i = 0; i < len; ++i) { K k; V v; KS()(deserialize, "key", k); VS()(deserialize, "value", v, context); value.emplace(std::move(k), std::move(v)); } } }; // We have to order the map before serializing to make things consistent template struct SerializeUnorderedMap { template - void operator()(ISerializer& serialize, const char* name, boost::unordered_map& value) + void operator()(ISerializer& serialize, const char* name, std::unordered_map& value) { std::map ordered_value(value.begin(), value.end()); SerializeMap()(serialize, name, ordered_value); } template - void operator()(IDeserializer& deserialize, const char* name, boost::unordered_map& value) + void operator()(IDeserializer& deserialize, const char* name, std::unordered_map& value) { SerializeMap()(deserialize, name, value); } }; template struct SerializeU8_Enum { void operator()(ISerializer& serialize, const char* name, T value) { serialize.NumberU8(name, value, 0, max); } void operator()(IDeserializer& deserialize, const char* name, T& value) { u8 val; deserialize.NumberU8(name, val, 0, max); value = static_cast(val); } }; struct SerializeU8_Unbounded { void operator()(ISerializer& serialize, const char* name, u8 value) { serialize.NumberU8_Unbounded(name, value); } void operator()(IDeserializer& deserialize, const char* name, u8& value) { deserialize.NumberU8_Unbounded(name, value); } }; struct SerializeU16_Unbounded { void operator()(ISerializer& serialize, const char* name, u16 value) { serialize.NumberU16_Unbounded(name, value); } void operator()(IDeserializer& deserialize, const char* name, u16& value) { deserialize.NumberU16_Unbounded(name, value); } }; struct SerializeU32_Unbounded { void operator()(ISerializer& serialize, const char* name, u32 value) { serialize.NumberU32_Unbounded(name, value); } void operator()(IDeserializer& deserialize, const char* name, u32& value) { deserialize.NumberU32_Unbounded(name, value); } }; struct SerializeI32_Unbounded { void operator()(ISerializer& serialize, const char* name, i32 value) { serialize.NumberI32_Unbounded(name, value); } void operator()(IDeserializer& deserialize, const char* name, i32& value) { deserialize.NumberI32_Unbounded(name, value); } }; struct SerializeBool { void operator()(ISerializer& serialize, const char* name, bool value) { serialize.Bool(name, value); } void operator()(IDeserializer& deserialize, const char* name, bool& value) { deserialize.Bool(name, value); } }; struct SerializeString { void operator()(ISerializer& serialize, const char* name, const std::string& value) { serialize.StringASCII(name, value, 0, UINT32_MAX); } void operator()(IDeserializer& deserialize, const char* name, std::string& value) { deserialize.StringASCII(name, value, 0, UINT32_MAX); } }; struct SerializeWaypoint { void operator()(ISerializer& serialize, const char* UNUSED(name), const Waypoint& value) { serialize.NumberFixed_Unbounded("waypoint x", value.x); serialize.NumberFixed_Unbounded("waypoint z", value.z); } void operator()(IDeserializer& deserialize, const char* UNUSED(name), Waypoint& value) { deserialize.NumberFixed_Unbounded("waypoint x", value.x); deserialize.NumberFixed_Unbounded("waypoint z", value.z); } }; struct SerializeGoal { template void operator()(S& serialize, const char* UNUSED(name), PathGoal& value) { SerializeU8_Enum()(serialize, "type", value.type); serialize.NumberFixed_Unbounded("goal x", value.x); serialize.NumberFixed_Unbounded("goal z", value.z); serialize.NumberFixed_Unbounded("goal u x", value.u.X); serialize.NumberFixed_Unbounded("goal u z", value.u.Y); serialize.NumberFixed_Unbounded("goal v x", value.v.X); serialize.NumberFixed_Unbounded("goal v z", value.v.Y); serialize.NumberFixed_Unbounded("goal hw", value.hw); serialize.NumberFixed_Unbounded("goal hh", value.hh); serialize.NumberFixed_Unbounded("maxdist", value.maxdist); } }; #endif // INCLUDED_SERIALIZETEMPLATES Index: ps/trunk/source/simulation2/system/ComponentManager.h =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.h (revision 23190) +++ ps/trunk/source/simulation2/system/ComponentManager.h (revision 23191) @@ -1,384 +1,383 @@ /* Copyright (C) 2019 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_COMPONENTMANAGER #define INCLUDED_COMPONENTMANAGER -#include "Entity.h" -#include "Components.h" +#include "ps/Filesystem.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptVal.h" #include "simulation2/helpers/Player.h" -#include "ps/Filesystem.h" +#include "simulation2/system/Components.h" +#include "simulation2/system/Entity.h" #include -#include #include #include #include class IComponent; class CParamNode; class CMessage; class CSimContext; class CDynamicSubscription; class CComponentManager { NONCOPYABLE(CComponentManager); public: // We can't use EInterfaceId/etc directly, since scripts dynamically generate new IDs // and casting arbitrary ints to enums is undefined behaviour, so use 'int' typedefs typedef int InterfaceId; typedef int ComponentTypeId; typedef int MessageTypeId; private: // Component allocation types typedef IComponent* (*AllocFunc)(const ScriptInterface& scriptInterface, JS::HandleValue ctor); typedef void (*DeallocFunc)(IComponent*); // ComponentTypes come in three types: // Native: normal C++ component // ScriptWrapper: C++ component that wraps a JS component implementation // Script: a ScriptWrapper linked to a specific JS component implementation enum EComponentTypeType { CT_Native, CT_ScriptWrapper, CT_Script }; // Representation of a component type, to be used when instantiating components struct ComponentType { EComponentTypeType type; InterfaceId iid; AllocFunc alloc; DeallocFunc dealloc; std::string name; std::string schema; // RelaxNG fragment DefPersistentRooted ctor; // only valid if type == CT_Script // TODO: Constructor, move assignment operator and move constructor only have to be // explicitly defined for Visual Studio. VS2013 is still behind on C++11 support // What's missing is what they call "Rvalue references v3.0", see // https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref ComponentType() {} ComponentType (EComponentTypeType type, InterfaceId iid, AllocFunc alloc, DeallocFunc dealloc, std::string name, std::string schema, DefPersistentRooted ctor) : type(type), iid(iid), alloc(alloc), dealloc(dealloc), name(name), schema(schema), ctor(std::move(ctor)) { } ComponentType& operator= (ComponentType&& other) { type = std::move(other.type); iid = std::move(other.iid); alloc = std::move(other.alloc); dealloc = std::move(other.dealloc); name = std::move(other.name); schema = std::move(other.schema); ctor = std::move(other.ctor); return *this; } ComponentType(ComponentType&& other) { type = std::move(other.type); iid = std::move(other.iid); alloc = std::move(other.alloc); dealloc = std::move(other.dealloc); name = std::move(other.name); schema = std::move(other.schema); ctor = std::move(other.ctor); } }; public: CComponentManager(CSimContext&, shared_ptr rt, bool skipScriptFunctions = false); ~CComponentManager(); void LoadComponentTypes(); /** * Load a script and execute it in a new function scope. * @param filename VFS path to load * @param hotload set to true if this script has been loaded before, and redefinitions of * existing components should not be considered errors */ bool LoadScript(const VfsPath& filename, bool hotload = false); void RegisterMessageType(MessageTypeId mtid, const char* name); void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void MarkScriptedComponentForSystemEntity(CComponentManager::ComponentTypeId cid); /** * Subscribe the current component type to the given message type. * Each component's HandleMessage will be called on any BroadcastMessage of this message type, * or on any PostMessage of this type targeted at the component's entity. * Must only be called by a component type's ClassInit. */ void SubscribeToMessageType(MessageTypeId mtid); /** * Subscribe the current component type to all messages of the given message type. * Each component's HandleMessage will be called on any BroadcastMessage or PostMessage of this message type, * regardless of the entity. * Must only be called by a component type's ClassInit. */ void SubscribeGloballyToMessageType(MessageTypeId mtid); /** * Subscribe the given component instance to all messages of the given message type. * The component's HandleMessage will be called on any BroadcastMessage or PostMessage of * this message type, regardless of the entity. * * This can be called at any time (including inside the HandleMessage callback for this message type). * * The component type must not have statically subscribed to this message type in its ClassInit. * * The subscription status is not saved or network-synchronised. Components must remember to * resubscribe in their Deserialize methods if they still want the message. * * This is primarily intended for Interpolate and RenderSubmit messages, to avoid the cost of * sending the message to components that do not currently need to do any rendering. */ void DynamicSubscriptionNonsync(MessageTypeId mtid, IComponent* component, bool enabled); /** * @param cname Requested component type name (not including any "CID" or "CCmp" prefix) * @return The component type id, or CID__Invalid if not found */ ComponentTypeId LookupCID(const std::string& cname) const; /** * @return The name of the given component type, or "" if not found */ std::string LookupComponentTypeName(ComponentTypeId cid) const; /** * Set up an empty SYSTEM_ENTITY. Must be called after ResetState() and before GetSystemEntity(). */ void InitSystemEntity(); /** * Returns a CEntityHandle with id SYSTEM_ENTITY. */ CEntityHandle GetSystemEntity() { ASSERT(m_SystemEntity.GetId() == SYSTEM_ENTITY); return m_SystemEntity; } /** * Returns a CEntityHandle with id @p ent. * If @p allowCreate is true and there is no existing CEntityHandle, a new handle will be allocated. */ CEntityHandle LookupEntityHandle(entity_id_t ent, bool allowCreate = false); /** * Returns true if the entity with id @p ent exists. */ bool EntityExists(entity_id_t ent) const; /** * Returns a new entity ID that has never been used before. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(); /** * Returns a new local entity ID that has never been used before. * This entity will not be synchronised over the network, stored in saved games, etc. */ entity_id_t AllocateNewLocalEntity(); /** * Returns a new entity ID that has never been used before. * If possible, returns preferredId, and ensures this ID won't be allocated again. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(entity_id_t preferredId); /** * Constructs a component of type 'cid', initialised with data 'paramNode', * and attaches it to entity 'ent'. * * @return true on success; false on failure, and logs an error message */ bool AddComponent(CEntityHandle ent, ComponentTypeId cid, const CParamNode& paramNode); /** * Add all system components to the system entity (skip the scripted components or the AI components on demand) */ void AddSystemComponents(bool skipScriptedComponents, bool skipAI); /** * Adds an externally-created component, so that it is returned by QueryInterface * but does not get destroyed and does not receive messages from the component manager. * (This is intended for unit tests that need to add mock objects the tested components * expect to exist.) */ void AddMockComponent(CEntityHandle ent, InterfaceId iid, IComponent& component); /** * Allocates a component object of type 'cid', and attaches it to entity 'ent'. * (The component's Init is not called here - either Init or Deserialize must be called * before using the returned object.) */ IComponent* ConstructComponent(CEntityHandle ent, ComponentTypeId cid); /** * Constructs an entity based on the given template, and adds it the world with * entity ID @p ent. There should not be any existing components with that entity ID. * @return ent, or INVALID_ENTITY on error */ entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent); /** * Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called. * Has no effect if the entity does not exist, or has already been added to the destruction queue. */ void DestroyComponentsSoon(entity_id_t ent); /** * Does the actual destruction of components from DestroyComponentsSoon. * This must not be called if the component manager is on the call stack (since it * will break internal iterators). */ void FlushDestroyedComponents(); IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; - typedef std::pair InterfacePair; - typedef std::vector InterfaceList; - typedef boost::unordered_map InterfaceListUnordered; + using InterfacePair = std::pair; + using InterfaceList = std::vector; + using InterfaceListUnordered = std::unordered_map; InterfaceList GetEntitiesWithInterface(InterfaceId iid) const; const InterfaceListUnordered& GetEntitiesWithInterfaceUnordered(InterfaceId iid) const; /** * Send a message, targeted at a particular entity. The message will be received by any * components of that entity which subscribed to the message type, and by any other components * that subscribed globally to the message type. */ void PostMessage(entity_id_t ent, const CMessage& msg); /** * Send a message, not targeted at any particular entity. The message will be received by any * components that subscribed (either globally or not) to the message type. */ void BroadcastMessage(const CMessage& msg); /** * Resets the dynamic simulation state (deletes all entities, resets entity ID counters; * doesn't unload/reload component scripts). */ void ResetState(); /** * Initializes the random number generator with a seed determined by the host. */ void SetRNGSeed(u32 seed); // Various state serialization functions: bool ComputeStateHash(std::string& outHash, bool quick) const; bool DumpDebugState(std::ostream& stream, bool includeDebugInfo) const; // FlushDestroyedComponents must be called before SerializeState (since the destruction queue // won't get serialized) bool SerializeState(std::ostream& stream) const; bool DeserializeState(std::istream& stream); std::string GenerateSchema() const; ScriptInterface& GetScriptInterface() { return m_ScriptInterface; } private: // Implementations of functions exposed to scripts static void Script_RegisterComponentType_Common(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor, bool reRegister, bool systemComponent); static void Script_RegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_RegisterSystemComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_ReRegisterComponentType(ScriptInterface::CxPrivate* pCxPrivate, int iid, const std::string& cname, JS::HandleValue ctor); static void Script_RegisterInterface(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); static void Script_RegisterMessageType(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name); static void Script_RegisterGlobal(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name, JS::HandleValue value); static IComponent* Script_QueryInterface(ScriptInterface::CxPrivate* pCxPrivate, int ent, int iid); static std::vector Script_GetEntitiesWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid); static std::vector Script_GetComponentsWithInterface(ScriptInterface::CxPrivate* pCxPrivate, int iid); static void Script_PostMessage(ScriptInterface::CxPrivate* pCxPrivate, int ent, int mtid, JS::HandleValue data); static void Script_BroadcastMessage(ScriptInterface::CxPrivate* pCxPrivate, int mtid, JS::HandleValue data); static int Script_AddEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static int Script_AddLocalEntity(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName); static void Script_DestroyEntity(ScriptInterface::CxPrivate* pCxPrivate, int ent); static void Script_FlushDestroyedEntities(ScriptInterface::CxPrivate* pCxPrivate); CMessage* ConstructMessage(int mtid, JS::HandleValue data); void SendGlobalMessage(entity_id_t ent, const CMessage& msg); void FlattenDynamicSubscriptions(); void RemoveComponentDynamicSubscriptions(IComponent* component); ComponentTypeId GetScriptWrapper(InterfaceId iid); CEntityHandle AllocateEntityHandle(entity_id_t ent); ScriptInterface m_ScriptInterface; CSimContext& m_SimContext; CEntityHandle m_SystemEntity; ComponentTypeId m_CurrentComponent; // used when loading component types bool m_CurrentlyHotloading; // TODO: some of these should be vectors std::map m_ComponentTypesById; std::vector m_ScriptedSystemComponents; - std::vector > m_ComponentsByInterface; // indexed by InterfaceId + std::vector > m_ComponentsByInterface; // indexed by InterfaceId std::map > m_ComponentsByTypeId; std::map > m_LocalMessageSubscriptions; std::map > m_GlobalMessageSubscriptions; std::map m_ComponentTypeIdsByName; std::map m_MessageTypeIdsByName; std::map m_MessageTypeNamesById; std::map m_InterfaceIdsByName; std::map m_DynamicMessageSubscriptionsNonsync; std::map > m_DynamicMessageSubscriptionsNonsyncByComponent; std::unordered_map m_ComponentCaches; // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one, // while keeping QueryInterface and PostMessage sufficiently efficient? std::vector m_DestructionQueue; ComponentTypeId m_NextScriptComponentTypeId; entity_id_t m_NextEntityId; entity_id_t m_NextLocalEntityId; boost::rand48 m_RNG; friend class TestComponentManager; }; #endif // INCLUDED_COMPONENTMANAGER Index: ps/trunk/source/graphics/FontManager.h =================================================================== --- ps/trunk/source/graphics/FontManager.h (revision 23190) +++ ps/trunk/source/graphics/FontManager.h (revision 23191) @@ -1,41 +1,42 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2019 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_FONTMANAGER #define INCLUDED_FONTMANAGER -#include +#include "ps/CStrIntern.h" + +#include class CFont; -class CStrIntern; /** * Font manager: loads and caches bitmap fonts. */ class CFontManager { public: shared_ptr LoadFont(CStrIntern fontName); private: bool ReadFont(CFont* font, CStrIntern fontName); - typedef boost::unordered_map > FontsMap; + using FontsMap = std::unordered_map >; FontsMap m_Fonts; }; #endif // INCLUDED_FONTMANAGER Index: ps/trunk/source/graphics/MeshManager.h =================================================================== --- ps/trunk/source/graphics/MeshManager.h (revision 23190) +++ ps/trunk/source/graphics/MeshManager.h (revision 23191) @@ -1,46 +1,46 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 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_MESHMANAGER #define INCLUDED_MESHMANAGER #include "lib/file/vfs/vfs_path.h" -#include #include +#include class CModelDef; -typedef std::shared_ptr CModelDefPtr; +using CModelDefPtr = std::shared_ptr; class CColladaManager; class CMeshManager { NONCOPYABLE(CMeshManager); public: CMeshManager(CColladaManager& colladaManager); ~CMeshManager(); CModelDefPtr GetMesh(const VfsPath& pathname); private: - typedef boost::unordered_map > mesh_map; + using mesh_map = std::unordered_map >; mesh_map m_MeshMap; CColladaManager& m_ColladaManager; }; #endif Index: ps/trunk/source/graphics/ObjectBase.h =================================================================== --- ps/trunk/source/graphics/ObjectBase.h (revision 23190) +++ ps/trunk/source/graphics/ObjectBase.h (revision 23191) @@ -1,199 +1,198 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2019 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_OBJECTBASE #define INCLUDED_OBJECTBASE +#include "lib/file/vfs/vfs_path.h" +#include "ps/CStr.h" +#include "ps/CStrIntern.h" + class CModel; -class CSkeletonAnim; class CObjectManager; +class CSkeletonAnim; class CXeromyces; class XMBElement; -#include -#include -#include -#include -#include "lib/file/vfs/vfs_path.h" -#include "ps/CStr.h" -#include "ps/CStrIntern.h" - #include +#include +#include +#include +#include class CObjectBase { NONCOPYABLE(CObjectBase); public: - struct Anim { // constructor Anim() : m_Frequency(0), m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {} // name of the animation - "Idle", "Run", etc CStr m_AnimName; // ID of the animation: if not empty, something specific to sync with props. CStr m_ID = ""; int m_Frequency; // filename of the animation - manidle.psa, manrun.psa, etc VfsPath m_FileName; // animation speed, as specified in XML actor file float m_Speed; // fraction [0.0, 1.0] of the way through the animation that the interesting bit(s) // happens, or -1.0 if unspecified float m_ActionPos; float m_ActionPos2; float m_SoundPos; }; struct Prop { // constructor Prop() : m_minHeight(0.f), m_maxHeight(0.f), m_selectable(true) {} // name of the prop point to attach to - "Prop01", "Prop02", "Head", "LeftHand", etc .. CStr m_PropPointName; // name of the model file - art/actors/props/sword.xml or whatever CStrW m_ModelName; // allow the prop to ajust the height from minHeight to maxHeight relative to the main model float m_minHeight; float m_maxHeight; bool m_selectable; }; struct Samp { // identifier name of sampler in GLSL shaders CStrIntern m_SamplerName; // path to load from VfsPath m_SamplerFile; }; struct Decal { Decal() : m_SizeX(0.f), m_SizeZ(0.f), m_Angle(0.f), m_OffsetX(0.f), m_OffsetZ(0.f) {} float m_SizeX; float m_SizeZ; float m_Angle; float m_OffsetX; float m_OffsetZ; }; struct Variant { Variant() : m_Frequency(0) {} CStr m_VariantName; // lowercase name int m_Frequency; VfsPath m_ModelFilename; Decal m_Decal; VfsPath m_Particles; CStr m_Color; std::vector m_Anims; std::vector m_Props; std::vector m_Samplers; }; struct Variation { VfsPath model; Decal decal; VfsPath particles; CStr color; std::multimap props; std::multimap anims; std::multimap samplers; }; CObjectBase(CObjectManager& objectManager); // Get the variation key (indices of chosen variants from each group) // based on the selection strings std::vector CalculateVariationKey(const std::vector >& selections); // Get the final actor data, combining all selected variants const Variation BuildVariation(const std::vector& variationKey); // Get a set of selection strings that are complete enough to specify an // exact variation of the actor, using the initial selections wherever possible // and choosing randomly where a choice is necessary. std::set CalculateRandomVariation(uint32_t seed, const std::set& initialSelections); // Given a prioritized vector of selection string sets that partially specify // a variation, calculates a remaining set of selection strings such that the resulting // set merged with the initial selections fully specifies an exact variation of // the actor. The resulting selections are selected randomly, but only where a choice // is necessary (i.e. where there are multiple variants but the initial selections, // applied in priority order, fail to select one). std::set CalculateRandomRemainingSelections(uint32_t seed, const std::vector >& initialSelections); // Get a list of variant groups for this object, plus for all possible // props. Duplicated groups are removed, if several props share the same // variant names. std::vector > GetVariantGroups() const; /** * Initialise this object by loading from the given file. * Returns false on error. */ bool Load(const VfsPath& pathname); /** * Reload this object from the file that it was previously loaded from. * Returns false on error. */ bool Reload(); /** * Returns whether this object (including any possible props) * uses the given file. (This is used for hotloading.) */ bool UsesFile(const VfsPath& pathname); // filename that this was loaded from VfsPath m_Pathname; // short human-readable name CStrW m_ShortName; struct { // cast shadows from this object bool m_CastShadows; // float on top of water bool m_FloatOnWater; } m_Properties; // the material file VfsPath m_Material; private: // A low-quality RNG like rand48 causes visible non-random patterns (particularly // in large grids of the same actor with consecutive seeds, e.g. forests), // so use a better one that appears to avoid those patterns - typedef boost::mt19937 rng_t; + using rng_t = boost::mt19937; std::set CalculateRandomRemainingSelections(rng_t& rng, const std::vector >& initialSelections); std::vector< std::vector > m_VariantGroups; CObjectManager& m_ObjectManager; - boost::unordered_set m_UsedFiles; + std::unordered_set m_UsedFiles; void LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant); }; #endif Index: ps/trunk/source/graphics/ParticleManager.cpp =================================================================== --- ps/trunk/source/graphics/ParticleManager.cpp (revision 23190) +++ ps/trunk/source/graphics/ParticleManager.cpp (revision 23191) @@ -1,100 +1,102 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2019 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 "ParticleManager.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "renderer/Scene.h" +#include + static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } CParticleManager::CParticleManager() : m_CurrentTime(0.f) { RegisterFileReloadFunc(ReloadChangedFileCB, this); } CParticleManager::~CParticleManager() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } CParticleEmitterTypePtr CParticleManager::LoadEmitterType(const VfsPath& path) { - boost::unordered_map::iterator it = m_EmitterTypes.find(path); + std::unordered_map::iterator it = m_EmitterTypes.find(path); if (it != m_EmitterTypes.end()) return it->second; CParticleEmitterTypePtr emitterType(new CParticleEmitterType(path, *this)); m_EmitterTypes[path] = emitterType; return emitterType; } void CParticleManager::AddUnattachedEmitter(const CParticleEmitterPtr& emitter) { m_UnattachedEmitters.push_back(emitter); } void CParticleManager::ClearUnattachedEmitters() { m_UnattachedEmitters.clear(); } void CParticleManager::Interpolate(const float simFrameLength) { m_CurrentTime += simFrameLength; } struct EmitterHasNoParticles { bool operator()(const CParticleEmitterPtr& emitterPtr) { CParticleEmitter& emitter = *emitterPtr.get(); for (size_t i = 0; i < emitter.m_Particles.size(); ++i) { SParticle& p = emitter.m_Particles[i]; if (p.age < p.maxAge) return false; } return true; } }; void CParticleManager::RenderSubmit(SceneCollector& collector, const CFrustum& UNUSED(frustum)) { PROFILE("submit unattached particles"); // Delete any unattached emitters that have no particles left m_UnattachedEmitters.remove_if(EmitterHasNoParticles()); // TODO: should do some frustum culling for (std::list::iterator it = m_UnattachedEmitters.begin(); it != m_UnattachedEmitters.end(); ++it) collector.Submit(it->get()); } Status CParticleManager::ReloadChangedFile(const VfsPath& path) { m_EmitterTypes.erase(path); return INFO::OK; } Index: ps/trunk/source/graphics/ParticleManager.h =================================================================== --- ps/trunk/source/graphics/ParticleManager.h (revision 23190) +++ ps/trunk/source/graphics/ParticleManager.h (revision 23191) @@ -1,67 +1,67 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2019 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_PARTICLEMANAGER #define INCLUDED_PARTICLEMANAGER #include "graphics/ParticleEmitter.h" #include "graphics/ParticleEmitterType.h" #include -#include +#include class SceneCollector; class CParticleManager { public: CParticleManager(); ~CParticleManager(); CParticleEmitterTypePtr LoadEmitterType(const VfsPath& path); /** * Tell the manager to handle rendering of an emitter that is no longer * attached to a unit. */ void AddUnattachedEmitter(const CParticleEmitterPtr& emitter); /** * Delete unattached emitters if we don't wish to see them anymore (like in actor viewer) */ void ClearUnattachedEmitters(); void RenderSubmit(SceneCollector& collector, const CFrustum& frustum); void Interpolate(const float simFrameLength); float GetCurrentTime() const { return m_CurrentTime; } Status ReloadChangedFile(const VfsPath& path); /// Random number generator shared between all particle emitters. boost::mt19937 m_RNG; private: float m_CurrentTime; std::list m_UnattachedEmitters; - boost::unordered_map m_EmitterTypes; + std::unordered_map m_EmitterTypes; }; #endif // INCLUDED_PARTICLEMANAGER Index: ps/trunk/source/graphics/ShaderDefines.cpp =================================================================== --- ps/trunk/source/graphics/ShaderDefines.cpp (revision 23190) +++ ps/trunk/source/graphics/ShaderDefines.cpp (revision 23191) @@ -1,281 +1,279 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 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 "ShaderDefines.h" #include "graphics/ShaderProgram.h" +#include "lib/hash.h" #include "maths/Vector4D.h" #include "ps/ThreadUtil.h" #include -size_t hash_value(const CVector4D& v) +namespace std { - size_t hash = 0; - boost::hash_combine(hash, v.X); - boost::hash_combine(hash, v.Y); - boost::hash_combine(hash, v.Z); - boost::hash_combine(hash, v.W); - return hash; -} - -size_t hash_value(const CShaderParams::SItems& items) -{ - return items.hash; -} - -size_t hash_value(const CShaderParams::SItems& items) +template<> +struct hash { - return items.hash; + std::size_t operator()(const CVector4D& v) const + { + size_t hash = 0; + hash_combine(hash, v.X); + hash_combine(hash, v.Y); + hash_combine(hash, v.Z); + hash_combine(hash, v.W); + return hash; + } +}; } bool operator==(const CShaderParams::SItems& a, const CShaderParams::SItems& b) { return a.items == b.items; } bool operator==(const CShaderParams::SItems& a, const CShaderParams::SItems& b) { return a.items == b.items; } template struct ItemNameCmp { typedef typename CShaderParams::SItems::Item Item; typedef Item first_argument_type; typedef Item second_argument_type; bool operator()(const Item& a, const Item& b) const { return a.first < b.first; } }; template struct ItemNameGeq { typedef typename CShaderParams::SItems::Item Item; bool operator()(const Item& a, const Item& b) const { return !(b.first < a.first); } }; template typename CShaderParams::SItems* CShaderParams::GetInterned(const SItems& items) { ENSURE(ThreadUtil::IsMainThread()); // s_InternedItems is not thread-safe typename InternedItems_t::iterator it = s_InternedItems.find(items); if (it != s_InternedItems.end()) return it->second.get(); // Sanity test: the items list is meant to be sorted by name. // This is a reasonable place to verify that, since this will be called once per distinct SItems. typedef ItemNameCmp Cmp; ENSURE(std::adjacent_find(items.items.begin(), items.items.end(), std::binary_negate(Cmp())) == items.items.end()); shared_ptr ptr(new SItems(items)); s_InternedItems.insert(std::make_pair(items, ptr)); return ptr.get(); } template CShaderParams::CShaderParams() { *this = s_Empty; } template CShaderParams::CShaderParams(SItems* items) : m_Items(items) { } template CShaderParams CShaderParams::CreateEmpty() { SItems items; items.RecalcHash(); return CShaderParams(GetInterned(items)); } template void CShaderParams::Set(CStrIntern name, const value_t& value) { SItems items = *m_Items; typename SItems::Item addedItem = std::make_pair(name, value); // Add the new item in a way that preserves the sortedness and uniqueness of item names for (typename std::vector::iterator it = items.items.begin(); ; ++it) { if (it == items.items.end() || addedItem.first < it->first) { items.items.insert(it, addedItem); break; } else if (addedItem.first == it->first) { it->second = addedItem.second; break; } } items.RecalcHash(); m_Items = GetInterned(items); } template void CShaderParams::SetMany(const CShaderParams& params) { SItems items; // set_union merges the two sorted lists into a new sorted list; // if two items are equivalent (i.e. equal names, possibly different values) // then the one from the first list is kept std::set_union( params.m_Items->items.begin(), params.m_Items->items.end(), m_Items->items.begin(), m_Items->items.end(), std::inserter(items.items, items.items.begin()), ItemNameCmp()); items.RecalcHash(); m_Items = GetInterned(items); } template std::map CShaderParams::GetMap() const { std::map ret; for (size_t i = 0; i < m_Items->items.size(); ++i) ret[m_Items->items[i].first] = m_Items->items[i].second; return ret; } template size_t CShaderParams::GetHash() const { return m_Items->hash; } template void CShaderParams::SItems::RecalcHash() { size_t h = 0; for (size_t i = 0; i < items.size(); ++i) { - boost::hash_combine(h, items[i].first); - boost::hash_combine(h, items[i].second); + hash_combine(h, items[i].first); + hash_combine(h, items[i].second); } hash = h; } void CShaderDefines::Add(CStrIntern name, CStrIntern value) { Set(name, value); } int CShaderDefines::GetInt(const char* name) const { CStrIntern nameIntern(name); for (size_t i = 0; i < m_Items->items.size(); ++i) { if (m_Items->items[i].first == nameIntern) { int ret; std::stringstream str(m_Items->items[i].second.c_str()); str >> ret; return ret; } } return 0; } void CShaderUniforms::Add(const char* name, const CVector4D& value) { Set(CStrIntern(name), value); } CVector4D CShaderUniforms::GetVector(const char* name) const { CStrIntern nameIntern(name); for (size_t i = 0; i < m_Items->items.size(); ++i) { if (m_Items->items[i].first == nameIntern) { return m_Items->items[i].second; } } return CVector4D(); } void CShaderUniforms::BindUniforms(const CShaderProgramPtr& shader) const { const std::vector& items = m_Items->items; for (size_t i = 0; i < items.size(); ++i) { CShaderProgram::Binding binding = shader->GetUniformBinding(items[i].first); if (binding.Active()) { CVector4D v = items[i].second; shader->Uniform(binding, v.X, v.Y, v.Z, v.W); } } } void CShaderRenderQueries::Add(const char* name) { if (name == CStr("sim_time")) { m_Items.emplace_back(RQUERY_TIME, CStrIntern(name)); } else if (name == CStr("water_tex")) { m_Items.emplace_back(RQUERY_WATER_TEX, CStrIntern(name)); } else if (name == CStr("sky_cube")) { m_Items.emplace_back(RQUERY_SKY_CUBE, CStrIntern(name)); } } void CShaderConditionalDefines::Add(const char* defname, const char* defvalue, int type, std::vector &args) { CondDefine cd; cd.m_DefName = CStrIntern(defname); cd.m_DefValue = CStrIntern(defvalue); cd.m_CondArgs = args; cd.m_CondType = type; m_Defines.push_back(cd); } // Explicit instantiations: template<> CShaderParams::InternedItems_t CShaderParams::s_InternedItems = CShaderParams::InternedItems_t(); template<> CShaderParams::InternedItems_t CShaderParams::s_InternedItems = CShaderParams::InternedItems_t(); template<> CShaderParams CShaderParams::s_Empty = CShaderParams::CreateEmpty(); template<> CShaderParams CShaderParams::s_Empty = CShaderParams::CreateEmpty(); template class CShaderParams; template class CShaderParams; Index: ps/trunk/source/graphics/ShaderDefines.h =================================================================== --- ps/trunk/source/graphics/ShaderDefines.h (revision 23190) +++ ps/trunk/source/graphics/ShaderDefines.h (revision 23191) @@ -1,225 +1,233 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 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_SHADERDEFINES #define INCLUDED_SHADERDEFINES #include "graphics/ShaderProgramPtr.h" #include "ps/CStr.h" #include "ps/CStrIntern.h" -#include #include +#include class CVector4D; /** * Represents a mapping of name strings to value, for use with * CShaderDefines (values are strings) and CShaderUniforms (values are vec4s). * * Stored as interned vectors of name-value pairs, to support high performance * comparison operators. * * Not thread-safe - must only be used from the main thread. */ template class CShaderParams { public: /** * Create an empty map of defines. */ CShaderParams(); /** * Add a name and associated value to the map of parameters. * If the name is already defined, its value will be replaced. */ void Set(CStrIntern name, const value_t& value); /** * Add all the names and values from another set of parameters. * If any name is already defined in this object, its value will be replaced. */ void SetMany(const CShaderParams& params); /** * Return a copy of the current name/value mapping. */ std::map GetMap() const; /** * Return a hash of the current mapping. */ size_t GetHash() const; /** * Compare with some arbitrary total order. * The order may be different each time the application is run * (it is based on interned memory addresses). */ bool operator<(const CShaderParams& b) const { return m_Items < b.m_Items; } /** * Fast equality comparison. */ bool operator==(const CShaderParams& b) const { return m_Items == b.m_Items; } /** * Fast inequality comparison. */ bool operator!=(const CShaderParams& b) const { return m_Items != b.m_Items; } struct SItems { // Name/value pair - typedef std::pair Item; + using Item = std::pair; // Sorted by name; no duplicated names std::vector items; size_t hash; void RecalcHash(); }; + struct SItemsHash + { + std::size_t operator()(const SItems& items) const + { + return items.hash; + } + }; + protected: SItems* m_Items; // interned value private: - typedef boost::unordered_map > InternedItems_t; + using InternedItems_t = std::unordered_map, SItemsHash >; static InternedItems_t s_InternedItems; /** * Returns a pointer to an SItems equal to @p items. * The pointer will be valid forever, and the same pointer will be returned * for any subsequent requests for an equal items list. */ static SItems* GetInterned(const SItems& items); CShaderParams(SItems* items); static CShaderParams CreateEmpty(); static CShaderParams s_Empty; }; /** * Represents a mapping of name strings to value strings, for use with * \#if and \#ifdef and similar conditionals in shaders. * * Not thread-safe - must only be used from the main thread. */ class CShaderDefines : public CShaderParams { public: /** * Add a name and associated value to the map of defines. * If the name is already defined, its value will be replaced. */ void Add(CStrIntern name, CStrIntern value); /** * Return the value for the given name as an integer, or 0 if not defined. */ int GetInt(const char* name) const; }; /** * Represents a mapping of name strings to value CVector4Ds, for use with * uniforms in shaders. * * Not thread-safe - must only be used from the main thread. */ class CShaderUniforms : public CShaderParams { public: /** * Add a name and associated value to the map of uniforms. * If the name is already defined, its value will be replaced. */ void Add(const char* name, const CVector4D& value); /** * Return the value for the given name, or (0,0,0,0) if not defined. */ CVector4D GetVector(const char* name) const; /** * Bind the collection of uniforms onto the given shader. */ void BindUniforms(const CShaderProgramPtr& shader) const; }; // Add here the types of queries we can make in the renderer enum RENDER_QUERIES { RQUERY_TIME, RQUERY_WATER_TEX, RQUERY_SKY_CUBE }; /** * Uniform values that need to be evaluated in the renderer. * * Not thread-safe - must only be used from the main thread. */ class CShaderRenderQueries { public: - typedef std::pair RenderQuery; + using RenderQuery = std::pair; void Add(const char* name); size_t GetSize() const { return m_Items.size(); } RenderQuery GetItem(size_t i) const { return m_Items[i]; } private: std::vector m_Items; }; enum DEFINE_CONDITION_TYPES { DCOND_DISTANCE }; class CShaderConditionalDefines { public: struct CondDefine { CStrIntern m_DefName; CStrIntern m_DefValue; int m_CondType; std::vector m_CondArgs; }; void Add(const char* defname, const char* defvalue, int type, std::vector &args); size_t GetSize() const { return m_Defines.size(); } const CondDefine& GetItem(size_t i) const { return m_Defines[i]; } private: std::vector m_Defines; }; #endif // INCLUDED_SHADERDEFINES Index: ps/trunk/source/graphics/ShaderManager.cpp =================================================================== --- ps/trunk/source/graphics/ShaderManager.cpp (revision 23190) +++ ps/trunk/source/graphics/ShaderManager.cpp (revision 23191) @@ -1,581 +1,582 @@ /* Copyright (C) 2019 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 "ShaderManager.h" #include "graphics/ShaderTechnique.h" #include "lib/config2.h" +#include "lib/hash.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/CStrIntern.h" #include "ps/Filesystem.h" #include "ps/PreprocessorWrapper.h" #include "ps/Profile.h" #if USE_SHADER_XML_VALIDATION # include "ps/XML/RelaxNG.h" #endif #include "ps/XML/Xeromyces.h" #include "ps/XML/XMLWriter.h" #include "renderer/Renderer.h" TIMER_ADD_CLIENT(tc_ShaderValidation); CShaderManager::CShaderManager() { #if USE_SHADER_XML_VALIDATION { TIMER_ACCRUE(tc_ShaderValidation); if (!CXeromyces::AddValidator(g_VFS, "shader", "shaders/program.rng")) LOGERROR("CShaderManager: failed to load grammar shaders/program.rng"); } #endif // Allow hotloading of textures RegisterFileReloadFunc(ReloadChangedFileCB, this); } CShaderManager::~CShaderManager() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } CShaderProgramPtr CShaderManager::LoadProgram(const char* name, const CShaderDefines& defines) { CacheKey key = { name, defines }; std::map::iterator it = m_ProgramCache.find(key); if (it != m_ProgramCache.end()) return it->second; CShaderProgramPtr program; if (!NewProgram(name, defines, program)) { LOGERROR("Failed to load shader '%s'", name); program = CShaderProgramPtr(); } m_ProgramCache[key] = program; return program; } static GLenum ParseAttribSemantics(const CStr& str) { // Map known semantics onto the attribute locations documented by NVIDIA if (str == "gl_Vertex") return 0; if (str == "gl_Normal") return 2; if (str == "gl_Color") return 3; if (str == "gl_SecondaryColor") return 4; if (str == "gl_FogCoord") return 5; if (str == "gl_MultiTexCoord0") return 8; if (str == "gl_MultiTexCoord1") return 9; if (str == "gl_MultiTexCoord2") return 10; if (str == "gl_MultiTexCoord3") return 11; if (str == "gl_MultiTexCoord4") return 12; if (str == "gl_MultiTexCoord5") return 13; if (str == "gl_MultiTexCoord6") return 14; if (str == "gl_MultiTexCoord7") return 15; // Define some arbitrary names for user-defined attribute locations // that won't conflict with any standard semantics if (str == "CustomAttribute0") return 1; if (str == "CustomAttribute1") return 6; if (str == "CustomAttribute2") return 7; debug_warn("Invalid attribute semantics"); return 0; } bool CShaderManager::NewProgram(const char* name, const CShaderDefines& baseDefines, CShaderProgramPtr& program) { PROFILE2("loading shader"); PROFILE2_ATTR("name: %s", name); if (strncmp(name, "fixed:", 6) == 0) { program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines)); if (!program) return false; program->Reload(); return true; } VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return false; #if USE_SHADER_XML_VALIDATION { TIMER_ACCRUE(tc_ShaderValidation); // Serialize the XMB data and pass it to the validator XMLWriter_File shaderFile; shaderFile.SetPrettyPrint(false); shaderFile.XMB(XeroFile); bool ok = CXeromyces::ValidateEncoded("shader", wstring_from_utf8(name), shaderFile.GetOutput()); if (!ok) return false; } #endif // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(attrib); EL(define); EL(fragment); EL(stream); EL(uniform); EL(vertex); AT(file); AT(if); AT(loc); AT(name); AT(semantics); AT(type); AT(value); #undef AT #undef EL CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement Root = XeroFile.GetRoot(); bool isGLSL = (Root.GetAttributes().GetNamedItem(at_type) == "glsl"); VfsPath vertexFile; VfsPath fragmentFile; CShaderDefines defines = baseDefines; std::map vertexUniforms; std::map fragmentUniforms; std::map vertexAttribs; int streamFlags = 0; XERO_ITER_EL(Root, Child) { if (Child.GetNodeName() == el_define) { defines.Add(CStrIntern(Child.GetAttributes().GetNamedItem(at_name)), CStrIntern(Child.GetAttributes().GetNamedItem(at_value))); } else if (Child.GetNodeName() == el_vertex) { vertexFile = L"shaders/" + Child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(Child, Param) { XMBAttributeList Attrs = Param.GetAttributes(); CStr cond = Attrs.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (Param.GetNodeName() == el_uniform) { vertexUniforms[CStrIntern(Attrs.GetNamedItem(at_name))] = Attrs.GetNamedItem(at_loc).ToInt(); } else if (Param.GetNodeName() == el_stream) { CStr StreamName = Attrs.GetNamedItem(at_name); if (StreamName == "pos") streamFlags |= STREAM_POS; else if (StreamName == "normal") streamFlags |= STREAM_NORMAL; else if (StreamName == "color") streamFlags |= STREAM_COLOR; else if (StreamName == "uv0") streamFlags |= STREAM_UV0; else if (StreamName == "uv1") streamFlags |= STREAM_UV1; else if (StreamName == "uv2") streamFlags |= STREAM_UV2; else if (StreamName == "uv3") streamFlags |= STREAM_UV3; } else if (Param.GetNodeName() == el_attrib) { int attribLoc = ParseAttribSemantics(Attrs.GetNamedItem(at_semantics)); vertexAttribs[CStrIntern(Attrs.GetNamedItem(at_name))] = attribLoc; } } } else if (Child.GetNodeName() == el_fragment) { fragmentFile = L"shaders/" + Child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(Child, Param) { XMBAttributeList Attrs = Param.GetAttributes(); CStr cond = Attrs.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (Param.GetNodeName() == el_uniform) { // A somewhat incomplete listing, missing "shadow" and "rect" versions // which are interpreted as 2D (NB: our shadowmaps may change // type based on user config). GLenum type = GL_TEXTURE_2D; CStr t = Attrs.GetNamedItem(at_type); if (t == "sampler1D") #if CONFIG2_GLES debug_warn(L"sampler1D not implemented on GLES"); #else type = GL_TEXTURE_1D; #endif else if (t == "sampler2D") type = GL_TEXTURE_2D; else if (t == "sampler3D") #if CONFIG2_GLES debug_warn(L"sampler3D not implemented on GLES"); #else type = GL_TEXTURE_3D; #endif else if (t == "samplerCube") type = GL_TEXTURE_CUBE_MAP; fragmentUniforms[CStrIntern(Attrs.GetNamedItem(at_name))] = std::make_pair(Attrs.GetNamedItem(at_loc).ToInt(), type); } } } } if (isGLSL) program = CShaderProgramPtr(CShaderProgram::ConstructGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamFlags)); else program = CShaderProgramPtr(CShaderProgram::ConstructARB(vertexFile, fragmentFile, defines, vertexUniforms, fragmentUniforms, streamFlags)); program->Reload(); // m_HotloadFiles[xmlFilename].insert(program); // TODO: should reload somehow when the XML changes m_HotloadFiles[vertexFile].insert(program); m_HotloadFiles[fragmentFile].insert(program); return true; } static GLenum ParseComparisonFunc(const CStr& str) { if (str == "never") return GL_NEVER; if (str == "always") return GL_ALWAYS; if (str == "less") return GL_LESS; if (str == "lequal") return GL_LEQUAL; if (str == "equal") return GL_EQUAL; if (str == "gequal") return GL_GEQUAL; if (str == "greater") return GL_GREATER; if (str == "notequal") return GL_NOTEQUAL; debug_warn("Invalid comparison func"); return GL_ALWAYS; } static GLenum ParseBlendFunc(const CStr& str) { if (str == "zero") return GL_ZERO; if (str == "one") return GL_ONE; if (str == "src_color") return GL_SRC_COLOR; if (str == "one_minus_src_color") return GL_ONE_MINUS_SRC_COLOR; if (str == "dst_color") return GL_DST_COLOR; if (str == "one_minus_dst_color") return GL_ONE_MINUS_DST_COLOR; if (str == "src_alpha") return GL_SRC_ALPHA; if (str == "one_minus_src_alpha") return GL_ONE_MINUS_SRC_ALPHA; if (str == "dst_alpha") return GL_DST_ALPHA; if (str == "one_minus_dst_alpha") return GL_ONE_MINUS_DST_ALPHA; if (str == "constant_color") return GL_CONSTANT_COLOR; if (str == "one_minus_constant_color") return GL_ONE_MINUS_CONSTANT_COLOR; if (str == "constant_alpha") return GL_CONSTANT_ALPHA; if (str == "one_minus_constant_alpha") return GL_ONE_MINUS_CONSTANT_ALPHA; if (str == "src_alpha_saturate") return GL_SRC_ALPHA_SATURATE; debug_warn("Invalid blend func"); return GL_ZERO; } size_t CShaderManager::EffectCacheKeyHash::operator()(const EffectCacheKey& key) const { size_t hash = 0; - boost::hash_combine(hash, key.name.GetHash()); - boost::hash_combine(hash, key.defines1.GetHash()); - boost::hash_combine(hash, key.defines2.GetHash()); + hash_combine(hash, key.name.GetHash()); + hash_combine(hash, key.defines1.GetHash()); + hash_combine(hash, key.defines2.GetHash()); return hash; } bool CShaderManager::EffectCacheKey::operator==(const EffectCacheKey& b) const { return (name == b.name && defines1 == b.defines1 && defines2 == b.defines2); } CShaderTechniquePtr CShaderManager::LoadEffect(CStrIntern name) { return LoadEffect(name, g_Renderer.GetSystemShaderDefines(), CShaderDefines()); } CShaderTechniquePtr CShaderManager::LoadEffect(CStrIntern name, const CShaderDefines& defines1, const CShaderDefines& defines2) { // Return the cached effect, if there is one EffectCacheKey key = { name, defines1, defines2 }; EffectCacheMap::iterator it = m_EffectCache.find(key); if (it != m_EffectCache.end()) return it->second; // First time we've seen this key, so construct a new effect: // Merge the two sets of defines, so NewEffect doesn't have to care about the split CShaderDefines defines(defines1); defines.SetMany(defines2); CShaderTechniquePtr tech(new CShaderTechnique()); if (!NewEffect(name.c_str(), defines, tech)) { LOGERROR("Failed to load effect '%s'", name.c_str()); tech = CShaderTechniquePtr(); } m_EffectCache[key] = tech; return tech; } bool CShaderManager::NewEffect(const char* name, const CShaderDefines& baseDefines, CShaderTechniquePtr& tech) { PROFILE2("loading effect"); PROFILE2_ATTR("name: %s", name); // Shortcut syntax for effects that just contain a single shader if (strncmp(name, "shader:", 7) == 0) { CShaderProgramPtr program = LoadProgram(name+7, baseDefines); if (!program) return false; CShaderPass pass; pass.SetShader(program); tech->AddPass(pass); return true; } VfsPath xmlFilename = L"shaders/effects/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return false; // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(alpha); EL(blend); EL(define); EL(depth); EL(pass); EL(require); EL(sort_by_distance); AT(context); AT(dst); AT(func); AT(ref); AT(shader); AT(shaders); AT(src); AT(mask); AT(name); AT(value); #undef AT #undef EL // Read some defines that influence how we pick techniques bool hasARB = (baseDefines.GetInt("SYS_HAS_ARB") != 0); bool hasGLSL = (baseDefines.GetInt("SYS_HAS_GLSL") != 0); bool preferGLSL = (baseDefines.GetInt("SYS_PREFER_GLSL") != 0); // Prepare the preprocessor for conditional tests CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement Root = XeroFile.GetRoot(); // Find all the techniques that we can use, and their preference std::vector > usableTechs; XERO_ITER_EL(Root, Technique) { int preference = 0; bool isUsable = true; XERO_ITER_EL(Technique, Child) { XMBAttributeList Attrs = Child.GetAttributes(); if (Child.GetNodeName() == el_require) { if (Attrs.GetNamedItem(at_shaders) == "fixed") { // FFP not supported by OpenGL ES #if CONFIG2_GLES isUsable = false; #endif } else if (Attrs.GetNamedItem(at_shaders) == "arb") { if (!hasARB) isUsable = false; } else if (Attrs.GetNamedItem(at_shaders) == "glsl") { if (!hasGLSL) isUsable = false; if (preferGLSL) preference += 100; else preference -= 100; } else if (!Attrs.GetNamedItem(at_context).empty()) { CStr cond = Attrs.GetNamedItem(at_context); if (!preprocessor.TestConditional(cond)) isUsable = false; } } } if (isUsable) usableTechs.emplace_back(Technique, preference); } if (usableTechs.empty()) { debug_warn(L"Can't find a usable technique"); return false; } // Sort by preference, tie-break on order of specification std::stable_sort(usableTechs.begin(), usableTechs.end(), [](const std::pair& a, const std::pair& b) { return b.second < a.second; }); CShaderDefines techDefines = baseDefines; XERO_ITER_EL(usableTechs[0].first, Child) { if (Child.GetNodeName() == el_define) { techDefines.Add(CStrIntern(Child.GetAttributes().GetNamedItem(at_name)), CStrIntern(Child.GetAttributes().GetNamedItem(at_value))); } else if (Child.GetNodeName() == el_sort_by_distance) { tech->SetSortByDistance(true); } else if (Child.GetNodeName() == el_pass) { CShaderDefines passDefines = techDefines; CShaderPass pass; XERO_ITER_EL(Child, Element) { if (Element.GetNodeName() == el_define) { passDefines.Add(CStrIntern(Element.GetAttributes().GetNamedItem(at_name)), CStrIntern(Element.GetAttributes().GetNamedItem(at_value))); } else if (Element.GetNodeName() == el_alpha) { GLenum func = ParseComparisonFunc(Element.GetAttributes().GetNamedItem(at_func)); float ref = Element.GetAttributes().GetNamedItem(at_ref).ToFloat(); pass.AlphaFunc(func, ref); } else if (Element.GetNodeName() == el_blend) { GLenum src = ParseBlendFunc(Element.GetAttributes().GetNamedItem(at_src)); GLenum dst = ParseBlendFunc(Element.GetAttributes().GetNamedItem(at_dst)); pass.BlendFunc(src, dst); } else if (Element.GetNodeName() == el_depth) { if (!Element.GetAttributes().GetNamedItem(at_func).empty()) pass.DepthFunc(ParseComparisonFunc(Element.GetAttributes().GetNamedItem(at_func))); if (!Element.GetAttributes().GetNamedItem(at_mask).empty()) pass.DepthMask(Element.GetAttributes().GetNamedItem(at_mask) == "true" ? 1 : 0); } } // Load the shader program after we've read all the possibly-relevant s pass.SetShader(LoadProgram(Child.GetAttributes().GetNamedItem(at_shader).c_str(), passDefines)); tech->AddPass(pass); } } return true; } size_t CShaderManager::GetNumEffectsLoaded() { return m_EffectCache.size(); } /*static*/ Status CShaderManager::ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } Status CShaderManager::ReloadChangedFile(const VfsPath& path) { // Find all shaders using this file HotloadFilesMap::iterator files = m_HotloadFiles.find(path); if (files != m_HotloadFiles.end()) { // Reload all shaders using this file for (std::set >::iterator it = files->second.begin(); it != files->second.end(); ++it) { if (std::shared_ptr program = it->lock()) program->Reload(); } } // TODO: hotloading changes to shader XML files and effect XML files would be nice return INFO::OK; } Index: ps/trunk/source/graphics/ShaderManager.h =================================================================== --- ps/trunk/source/graphics/ShaderManager.h (revision 23190) +++ ps/trunk/source/graphics/ShaderManager.h (revision 23191) @@ -1,131 +1,130 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 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_SHADERMANAGER #define INCLUDED_SHADERMANAGER #define USE_SHADER_XML_VALIDATION 1 -#include -#include -#include - #include "graphics/ShaderDefines.h" #include "graphics/ShaderProgram.h" #include "graphics/ShaderTechnique.h" #if USE_SHADER_XML_VALIDATION # include "ps/XML/RelaxNG.h" #endif +#include +#include +#include /** * Shader manager: loads and caches shader programs. * * For a high-level overview of shaders and materials, see * http://trac.wildfiregames.com/wiki/MaterialSystem */ class CShaderManager { public: CShaderManager(); ~CShaderManager(); /** * Load a shader program. * @param name name of shader XML specification (file is loaded from shaders/${name}.xml) * @param defines key/value set of preprocessor definitions * @return loaded program, or null pointer on error */ CShaderProgramPtr LoadProgram(const char* name, const CShaderDefines& defines); /** * Load a shader effect. * Effects can be implemented via many techniques; this returns the best usable technique. * @param name name of effect XML specification (file is loaded from shaders/effects/${name}.xml) * @param defines1,defines2 key/value set of preprocessor definitions; defines2 has higher precedence * @return loaded technique, or empty technique on error */ CShaderTechniquePtr LoadEffect(CStrIntern name, const CShaderDefines& defines1, const CShaderDefines& defines2); /** * Load a shader effect, with default system defines (from CRenderer::GetSystemShaderDefines). */ CShaderTechniquePtr LoadEffect(CStrIntern name); /** * Returns the number of shader effects that are currently loaded. */ size_t GetNumEffectsLoaded(); private: struct CacheKey { std::string name; CShaderDefines defines; bool operator<(const CacheKey& k) const { if (name < k.name) return true; if (k.name < name) return false; return defines < k.defines; } }; // A CShaderProgram contains expensive GL state, so we ought to cache it. // The compiled state depends solely on the filename and list of defines, // so we store that in CacheKey. // TODO: is this cache useful when we already have an effect cache? std::map m_ProgramCache; /** * Key for effect cache lookups. * This stores two separate CShaderDefines because the renderer typically * has one set from the rendering context and one set from the material; * by handling both separately here, we avoid the cost of having to merge * the two sets into a single one before doing the cache lookup. */ struct EffectCacheKey { CStrIntern name; CShaderDefines defines1; CShaderDefines defines2; bool operator==(const EffectCacheKey& b) const; }; struct EffectCacheKeyHash { size_t operator()(const EffectCacheKey& key) const; }; - typedef boost::unordered_map EffectCacheMap; + using EffectCacheMap = std::unordered_map; EffectCacheMap m_EffectCache; // Store the set of shaders that need to be reloaded when the given file is modified - typedef boost::unordered_map, std::owner_less>>> HotloadFilesMap; + using HotloadFilesMap = std::unordered_map, std::owner_less > > >; HotloadFilesMap m_HotloadFiles; bool NewProgram(const char* name, const CShaderDefines& defines, CShaderProgramPtr& program); bool NewEffect(const char* name, const CShaderDefines& defines, CShaderTechniquePtr& tech); static Status ReloadChangedFileCB(void* param, const VfsPath& path); Status ReloadChangedFile(const VfsPath& path); }; #endif // INCLUDED_SHADERMANAGER Index: ps/trunk/source/graphics/SkeletonAnimManager.cpp =================================================================== --- ps/trunk/source/graphics/SkeletonAnimManager.cpp (revision 23190) +++ ps/trunk/source/graphics/SkeletonAnimManager.cpp (revision 23191) @@ -1,91 +1,92 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2019 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 . */ /* * Owner of all skeleton animations */ #include "precompiled.h" #include "SkeletonAnimManager.h" #include "graphics/ColladaManager.h" #include "graphics/Model.h" #include "graphics/SkeletonAnimDef.h" #include "ps/CLogger.h" #include "ps/FileIo.h" +#include /////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimManager constructor CSkeletonAnimManager::CSkeletonAnimManager(CColladaManager& colladaManager) : m_ColladaManager(colladaManager) { } /////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimManager destructor CSkeletonAnimManager::~CSkeletonAnimManager() { - typedef boost::unordered_map::iterator Iter; + using Iter = std::unordered_map::iterator; for (Iter i = m_Animations.begin(); i != m_Animations.end(); ++i) delete i->second; } /////////////////////////////////////////////////////////////////////////////// // GetAnimation: return a given animation by filename; return null if filename // doesn't refer to valid animation file CSkeletonAnimDef* CSkeletonAnimManager::GetAnimation(const VfsPath& pathname) { VfsPath name = pathname.ChangeExtension(L""); // Find if it's already been loaded - boost::unordered_map::iterator iter = m_Animations.find(name); + std::unordered_map::iterator iter = m_Animations.find(name); if (iter != m_Animations.end()) return iter->second; CSkeletonAnimDef* def = NULL; // Find the file to load VfsPath psaFilename = m_ColladaManager.GetLoadablePath(name, CColladaManager::PSA); if (psaFilename.empty()) { LOGERROR("Could not load animation '%s'", pathname.string8()); def = NULL; } else { try { def = CSkeletonAnimDef::Load(psaFilename); } catch (PSERROR_File&) { LOGERROR("Could not load animation '%s'", psaFilename.string8()); } } if (def) LOGMESSAGE("CSkeletonAnimManager::GetAnimation(%s): Loaded successfully", pathname.string8()); else LOGERROR("CSkeletonAnimManager::GetAnimation(%s): Failed loading, marked file as bad", pathname.string8()); // Add to map m_Animations[name] = def; // NULL if failed to load - we won't try loading it again return def; } Index: ps/trunk/source/graphics/SkeletonAnimManager.h =================================================================== --- ps/trunk/source/graphics/SkeletonAnimManager.h (revision 23190) +++ ps/trunk/source/graphics/SkeletonAnimManager.h (revision 23191) @@ -1,56 +1,57 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2019 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 . */ /* * Owner of all skeleton animations */ #ifndef INCLUDED_SKELETONANIMMANAGER #define INCLUDED_SKELETONANIMMANAGER +#include "lib/file/vfs/vfs_path.h" + #include #include -#include "lib/file/vfs/vfs_path.h" -#include +#include class CColladaManager; class CSkeletonAnimDef; class CStr8; /////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimManager : owner class of all skeleton anims - manages creation, // loading and destruction of animation data class CSkeletonAnimManager { NONCOPYABLE(CSkeletonAnimManager); public: // constructor, destructor CSkeletonAnimManager(CColladaManager& colladaManager); ~CSkeletonAnimManager(); // return a given animation by filename; return null if filename doesn't // refer to valid animation file CSkeletonAnimDef* GetAnimation(const VfsPath& pathname); private: // map of all known animations. Value is NULL if it failed to load. - boost::unordered_map m_Animations; + std::unordered_map m_Animations; CColladaManager& m_ColladaManager; }; #endif Index: ps/trunk/source/graphics/TextureManager.cpp =================================================================== --- ps/trunk/source/graphics/TextureManager.cpp (revision 23190) +++ ps/trunk/source/graphics/TextureManager.cpp (revision 23191) @@ -1,684 +1,678 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 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 -#include -#include -#include - #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/hash.h" #include "lib/res/graphics/ogl_tex.h" +#include "lib/res/h_mgr.h" #include "lib/timer.h" #include "maths/MD5.h" #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" +#include +#include +#include + struct TPhash - : std::unary_function, - std::unary_function { std::size_t operator()(CTextureProperties const& a) const { std::size_t seed = 0; - boost::hash_combine(seed, a.m_Path); - boost::hash_combine(seed, a.m_Filter); - boost::hash_combine(seed, a.m_WrapS); - boost::hash_combine(seed, a.m_WrapT); - boost::hash_combine(seed, a.m_Aniso); - boost::hash_combine(seed, a.m_Format); + hash_combine(seed, m_PathHash(a.m_Path)); + hash_combine(seed, a.m_Filter); + hash_combine(seed, a.m_WrapS); + hash_combine(seed, a.m_WrapT); + hash_combine(seed, a.m_Aniso); + hash_combine(seed, a.m_Format); return seed; } + std::size_t operator()(CTexturePtr const& a) const { return (*this)(a->m_Properties); } + +private: + std::hash m_PathHash; }; struct TPequal_to : std::binary_function, std::binary_function { bool operator()(CTextureProperties const& a, CTextureProperties const& b) const { return a.m_Path == b.m_Path && a.m_Filter == b.m_Filter && a.m_WrapS == b.m_WrapS && a.m_WrapT == b.m_WrapT && a.m_Aniso == b.m_Aniso && a.m_Format == b.m_Format; } bool operator()(CTexturePtr const& a, CTexturePtr const& b) const { return (*this)(a->m_Properties, b->m_Properties); } }; -std::size_t hash_value(const CTexturePtr& v) -{ - TPhash h; - return h(v); -} -std::size_t hash_value(const CTextureProperties& v) -{ - TPhash h; - return h(v); -} - class CTextureManagerImpl { friend class CTexture; public: CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) : m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL), m_TextureConverter(vfs, highQuality), m_DefaultHandle(0), m_ErrorHandle(0) { // Initialise some textures that will always be available, // without needing to load any files // Default placeholder texture (grey) if (!m_DisableGL) { // 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)t.wrap(1, 1, 24, 0, data, 0); 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) if (!m_DisableGL) { // 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)t.wrap(1, 1, 24, 0, data, 0); 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) { if (m_DisableGL) return; PROFILE2("load texture"); PROFILE2_ATTR("name: %ls", path.string().c_str()); Handle h = ogl_tex_load(m_VFS, path, RES_UNIQUE); if (h <= 0) { LOGERROR("Texture failed to load; \"%s\"", texture->m_Properties.m_Path.string8()); // 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 color from the texture (void)ogl_tex_get_average_color(h, &texture->m_BaseColor); // Set GL upload properties (void)ogl_tex_set_wrap(h, texture->m_Properties.m_WrapS, texture->m_Properties.m_WrapT); (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, texture->m_Properties.m_Format) < 0) { LOGERROR("Texture failed to upload: \"%s\"", texture->m_Properties.m_Path.string8()); 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); } /** * Set up some parameters for the loose cache filename code. */ void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version) { // Hash the settings, so we won't use an old loose cache file if the // settings have changed CTextureConverter::Settings settings = GetConverterSettings(texture); settings.Hash(hash); // Arbitrary version number - change this if we update the code and // need to invalidate old users' caches version = 1; } /** * 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) { MD5 hash; u32 version; PrepareCacheKey(texture, hash, version); VfsPath loadPath; Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath); if (ret == INFO::OK) { // Found a cached texture - load it LoadTexture(texture, loadPath); return true; } else if (ret == INFO::SKIPPED) { // No cached version was found - we'll need to create it return false; } else { ENSURE(ret < 0); // No source file or archive cache was found, so we can't load the // real texture at all - return the error texture instead LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8()); texture->SetHandle(m_ErrorHandle); return true; } } /** * 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; PROFILE2("convert texture"); PROFILE2_ATTR("name: %ls", sourcePath.string().c_str()); MD5 hash; u32 version; PrepareCacheKey(texture, hash, version); VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version); // LOGWARNING("Converting texture \"%s\"", srcPath.c_str()); CTextureConverter::Settings settings = GetConverterSettings(texture); m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings); } bool TextureExists(const VfsPath& path) const { return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK || m_VFS->GetFileInfo(path, 0) == INFO::OK; } bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath) { archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath); CTextureProperties textureProps(sourcePath); CTexturePtr texture = CreateTexture(textureProps); CTextureConverter::Settings settings = GetConverterSettings(texture); if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings)) return false; while (true) { CTexturePtr textureOut; VfsPath dest; bool ok; if (m_TextureConverter.Poll(textureOut, dest, ok)) return ok; std::this_thread::sleep_for(std::chrono::microseconds(1)); } } 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("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8()); 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) { fs::wpath srcPath = texture->m_Properties.m_Path.string(); std::vector files; VfsPath p; for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it) { VfsPath settingsPath = p / "textures.xml"; m_HotloadFiles[settingsPath].insert(texture); CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath); if (f) files.push_back(f); p = p / GetWstringFromWpath(*it); } return m_TextureConverter.ComputeSettings(GetWstringFromWpath(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 Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } Status ReloadChangedFile(const VfsPath& path) { // Uncache settings file, if this is one m_SettingsFiles.erase(path); // Find all textures using this file HotloadFilesMap::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 (std::shared_ptr texture = it->lock()) { texture->m_State = CTexture::UNLOADED; texture->SetHandle(m_DefaultHandle); } } } return INFO::OK; } size_t GetBytesUploaded() const { size_t size = 0; for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) size += (*it)->GetUploadedSize(); return size; } private: PIVFS m_VFS; CCacheLoader m_CacheLoader; bool m_DisableGL; CTextureConverter m_TextureConverter; Handle m_DefaultHandle; Handle m_ErrorHandle; CTexturePtr m_ErrorTexture; // Cache of all loaded textures - typedef boost::unordered_set TextureCache; + using TextureCache = + std::unordered_set; 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 - typedef boost::unordered_map, std::owner_less>>> HotloadFilesMap; + using HotloadFilesMap = + std::unordered_map, std::owner_less > > >; HotloadFilesMap m_HotloadFiles; // Cache for the conversion settings files - typedef boost::unordered_map > SettingsFilesMap; + using SettingsFilesMap = + std::unordered_map >; SettingsFilesMap m_SettingsFiles; }; CTexture::CTexture(Handle handle, const CTextureProperties& props, CTextureManagerImpl* textureManager) : m_Handle(handle), m_BaseColor(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) if (m_Handle) h_add_ref(m_Handle); } CTexture::~CTexture() { if (m_Handle) ogl_tex_free(m_Handle); } void CTexture::Bind(size_t unit) { ogl_tex_bind(GetHandle(), unit); } Handle CTexture::GetHandle() { // 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(); return m_Handle; } 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 (std::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 (std::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::GetBaseColor() const { return m_BaseColor; } size_t CTexture::GetUploadedSize() const { size_t size = 0; (void)ogl_tex_get_uploaded_size(m_Handle, &size); return size; } // CTextureManager: forward all calls to impl: CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, bool disableGL) : m(new CTextureManagerImpl(vfs, highQuality, disableGL)) { } CTextureManager::~CTextureManager() { delete m; } CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props) { return m->CreateTexture(props); } bool CTextureManager::TextureExists(const VfsPath& path) const { return m->TextureExists(path); } CTexturePtr CTextureManager::GetErrorTexture() { return m->GetErrorTexture(); } bool CTextureManager::MakeProgress() { return m->MakeProgress(); } bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath) { return m->GenerateCachedTexture(path, outputPath); } size_t CTextureManager::GetBytesUploaded() const { return m->GetBytesUploaded(); } Index: ps/trunk/source/graphics/TextureManager.h =================================================================== --- ps/trunk/source/graphics/TextureManager.h (revision 23190) +++ ps/trunk/source/graphics/TextureManager.h (revision 23191) @@ -1,318 +1,314 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 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 - -#include "lib/ogl.h" +#include "graphics/Texture.h" #include "lib/file/vfs/vfs.h" +#include "lib/ogl.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 { NONCOPYABLE(CTextureManager); public: /** * Construct texture manager. vfs must be the VFS instance used for all textures * loaded from this object. * highQuality is slower and intended for batch-conversion modes. * disableGL is intended for tests, and will disable all GL uploads. */ CTextureManager(PIVFS vfs, bool highQuality, bool disableGL); ~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(); /** * Synchronously converts and compresses and saves the texture, * and returns the output path (minus a "cache/" prefix). This * is intended for pre-caching textures in release archives. * @return true on success */ bool GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath); /** * Returns true if the given texture exists. * This tests both for the original and converted filename. */ bool TextureExists(const VfsPath& path) const; /** * Returns total number of bytes uploaded for all current texture. */ size_t GetBytesUploaded() const; 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; friend struct TPequal_to; friend struct TPhash; 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_WrapS(GL_REPEAT), m_WrapT(GL_REPEAT), m_Aniso(1.0f), m_Format(0) { } /** * 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_WrapS = wrap; m_WrapT = wrap; } /** * Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc), * separately for S and T. */ void SetWrap(GLint wrap_s, GLint wrap_t) { m_WrapS = wrap_s; m_WrapT = wrap_t; } /** * Set maximum anisotropy value. Must be >= 1.0. Should be a power of 2. */ void SetMaxAnisotropy(float aniso) { m_Aniso = aniso; } /** * Set GL texture upload format, to override the default. * Typically GL_ALPHA or GL_LUMINANCE for 8-bit textures. */ void SetFormatOverride(GLenum format) { m_Format = format; } // 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: // Must update TPhash, TPequal_to when changing these fields VfsPath m_Path; GLint m_Filter; GLint m_WrapS; GLint m_WrapT; float m_Aniso; GLenum m_Format; }; /** * 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; friend struct TPequal_to; friend struct TPhash; // 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 GetBaseColor() const; /** * Returns total number of bytes uploaded for this texture. */ size_t GetUploadedSize() 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); /** * Returns a ogl_tex handle, for later binding. See comments from Bind(). */ Handle GetHandle(); /** * 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_BaseColor; 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) std::weak_ptr m_Self; }; -std::size_t hash_value(const CTexturePtr& v); -std::size_t hash_value(const CTextureProperties& v); - #endif // INCLUDED_TEXTUREMANAGER Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 23190) +++ ps/trunk/source/gui/CGUI.cpp (revision 23191) @@ -1,1248 +1,1249 @@ /* Copyright (C) 2019 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 "CGUI.h" #include "gui/GUIObjectTypes.h" #include "gui/IGUIScrollBar.h" #include "gui/Scripting/ScriptFunctions.h" #include "i18n/L10n.h" #include "lib/bits.h" #include "lib/input.h" #include "lib/sysdep/sysdep.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "scriptinterface/ScriptInterface.h" #include +#include extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion CGUI::CGUI(const shared_ptr& runtime) : m_BaseObject(*this), m_FocusedObject(nullptr), m_InternalNameNumber(0), m_MouseButtons(0) { m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", runtime)); m_ScriptInterface->SetCallbackData(this); GuiScriptingInit(*m_ScriptInterface); m_ScriptInterface->LoadGlobalScripts(); } CGUI::~CGUI() { for (const std::pair& p : m_pAllObjects) delete p.second; for (const std::pair& p : m_Sprites) delete p.second; } InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast(ev->ev.user.data1); if (m_GlobalHotkeys.find(hotkey) != m_GlobalHotkeys.end() && ev->ev.type == SDL_HOTKEYDOWN) { ret = IN_HANDLED; JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedObject globalObj(cx, &GetGlobalObject().toObject()); JS::RootedValue result(cx); JS_CallFunctionValue(cx, globalObj, m_GlobalHotkeys[hotkey], JS::HandleValueArray::empty(), &result); } std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { if (ev->ev.type == SDL_HOTKEYDOWN) ret = obj->SendEvent(GUIM_PRESSED, "press"); else ret = obj->SendEvent(GUIM_RELEASED, "release"); } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale); SGUIMessage msg(GUIM_MOUSE_MOTION); m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CPos oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale); } // Only one object can be hovered IGUIObject* pNearest = nullptr; // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress! { PROFILE("mouse events"); // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly nullptr pNearest = FindObjectUnderMouse(); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress"); break; case SDL_BUTTON_RIGHT: if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress"); break; default: break; } } else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest) { if (ev->ev.wheel.y < 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown"); else if (ev->ev.wheel.y > 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup"); } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease"); } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease"); } break; } // Reset all states on all visible objects m_BaseObject.RecurseObject(&IGUIObject::IsHidden, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); } } // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) m_MousePos = oldMousePos; // Handle keys for input boxes if (GetFocusedObject()) { if ((ev->ev.type == SDL_KEYDOWN && ev->ev.key.keysym.sym != SDLK_ESCAPE && !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] && !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) || ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { const CStr action = "tick"; m_BaseObject.RecurseObject(nullptr, &IGUIObject::ScriptEvent, action); m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this); } void CGUI::SendEventToAll(const CStr& EventName) { // janwas 2006-03-03: spoke with Ykkrosh about EventName case. // when registering, case is converted to lower - this avoids surprise // if someone were to get the case wrong and then not notice their // handler is never called. however, until now, the other end // (sending events here) wasn't converting to lower case, // leading to a similar problem. // now fixed; case is irrelevant since all are converted to lower. const CStr EventNameLower = EventName.LowerCase(); m_BaseObject.RecurseObject(nullptr, &IGUIObject::ScriptEvent, EventNameLower); } void CGUI::SendEventToAll(const CStr& EventName, const JS::HandleValueArray& paramData) { const CStr EventNameLower = EventName.LowerCase(); m_BaseObject.RecurseObject(nullptr, &IGUIObject::ScriptEvent, EventNameLower, paramData); } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); m_BaseObject.RecurseObject(&IGUIObject::IsHidden, &IGUIObject::Draw); } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (!Sprite) return; // TODO: Clipping? Sprite.Draw(*this, Rect, CellID, m_Sprites, Z); } void CGUI::UpdateResolution() { m_BaseObject.RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); } IGUIObject* CGUI::ConstructObject(const CStr& str) { std::map::iterator it = m_ObjectTypes.find(str); if (it == m_ObjectTypes.end()) return nullptr; return (*it->second)(*this); } bool CGUI::AddObject(IGUIObject& parent, IGUIObject& child) { if (child.m_Name.empty()) { LOGERROR("Can't register an object without name!"); return false; } if (m_pAllObjects.find(child.m_Name) != m_pAllObjects.end()) { LOGERROR("Can't register more than one object of the name %s", child.m_Name.c_str()); return false; } m_pAllObjects[child.m_Name] = &child; parent.AddChild(child); return true; } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.find(Name) != m_pAllObjects.end(); } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return nullptr; return it->second; } IGUIObject* CGUI::FindObjectUnderMouse() { IGUIObject* pNearest = nullptr; m_BaseObject.RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) { SGUIMessage msg(GUIM_LOST_FOCUS); m_FocusedObject->HandleMessage(msg); } m_FocusedObject = pObject; if (m_FocusedObject) { SGUIMessage msg(GUIM_GOT_FOCUS); m_FocusedObject->HandleMessage(msg); } } void CGUI::SetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag) { if (!hotkeyTag.empty()) m_HotkeyObjects[hotkeyTag].push_back(pObject); } void CGUI::UnsetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag) { if (hotkeyTag.empty()) return; std::vector& assignment = m_HotkeyObjects[hotkeyTag]; assignment.erase( std::remove_if( assignment.begin(), assignment.end(), [&pObject](const IGUIObject* hotkeyObject) { return pObject == hotkeyObject; }), assignment.end()); } void CGUI::SetGlobalHotkey(const CStr& hotkeyTag, JS::HandleValue function) { JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); if (hotkeyTag.empty()) { JS_ReportError(cx, "Cannot assign a function to an empty hotkey identifier!"); return; } if (!function.isObject() || !JS_ObjectIsFunction(cx, &function.toObject())) { JS_ReportError(cx, "Cannot assign non-function value to global hotkey '%s'", hotkeyTag.c_str()); return; } UnsetGlobalHotkey(hotkeyTag); m_GlobalHotkeys[hotkeyTag].init(cx, function); } void CGUI::UnsetGlobalHotkey(const CStr& hotkeyTag) { m_GlobalHotkeys.erase(hotkeyTag); } const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const { std::map::const_iterator it = m_ScrollBarStyles.find(style); if (it == m_ScrollBarStyles.end()) return nullptr; return &it->second; } /** * @callgraph */ -void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set& Paths) +void CGUI::LoadXmlFile(const VfsPath& Filename, std::unordered_set& Paths) { Paths.insert(Filename); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK) return; XMBElement node = XeroFile.GetRoot(); CStr root_name(XeroFile.GetElementString(node.GetNodeName())); if (root_name == "objects") Xeromyces_ReadRootObjects(node, &XeroFile, Paths); else if (root_name == "sprites") Xeromyces_ReadRootSprites(node, &XeroFile); else if (root_name == "styles") Xeromyces_ReadRootStyles(node, &XeroFile); else if (root_name == "setup") Xeromyces_ReadRootSetup(node, &XeroFile); else LOGERROR("CGUI::LoadXmlFile encountered an unknown XML root node type: %s", root_name.c_str()); } void CGUI::LoadedXmlFiles() { m_BaseObject.RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); SGUIMessage msg(GUIM_LOAD); m_BaseObject.RecurseObject(nullptr, &IGUIObject::HandleMessage, msg); SendEventToAll("load"); } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== -void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set& Paths) +void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, std::unordered_set& Paths) { int el_script = pFile->GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or