Index: ps/trunk/source/renderer/DebugRenderer.h =================================================================== --- ps/trunk/source/renderer/DebugRenderer.h (revision 25329) +++ ps/trunk/source/renderer/DebugRenderer.h (revision 25330) @@ -1,79 +1,78 @@ /* Copyright (C) 2021 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_DEBUGRENDERER #define INCLUDED_DEBUGRENDERER #include class CBoundingBoxAligned; class CBrush; class CCamera; class CMatrix3D; class CVector3D; struct CColor; // Helper for unoptimized rendering of geometrics primitives. Should not be // used for regular passes. class CDebugRenderer { public: /** * Render the line in world space. */ void DrawLine(const CVector3D& from, const CVector3D& to, const CColor& color, const float width); void DrawLine(const std::vector& line, const CColor& color, const float width); /** * Render the circle in world space oriented to the view camera. */ void DrawCircle(const CVector3D& origin, const float radius, const CColor& color); /** * Render: Renders the camera's frustum in world space. - * The caller should set the color using glColorXy before calling Render. * * @param intermediates determines how many intermediate distance planes should * be hinted at between the near and far planes */ void DrawCameraFrustum(const CCamera& camera, const CColor& color, int intermediates = 0); /** * Render the surfaces of the bound box as triangles. */ void DrawBoundingBox(const CBoundingBoxAligned& boundingBox, const CColor& color); void DrawBoundingBox(const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform); /** * Render the outline of the bound box as lines. */ void DrawBoundingBoxOutline(const CBoundingBoxAligned& boundingBox, const CColor& color); void DrawBoundingBoxOutline(const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform); /** * Render the surfaces of the brush as triangles. */ void DrawBrush(const CBrush& brush, const CColor& color); /** * Render the outline of the brush as lines. */ void DrawBrushOutline(const CBrush& brush, const CColor& color); }; #endif // INCLUDED_DEBUGRENDERER Index: ps/trunk/source/renderer/OverlayRenderer.cpp =================================================================== --- ps/trunk/source/renderer/OverlayRenderer.cpp (revision 25329) +++ ps/trunk/source/renderer/OverlayRenderer.cpp (revision 25330) @@ -1,774 +1,772 @@ /* Copyright (C) 2021 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 "graphics/Camera.h" #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/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/TexturedLineRData.h" #include "renderer/VertexArray.h" #include "renderer/VertexBuffer.h" #include "renderer/VertexBufferManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/system/SimContext.h" #include namespace { CShaderProgramPtr GetOverlayLineShader(const CShaderDefines& defines) { const char* shaderName; if (g_RenderingOptions.GetPreferGLSL()) shaderName = "glsl/overlayline"; else shaderName = "arb/overlayline"; return g_Renderer.GetShaderManager().LoadProgram(shaderName, defines); } } // anonymous namespace /** * 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 { 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 (u16 i = 0; i < static_cast(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(); } OverlayRenderer::OverlayRenderer() { m = new OverlayRendererInternals(); } OverlayRenderer::~OverlayRenderer() { delete m; } void OverlayRenderer::Initialize() { m->Initialize(); } void OverlayRenderer::Submit(SOverlayLine* line) { 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; 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 = std::make_shared(); 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 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 (SOverlayLine* line : m->lines) { if (line->m_Coords.empty()) continue; g_Renderer.GetDebugRenderer().DrawLine(line->m_Coords, line->m_Color, static_cast(line->m_Thickness)); } 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); CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); CShaderProgramPtr shaderTexLineNormal = GetOverlayLineShader(m->defsOverlayLineNormal); CShaderProgramPtr shaderTexLineAlwaysVisible = GetOverlayLineShader(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); shaderTexLineNormal->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); // 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); shaderTexLineAlwaysVisible->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); // 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) { #if !CONFIG2_GLES if (g_Renderer.GetOverlayRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); #endif 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); } #if !CONFIG2_GLES if (g_Renderer.GetOverlayRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif } void OverlayRenderer::RenderQuadOverlays() { #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderQuadOverlays for GLES return; #endif if (m->quadBatchMap.empty()) return; CShaderProgramPtr shader = GetOverlayLineShader(m->defsQuadOverlay); if (!shader) return; #if !CONFIG2_GLES if (g_Renderer.GetOverlayRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); #endif pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(0); CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); shader->Bind(); shader->BindTexture(str_losTex, los.GetTexture()); shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); // 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); #if !CONFIG2_GLES if (g_Renderer.GetOverlayRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif } void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) { PROFILE3_GPU("overlays (fg)"); #if CONFIG2_GLES #warning TODO: implement OverlayRenderer::RenderForegroundOverlays for GLES #else if (g_Renderer.GetOverlayRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); CVector3D right = -viewCamera.GetOrientation().GetLeft(); CVector3D up = viewCamera.GetOrientation().GetUp(); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_foreground_overlay); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); float uvs[8] = { 0,1, 1,1, 1,0, 0,0 }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, sizeof(float)*2, &uvs[0]); for (size_t i = 0; i < m->sprites.size(); ++i) { SOverlaySprite* sprite = m->sprites[i]; if (!i || sprite->m_Texture != m->sprites[i - 1]->m_Texture) shader->BindTexture(str_baseTex, sprite->m_Texture); 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 }; shader->VertexPointer(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; } tech->EndPass(); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); if (g_Renderer.GetOverlayRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #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 (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, g_Renderer.GetViewCamera().GetViewProjection()); shader->Uniform(str_instancingTransform, 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/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 25329) +++ ps/trunk/source/renderer/Renderer.cpp (revision 25330) @@ -1,1939 +1,1937 @@ /* Copyright (C) 2021 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 . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #include "precompiled.h" #include "Renderer.h" #include "lib/bits.h" // is_pow2 #include "lib/res/graphics/ogl_tex.h" #include "lib/allocators/shared_ptr.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/Decal.h" #include "graphics/FontManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/MaterialManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/DebugRenderer.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PostprocManager.h" #include "renderer/RenderingOptions.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SilhouetteRenderer.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include #include #include #include struct SScreenRect { GLint x1, y1, x2, y2; }; /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_TextureMemory, Row_ShadersLoaded, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB reserved"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024); return buf; case Row_VBAllocated: if (col == 0) return "VB allocated"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024); return buf; case Row_TextureMemory: if (col == 0) return "textures uploaded"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { NONCOPYABLE(CRendererInternals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Texture manager CTextureManager textureManager; /// Terrain renderer TerrainRenderer terrainRenderer; /// Overlay renderer OverlayRenderer overlayRenderer; /// Particle manager CParticleManager particleManager; /// Particle renderer ParticleRenderer particleRenderer; /// Material manager CMaterialManager materialManager; /// Time manager CTimeManager timeManager; /// Shadow map ShadowMap shadow; /// Postprocessing effect manager CPostprocManager postprocManager; CDebugRenderer debugRenderer; CFontManager fontManager; SilhouetteRenderer silhouetteRenderer; /// Various model renderers struct Models { // NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer, // RenderModifier, etc) is mostly a relic of an older design that implemented // the different materials and rendering modes through extensive subclassing // and hooking objects together in various combinations. // The new design uses the CShaderManager API to abstract away the details // of rendering, and uses a data-driven approach to materials, so there are // now a small number of generic subclasses instead of many specialised subclasses, // but most of the old infrastructure hasn't been refactored out yet and leads to // some unwanted complexity. // Submitted models are split on two axes: // - Normal vs Transp[arent] - alpha-blended models are stored in a separate // list so we can draw them above/below the alpha-blended water plane correctly // - Skinned vs Unskinned - with hardware lighting we don't need to // duplicate mesh data per model instance (except for skinned models), // so non-skinned models get different ModelVertexRenderers ModelRendererPtr NormalSkinned; ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported ModelRendererPtr TranspSkinned; ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; ModelVertexRendererPtr VertexGPUSkinningShader; LitRenderModifierPtr ModShader; } Model; CShaderDefines globalContext; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { } /** * Renders all non-alpha-blended models with the given context. */ void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.NormalSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.NormalUnskinned != Model.NormalSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } /** * Renders all alpha-blended models with the given context. */ void CallTranspModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.TranspSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.TranspUnskinned != Model.TranspSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer constructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; g_ProfileViewer.AddRootTable(&m->profileTable); m_Width = 0; m_Height = 0; m_TerrainRenderMode = SOLID; m_WaterRenderMode = SOLID; m_ModelRenderMode = SOLID; m_OverlayRenderMode = SOLID; m_ClearColor[0] = m_ClearColor[1] = m_ClearColor[2] = m_ClearColor[3] = 0; m_DisplayTerrainPriorities = false; m_SkipSubmit = false; CStr skystring = "0 0 0"; CColor skycolor; CFG_GET_VAL("skycolor", skystring); if (skycolor.ParseString(skystring, 255.f)) SetClearColor(skycolor.AsSColor4ub()); m_LightEnv = nullptr; m_CurrentScene = nullptr; m_hCompositeAlphaMap = 0; m_Stats.Reset(); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; m_Caps.m_PrettyWater = false; // now start querying extensions if (!g_RenderingOptions.GetNoVBO() && ogl_HaveExtension("GL_ARB_vertex_buffer_object")) m_Caps.m_VBO = true; if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) m_Caps.m_FragmentShader = true; } #if CONFIG2_GLES m_Caps.m_Shadows = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } #endif #if CONFIG2_GLES m_Caps.m_PrettyWater = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", "GL_EXT_framebuffer_object", NULL)) m_Caps.m_PrettyWater = true; #endif } void CRenderer::RecomputeSystemShaderDefines() { CShaderDefines defines; if (m_Caps.m_ARBProgram) defines.Add(str_SYS_HAS_ARB, str_1); if (m_Caps.m_VertexShader && m_Caps.m_FragmentShader) defines.Add(str_SYS_HAS_GLSL, str_1); if (g_RenderingOptions.GetPreferGLSL()) defines.Add(str_SYS_PREFER_GLSL, str_1); m_SystemShaderDefines = defines; } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->globalContext = m_SystemShaderDefines; if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) { m->globalContext.Add(str_USE_SHADOW, str_1); if (m_Caps.m_ARBProgramShadow && g_RenderingOptions.GetARBProgramShadow()) m->globalContext.Add(str_USE_FP_SHADOW, str_1); if (g_RenderingOptions.GetShadowPCF()) m->globalContext.Add(str_USE_SHADOW_PCF, str_1); #if !CONFIG2_GLES m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1); #endif } if (g_RenderingOptions.GetPreferGLSL() && g_RenderingOptions.GetFog()) m->globalContext.Add(str_USE_FOG, str_1); m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer()); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, g_RenderingOptions.GetPreferGLSL())); if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too { m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, g_RenderingOptions.GetPreferGLSL())); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); } else { m->Model.VertexGPUSkinningShader.reset(); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); } m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // Dimensions m_Width = width; m_Height = height; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOGMESSAGE("CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE("CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE("CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(g_RenderingOptions.GetRenderPath()); RecomputeSystemShaderDefines(); // Let component renderers perform one-time initialization after graphics capabilities and // the shader path have been determined. m->overlayRenderer.Initialize(); if (g_RenderingOptions.GetPostProc()) m->postprocManager.Initialize(); return true; } // resize renderer view void CRenderer::Resize(int width, int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow.RecreateTexture(); m_Width = width; m_Height = height; m->postprocManager.Resize(); m_WaterManager->Resize(); } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. return; } // Renderer has been opened, so validate the selected renderpath if (rp == RenderPath::DEFAULT) { if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_RenderingOptions.GetPreferGLSL())) rp = RenderPath::SHADER; else rp = RenderPath::FIXED; } if (rp == RenderPath::SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_RenderingOptions.GetPreferGLSL()))) { LOGWARNING("Falling back to fixed function\n"); rp = RenderPath::FIXED; } } // TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere. g_RenderingOptions.m_RenderPath = rp; MakeShadersDirty(); RecomputeSystemShaderDefines(); // We might need to regenerate some render data after changing path if (g_Game) g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { PROFILE("begin frame"); // zero out all the per-frame stats m_Stats.Reset(); // choose model renderers for this frame if (m->ShadersDirty) ReloadShaders(); m->Model.ModShader->SetShadowMap(&m->shadow); m->Model.ModShader->SetLightEnv(m_LightEnv); } ////////////////////////////////////////////////////////////////////////////////////////// void CRenderer::SetSimulation(CSimulation2* simulation) { // set current simulation context for terrain renderer m->terrainRenderer.SetSimulation(simulation); } // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(SColor4ub color) { m_ClearColor[0] = float(color.R) / 255.0f; m_ClearColor[1] = float(color.G) / 255.0f; m_ClearColor[2] = float(color.B) / 255.0f; m_ClearColor[3] = float(color.A) / 255.0f; } void CRenderer::RenderShadowMap(const CShaderDefines& context) { PROFILE3_GPU("shadow map"); m->shadow.BeginRender(); { PROFILE("render patches"); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); m->terrainRenderer.RenderPatches(CULL_SHADOWS); glCullFace(GL_BACK); } CShaderDefines contextCast = context; contextCast.Add(str_MODE_SHADOWCAST, str_1); { PROFILE("render models"); m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } m->shadow.EndRender(); SetViewport(m_ViewCamera.GetViewPort()); } void CRenderer::RenderPatches(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("patches"); #if CONFIG2_GLES #warning TODO: implement wireface/edged rendering mode GLES #else // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // render all the patches, including blend pass ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED); m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) ? &m->shadow : 0); #if !CONFIG2_GLES if (m_TerrainRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_TerrainRenderMode == EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe - glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // setup some renderstate .. pglActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); glLineWidth(2.0f); // render tiles edges m->terrainRenderer.RenderPatches(cullGroup, CColor(0.5f, 0.5f, 1.0f, 1.0f)); - // set color for outline - glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer.RenderOutlines(cullGroup); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderModels(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("models"); int flags = 0; #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif m->CallModelRenderers(context, cullGroup, flags); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = context; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling) { PROFILE3_GPU("transparent models"); int flags = 0; #if !CONFIG2_GLES // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // disable face culling for two-sided models in sub-renders if (disableFaceCulling) glDisable(GL_CULL_FACE); CShaderDefines contextOpaque = context; contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1); CShaderDefines contextBlend = context; contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) m->CallTranspModelRenderers(contextOpaque, cullGroup, flags); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) m->CallTranspModelRenderers(contextBlend, cullGroup, flags); if (disableFaceCulling) glEnable(GL_CULL_FACE); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = contextOpaque; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallTranspModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) void CRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const { // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D normalMatrix = camera.GetOrientation().GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); CMatrix3D matrix = camera.GetProjection(); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix CVector4D q; q.X = (sgn(camPlane.X) - matrix[8]/matrix[11]) / matrix[0]; q.Y = (sgn(camPlane.Y) - matrix[9]/matrix[11]) / matrix[5]; q.Z = 1.0f/matrix[11]; q.W = (1.0f - matrix[10]/matrix[11]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.X; matrix[6] = c.Y; matrix[10] = c.Z - matrix[11]; matrix[14] = c.W; // Load it back into the camera camera.SetProjection(matrix); } void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; CMatrix3D projection; if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE) { const float aspectRatio = 1.0f; // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane()); } else projection = m_ViewCamera.GetProjection(); camera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. camera.m_Orientation.Scale(1, -1, 1); camera.m_Orientation.Translate(0, 2 * wm.m_WaterHeight, 0); camera.UpdateFrustum(scissor); // Clip slightly above the water to improve reflections of objects on the water // when the reflections are distorted. camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight + 2.0f)); SViewPort vp; vp.m_Height = wm.m_RefTextureSize; vp.m_Width = wm.m_RefTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(projection); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height / static_cast(std::max(1, m_Width)), 1.0f, 1.0f); camera.SetProjection(scaleMat * camera.GetProjection()); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f); SetObliqueFrustumClipping(camera, camPlane); } void CRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; CMatrix3D projection; if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE) { const float aspectRatio = 1.0f; // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane()); } else projection = m_ViewCamera.GetProjection(); camera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. camera.UpdateFrustum(scissor); camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores. SViewPort vp; vp.m_Height = wm.m_RefTextureSize; vp.m_Width = wm.m_RefTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(projection); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height / static_cast(std::max(1, m_Width)), 1.0f, 1.0f); camera.SetProjection(scaleMat * camera.GetProjection()); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor) { PROFILE3_GPU("water reflections"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeReflectionCamera(m_ViewCamera, scissor); SetViewport(m_ViewCamera.GetViewPort()); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); float vpHeight = wm.m_RefTextureSize; float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_ReflectionFbo); glClearColor(0.5f, 0.5f, 1.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glFrontFace(GL_CW); if (!g_RenderingOptions.GetWaterReflection()) { m->skyManager.RenderSky(); ogl_WarnIfError(); } else { // Render terrain and models RenderPatches(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT, true); ogl_WarnIfError(); } glFrontFace(GL_CCW); // Particles are always oriented to face the camera in the vertex shader, // so they don't need the inverted glFrontFace if (g_RenderingOptions.GetParticles()) { RenderParticles(CULL_REFLECTIONS); ogl_WarnIfError(); } glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; SetViewport(m_ViewCamera.GetViewPort()); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor) { PROFILE3_GPU("water refractions"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeRefractionCamera(m_ViewCamera, scissor); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f); SetObliqueFrustumClipping(m_ViewCamera, camPlane); SetViewport(m_ViewCamera.GetViewPort()); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection(); wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse(); wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation(); float vpHeight = wm.m_RefTextureSize; float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo); glClearColor(1.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render terrain and models RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; SetViewport(m_ViewCamera.GetViewPort()); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } void CRenderer::RenderSilhouettes(const CShaderDefines& context) { PROFILE3_GPU("silhouettes"); CShaderDefines contextOccluder = context; contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1); CShaderDefines contextDisplay = context; contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1); // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. // First we render all occluders into depth, then render all units with // inverted depth test so any behind an occluder will get drawn in a constant // color. float silhouetteAlpha = 0.75f; // Silhouette blending requires an almost-universally-supported extension; // fall back to non-blended if unavailable if (!ogl_HaveExtension("GL_EXT_blend_color")) silhouetteAlpha = 1.f; glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); // Render occluders: { PROFILE("render patches"); // To prevent units displaying silhouettes when parts of their model // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); m->terrainRenderer.RenderPatches(CULL_SILHOUETTE_OCCLUDER); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } { PROFILE("render transparent occluders"); m->CallTranspModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } glDepthFunc(GL_GEQUAL); glColorMask(1, 1, 1, 1); // Render more efficiently if alpha == 1 if (silhouetteAlpha == 1.f) { // Ideally we'd render objects back-to-front so nearer silhouettes would // appear on top, but sorting has non-zero cost. So we'll keep the depth // write enabled, to do the opposite - far objects will consistently appear // on top. glDepthMask(0); } else { // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the color of whatever model happens to be drawn first). glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); pglBlendColorEXT(0, 0, 0, silhouetteAlpha); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } { PROFILE("render model casters"); m->CallModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0); } { PROFILE("render transparent casters"); m->CallTranspModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0); } // Restore state glDepthFunc(GL_LEQUAL); if (silhouetteAlpha == 1.f) { glDepthMask(1); } else { glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pglBlendColorEXT(0, 0, 0, 0); glDisable(GL_STENCIL_TEST); } } void CRenderer::RenderParticles(int cullGroup) { PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(cullGroup); #if !CONFIG2_GLES if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); m->particleRenderer.RenderParticles(true); m->particleRenderer.RenderBounds(cullGroup); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor) { PROFILE3("render submissions"); GetScene().GetLOSTexture().InterpolateLOS(); CShaderDefines context = m->globalContext; int cullGroup = CULL_DEFAULT; ogl_WarnIfError(); // Set the camera SetViewport(m_ViewCamera.GetViewPort()); // Prepare model renderers { PROFILE3("prepare models"); m->Model.NormalSkinned->PrepareModels(); m->Model.TranspSkinned->PrepareModels(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->PrepareModels(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->PrepareModels(); } m->terrainRenderer.PrepareForRendering(); m->overlayRenderer.PrepareForRendering(); m->particleRenderer.PrepareForRendering(context); if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) { RenderShadowMap(context); } ogl_WarnIfError(); if (m_WaterManager->m_RenderWater) { if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { PROFILE3_GPU("water scissor"); RenderReflections(context, waterScissor); if (g_RenderingOptions.GetWaterRefraction()) RenderRefractions(context, waterScissor); } } if (g_RenderingOptions.GetPostProc()) { // We have to update the post process manager with real near/far planes // that we use for the scene rendering. m->postprocManager.SetDepthBufferClipPlanes( m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane() ); m->postprocManager.Initialize(); m->postprocManager.CaptureRenderOutput(); } { PROFILE3_GPU("clear buffers"); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } m->skyManager.RenderSky(); // render submitted patches and models RenderPatches(context, cullGroup); ogl_WarnIfError(); // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysBeforeWater(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(context, cullGroup); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { if (m_WaterManager->WillRenderFancyWater()) { // Render transparent stuff, but only the solid parts that can occlude block water. RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // Render transparent stuff again, but only the blended parts that overlap water. RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); ogl_WarnIfError(); } else { m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // Render transparent stuff, so it can overlap models/terrain. RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } } else { // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysAfterWater(cullGroup); ogl_WarnIfError(); // render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); // particles are transparent so render after water if (g_RenderingOptions.GetParticles()) { RenderParticles(cullGroup); ogl_WarnIfError(); } if (g_RenderingOptions.GetPostProc()) { if (g_Renderer.GetPostprocManager().IsMultisampleEnabled()) g_Renderer.GetPostprocManager().ResolveMultisampleFramebuffer(); m->postprocManager.ApplyPostproc(); m->postprocManager.ReleaseRenderOutput(); } if (g_RenderingOptions.GetSilhouettes()) { RenderSilhouettes(context); } // render debug lines if (g_RenderingOptions.GetDisplayFrustum()) DisplayFrustum(); if (g_RenderingOptions.GetDisplayShadowsFrustum()) { m->shadow.RenderDebugBounds(); m->shadow.RenderDebugTexture(); } m->silhouetteRenderer.RenderDebugOverlays(m_ViewCamera); // render overlays that should appear on top of all other objects m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE3("end frame"); // empty lists m->terrainRenderer.EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); m->silhouetteRenderer.EndFrame(); // Finish model renderers m->Model.NormalSkinned->EndFrame(); m->Model.TranspSkinned->EndFrame(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->EndFrame(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->EndFrame(); ogl_tex_bind(0, 0); } void CRenderer::OnSwapBuffers() { bool checkGLErrorAfterSwap = false; CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap); if (!checkGLErrorAfterSwap) return; PROFILE3("error check"); // We have to check GL errors after SwapBuffer to avoid possible // synchronizations during rendering. if (GLenum err = glGetError()) ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err)); } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { #if CONFIG2_GLES #warning TODO: implement CRenderer::DisplayFrustum for GLES #else glDepthMask(0); glDisable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 0.25f), 2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 1.0f), 2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); #endif ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer.RenderPriorities(CULL_DEFAULT); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // The camera always represents the actual camera used to render a scene, not any virtual camera // used for shadow rendering or reflections. void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); } void CRenderer::SetViewport(const SViewPort &vp) { m_Viewport = vp; glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } SViewPort CRenderer::GetViewport() { return m_Viewport; } void CRenderer::Submit(CPatch* patch) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(patch->GetWorldBounds()); m->silhouetteRenderer.AddOccluder(patch); } if (m_CurrentCullGroup == CULL_SHADOWS) { m->shadow.AddShadowCasterBound(patch->GetWorldBounds()); } m->terrainRenderer.Submit(m_CurrentCullGroup, patch); } void CRenderer::Submit(SOverlayLine* overlay) { // Overlays are only needed in the default cull group for now, // so just ignore submissions to any other group if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayQuad* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySphere* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { // Decals can't cast shadows since they're flat on the terrain. // They can receive shadows, but the terrain under them will have // already been passed to AddShadowCasterBound, so don't bother // doing it again here. m->terrainRenderer.Submit(m_CurrentCullGroup, decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(m_CurrentCullGroup, emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(model->GetWorldBounds()); if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER) m->silhouetteRenderer.AddOccluder(model); if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY) m->silhouetteRenderer.AddCaster(model); } if (m_CurrentCullGroup == CULL_SHADOWS) { if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS)) return; m->shadow.AddShadowCasterBound(model->GetWorldBounds()); } bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); if (model->GetMaterial().UsesAlphaBlending()) { if (requiresSkinning) m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model); else m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model); } else { if (requiresSkinning) m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model); else m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); m_CurrentCullGroup = CULL_DEFAULT; scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); if (g_RenderingOptions.GetSilhouettes()) { m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera); m_CurrentCullGroup = CULL_DEFAULT; m->silhouetteRenderer.RenderSubmitOverlays(*this); m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER; m->silhouetteRenderer.RenderSubmitOccluders(*this); m_CurrentCullGroup = CULL_SILHOUETTE_CASTER; m->silhouetteRenderer.RenderSubmitCasters(*this); } if (m_Caps.m_Shadows && g_RenderingOptions.GetShadows()) { m_CurrentCullGroup = CULL_SHADOWS; CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(); scene.EnumerateObjects(shadowFrustum, this); } CBoundingBoxAligned waterScissor; if (m_WaterManager->m_RenderWater) { waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera.GetViewProjection()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { if (g_RenderingOptions.GetWaterReflection()) { m_CurrentCullGroup = CULL_REFLECTIONS; CCamera reflectionCamera; ComputeReflectionCamera(reflectionCamera, waterScissor); scene.EnumerateObjects(reflectionCamera.GetFrustum(), this); } if (g_RenderingOptions.GetWaterRefraction()) { m_CurrentCullGroup = CULL_REFRACTIONS; CCamera refractionCamera; ComputeRefractionCamera(refractionCamera, waterScissor); scene.EnumerateObjects(refractionCamera.GetFrustum(), this); } // Render the waves to the Fancy effects texture m_WaterManager->RenderWaves(frustum); } } m_CurrentCullGroup = -1; ogl_WarnIfError(); RenderSubmissions(waterScissor); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { ENSURE(m_CurrentScene); return *m_CurrentScene; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit, GLuint tex) { pglActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D, tex); #if !CONFIG2_GLES if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture // NB: A variant of this function is duplicated in TerrainTextureEntry.cpp, for use with the Shader // renderpath. This copy is kept to load the 'standard' maps for the fixed pipeline and should // be removed if/when the fixed pipeline goes. int CRenderer::LoadAlphaMaps() { const wchar_t* const key = L"(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; VfsPath path(L"art/textures/terrain/alphamaps/standard"); const wchar_t* fnames[NumAlphaMaps] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NumAlphaMaps; i++) { // get src of copy u8* src = 0; ignore_result(ogl_tex_get_data(textures[i], &src)); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); m_AlphaMapCoords[i].v0 = 0.0f; m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NumAlphaMaps; i++) ignore_result(ogl_tex_free(textures[i])); // upload the composite texture Tex t; ignore_result(t.wrap(total_w, total_h, 8, TEX_GREY, data, 0)); /*VfsPath filename("blendtex.png"); DynArray da; RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da)); // write to disk //Status ret = INFO::OK; { shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if(bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); //else // ret = (Status)bytes_written; } ignore_result(da_free(&da));*/ m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); ignore_result(ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR)); ignore_result(ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE)); int ret = ogl_tex_upload(m_hCompositeAlphaMap, GL_ALPHA, 0, 0); return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// // UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps void CRenderer::UnloadAlphaMaps() { ogl_tex_free(m_hCompositeAlphaMap); m_hCompositeAlphaMap = 0; } Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) { CRenderer* renderer = static_cast(param); // If an alpha map changed, and we already loaded them, then reload them if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/")) { if (renderer->m_hCompositeAlphaMap) { renderer->UnloadAlphaMaps(); renderer->LoadAlphaMaps(); } } return INFO::OK; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; m_WaterManager->m_NeedsReloading = true; } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } TerrainRenderer& CRenderer::GetTerrainRenderer() { return m->terrainRenderer; } CTimeManager& CRenderer::GetTimeManager() { return m->timeManager; } CMaterialManager& CRenderer::GetMaterialManager() { return m->materialManager; } CPostprocManager& CRenderer::GetPostprocManager() { return m->postprocManager; } CDebugRenderer& CRenderer::GetDebugRenderer() { return m->debugRenderer; } CFontManager& CRenderer::GetFontManager() { return m->fontManager; } ShadowMap& CRenderer::GetShadowMap() { return m->shadow; } void CRenderer::ResetState() { // Clear all emitters, that were created in previous games GetParticleManager().ClearUnattachedEmitters(); }