Index: ps/trunk/source/graphics/LOSTexture.cpp =================================================================== --- ps/trunk/source/graphics/LOSTexture.cpp (revision 26142) +++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26143) @@ -1,418 +1,418 @@ /* 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 "LOSTexture.h" #include "graphics/ShaderManager.h" #include "lib/bits.h" #include "lib/config2.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/TimeManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" /* The LOS bitmap is computed with one value per LOS vertex, based on CCmpRangeManager's visibility information. The bitmap is then blurred using an NxN filter (in particular a 7-tap Binomial filter as an efficient integral approximation of a Gaussian). To implement the blur efficiently without using extra memory for a second copy of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side, then the blur shifts the image back into the corner. The blurred bitmap is then uploaded into a GL texture for use by the renderer. */ // Blur with a NxN filter, where N = g_BlurSize must be an odd number. // Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES. static const size_t g_BlurSize = 7; // Alignment (in bytes) of the pixel data passed into glTexSubImage2D. // This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since // that's what we set it to) but in some weird cases appears to have a different // value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway. static const size_t g_SubTextureAlignment = 4; CLOSTexture::CLOSTexture(CSimulation2& simulation) : m_Simulation(simulation), m_Dirty(true), m_ShaderInitialized(false), m_smoothFbo(0), m_MapSize(0), m_TextureSize(0), whichTex(true) { if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) CreateShader(); } CLOSTexture::~CLOSTexture() { if (m_smoothFbo) glDeleteFramebuffersEXT(1, &m_smoothFbo); if (m_Texture) DeleteTexture(); } // Create the LOS texture engine. Should be ran only once. bool CLOSTexture::CreateShader() { m_smoothShader = g_Renderer.GetShaderManager().LoadEffect(str_los_interp); CShaderProgramPtr shader = m_smoothShader->GetShader(); m_ShaderInitialized = m_smoothShader && shader; if (!m_ShaderInitialized) { LOGERROR("Failed to load SmoothLOS shader, disabling."); g_RenderingOptions.SetSmoothLOS(false); return false; } glGenFramebuffersEXT(1, &m_smoothFbo); return true; } void CLOSTexture::DeleteTexture() { m_Texture.reset(); m_TextureSmooth1.reset(); m_TextureSmooth2.reset(); } void CLOSTexture::MakeDirty() { m_Dirty = true; } -GLuint CLOSTexture::GetTextureSmooth() +Renderer::Backend::GL::CTexture* CLOSTexture::GetTextureSmooth() { if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS()) return GetTexture(); else - return (whichTex ? m_TextureSmooth1 : m_TextureSmooth2)->GetHandle(); + return (whichTex ? m_TextureSmooth1 : m_TextureSmooth2).get(); } void CLOSTexture::InterpolateLOS() { if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS()) return; if (!m_ShaderInitialized) { if (!CreateShader()) return; // RecomputeTexture(0) will not cause the ConstructTexture to run. // Force the textures to be created. DeleteTexture(); ConstructTexture(); m_Dirty = true; } if (m_Dirty) { RecomputeTexture(); m_Dirty = false; } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_smoothFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, (whichTex ? m_TextureSmooth2 : m_TextureSmooth1)->GetHandle(), 0); GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("LOS framebuffer object incomplete: 0x%04X", status); } m_smoothShader->BeginPass(); CShaderProgramPtr shader = m_smoothShader->GetShader(); glDisable(GL_BLEND); shader->Bind(); - shader->BindTexture(str_losTex1, m_Texture->GetHandle()); - shader->BindTexture(str_losTex2, (whichTex ? m_TextureSmooth1 : m_TextureSmooth2)->GetHandle()); + shader->BindTexture(str_losTex1, m_Texture.get()); + shader->BindTexture(str_losTex2, (whichTex ? m_TextureSmooth1 : m_TextureSmooth2).get()); shader->Uniform(str_delta, (float)g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f, 0.0f, 0.0f, 0.0f); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, m_TextureSize, m_TextureSize }; g_Renderer.SetViewport(vp); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); shader->Unbind(); m_smoothShader->EndPass(); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); whichTex = !whichTex; } -GLuint CLOSTexture::GetTexture() +Renderer::Backend::GL::CTexture* CLOSTexture::GetTexture() { if (m_Dirty) { RecomputeTexture(); m_Dirty = false; } - return m_Texture->GetHandle(); + return m_Texture.get(); } const CMatrix3D& CLOSTexture::GetTextureMatrix() { ENSURE(!m_Dirty); return m_TextureMatrix; } const CMatrix3D* CLOSTexture::GetMinimapTextureMatrix() { ENSURE(!m_Dirty); return &m_MinimapTextureMatrix; } void CLOSTexture::ConstructTexture() { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; m_MapSize = cmpRangeManager->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment)); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); m_Texture = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::A8, m_TextureSize, m_TextureSize, defaultSamplerDesc); // Initialise texture with SoD color, for the areas we don't // overwrite with glTexSubImage2D later u8* texData = new u8[m_TextureSize * m_TextureSize * 4]; memset(texData, 0x00, m_TextureSize * m_TextureSize * 4); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) { m_TextureSmooth1 = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, defaultSamplerDesc); m_TextureSmooth2 = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, defaultSamplerDesc); g_Renderer.BindTexture(0, m_TextureSmooth1->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); g_Renderer.BindTexture(0, m_TextureSmooth2->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); } g_Renderer.BindTexture(0, m_Texture->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData); delete[] texData; { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. first vertex) // onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel); // world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex) // onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel) float s = (m_MapSize-1) / (float)(m_TextureSize * (m_MapSize-1) * LOS_TILE_SIZE); float t = 0.5f / m_TextureSize; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / (float)m_TextureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CLOSTexture::RecomputeTexture() { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide()) DeleteTexture(); } bool recreated = false; if (!m_Texture) { ConstructTexture(); recreated = true; } PROFILE("recompute LOS texture"); std::vector losData; size_t pitch; losData.resize(GetBitmapSize(m_MapSize, m_MapSize, &pitch)); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer())); GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated) { g_Renderer.BindTexture(0, m_TextureSmooth1->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); g_Renderer.BindTexture(0, m_TextureSmooth2->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); } g_Renderer.BindTexture(0, m_Texture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]); } size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch) { *pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment); return *pitch * (h + g_BlurSize - 1); } void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch) { u8 *dataPtr = losData; // Initialise the top padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; for (size_t j = 0; j < h; ++j) { // Initialise the left padding for (size_t i = 0; i < g_BlurSize/2; ++i) *dataPtr++ = 0; // Fill in the visibility data for (size_t i = 0; i < w; ++i) { if (los.IsVisible_UncheckedRange(i, j)) *dataPtr++ = 255; else if (los.IsExplored_UncheckedRange(i, j)) *dataPtr++ = 127; else *dataPtr++ = 0; } // Initialise the right padding for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i) *dataPtr++ = 0; } // Initialise the bottom padding for (size_t j = 0; j < g_BlurSize/2; ++j) for (size_t i = 0; i < pitch; ++i) *dataPtr++ = 0; // Horizontal blur: for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0] + 6*d[1] + 15*d[2] + 20*d[3] + 15*d[4] + 6*d[5] + 1*d[6] ) / 64; } } // Vertical blur: for (size_t j = 0; j < h; ++j) { for (size_t i = 0; i < w; ++i) { u8* d = &losData[i+j*pitch]; *d = ( 1*d[0*pitch] + 6*d[1*pitch] + 15*d[2*pitch] + 20*d[3*pitch] + 15*d[4*pitch] + 6*d[5*pitch] + 1*d[6*pitch] ) / 64; } } } Index: ps/trunk/source/graphics/LOSTexture.h =================================================================== --- ps/trunk/source/graphics/LOSTexture.h (revision 26142) +++ ps/trunk/source/graphics/LOSTexture.h (revision 26143) @@ -1,103 +1,103 @@ /* 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_LOSTEXTURE #define INCLUDED_LOSTEXTURE #include "lib/ogl.h" #include "graphics/ShaderTechniquePtr.h" #include "maths/Matrix3D.h" #include "renderer/backend/gl/Texture.h" class CLosQuerier; class CSimulation2; /** * Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for * rendering and for the minimap. */ class CLOSTexture { NONCOPYABLE(CLOSTexture); friend class TestLOSTexture; public: CLOSTexture(CSimulation2& simulation); ~CLOSTexture(); /** * Marks the LOS texture as needing recomputation. Call this after each * simulation update, to ensure responsive updates. */ void MakeDirty(); /** * Recomputes the LOS texture if necessary, and returns the texture handle. * Also potentially switches the current active texture unit, and enables texturing on it. * The texture is in 8-bit ALPHA format. */ - GLuint GetTexture(); + Renderer::Backend::GL::CTexture* GetTexture(); void InterpolateLOS(); - GLuint GetTextureSmooth(); + Renderer::Backend::GL::CTexture* GetTextureSmooth(); /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture * coordinates, in the form expected by a matrix uniform. * This must only be called after BindTexture. */ const CMatrix3D& GetTextureMatrix(); /** * Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture * coordinates, in the form expected by a matrix uniform. * This must only be called after BindTexture. */ const CMatrix3D* GetMinimapTextureMatrix(); private: void DeleteTexture(); bool CreateShader(); void ConstructTexture(); void RecomputeTexture(); size_t GetBitmapSize(size_t w, size_t h, size_t* pitch); void GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch); CSimulation2& m_Simulation; bool m_Dirty; bool m_ShaderInitialized; std::unique_ptr m_Texture, m_TextureSmooth1, m_TextureSmooth2; bool whichTex; GLuint m_smoothFbo; CShaderTechniquePtr m_smoothShader; size_t m_MapSize; // vertexes per side GLsizei m_TextureSize; // texels per side CMatrix3D m_TextureMatrix; CMatrix3D m_MinimapTextureMatrix; }; #endif // INCLUDED_LOSTEXTURE Index: ps/trunk/source/graphics/MiniMapTexture.cpp =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26142) +++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26143) @@ -1,528 +1,528 @@ /* 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 "MiniMapTexture.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniPatch.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgramPtr.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "lib/bits.h" #include "lib/timer.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/WaterManager.h" #include "scriptinterface/Object.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/system/ParamNode.h" namespace { // Set max drawn entities to UINT16_MAX for now, which is more than enough // TODO: we should be cleverer about drawing them to reduce clutter const u16 MAX_ENTITIES_DRAWN = 65535; const size_t FINAL_TEXTURE_SIZE = 512; unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = unsigned(float(color & 0xff) * x); unsigned int g = unsigned(float((color >> 8) & 0xff) * x); unsigned int b = unsigned(float((color >> 16) & 0xff) * x); return (0xff000000 | b | g << 8 | r << 16); } void DrawTexture(CShaderProgramPtr shader) { const float quadUVs[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f }; const float quadVertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadUVs); shader->VertexPointer(3, GL_FLOAT, 0, quadVertices); shader->AssertPointersBound(); if (!g_Renderer.DoSkipSubmit()) glDrawArrays(GL_TRIANGLES, 0, 6); } struct MinimapUnitVertex { // This struct is copyable for convenience and because to move is to copy for primitives. u8 r, g, b, a; float x, y; }; // Adds a vertex to the passed VertexArray static void inline addVertex(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.x; (*attrPos)[1] = v.y; ++attrPos; } } // anonymous namespace CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation) : m_Simulation(simulation), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW) { // Register Relax NG validator. CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); m_ShallowPassageHeight = GetShallowPassageHeight(); double blinkDuration = 1.0; // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); } m_HalfBlinkDuration = blinkDuration / 2.0; m_AttributePos.type = GL_FLOAT; m_AttributePos.elems = 2; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeColor.type = GL_UNSIGNED_BYTE; m_AttributeColor.elems = 4; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_VertexArray.Layout(); m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) *index++ = i; m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) { (*attrColor)[0] = 0; (*attrColor)[1] = 0; (*attrColor)[2] = 0; (*attrColor)[3] = 0; ++attrColor; (*attrPos)[0] = -10000.0f; (*attrPos)[1] = -10000.0f; ++attrPos; } m_VertexArray.Upload(); } CMiniMapTexture::~CMiniMapTexture() { DestroyTextures(); } void CMiniMapTexture::Update(const float UNUSED(deltaRealTime)) { if (m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight) { m_TerrainTextureDirty = true; m_FinalTextureDirty = true; } } void CMiniMapTexture::Render() { const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain) return; if (!m_TerrainTexture) CreateTextures(terrain); if (m_TerrainTextureDirty) RebuildTerrainTexture(terrain); RenderFinalTexture(); } void CMiniMapTexture::CreateTextures(const CTerrain* terrain) { DestroyTextures(); m_MapSize = terrain->GetVerticesPerSide(); m_TextureSize = static_cast(round_up_to_pow2(static_cast(m_MapSize))); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); // Create terrain texture m_TerrainTexture = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, defaultSamplerDesc); // Initialise texture with solid black, for the areas we don't // overwrite with glTexSubImage2D later u32* texData = new u32[m_TextureSize * m_TextureSize]; for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i) texData[i] = 0xFF000000; g_Renderer.BindTexture(0, m_TerrainTexture->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); delete[] texData; m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; m_FinalTexture = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::R8G8B8A8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc); g_Renderer.BindTexture(0, m_FinalTexture->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glGenFramebuffersEXT(1, &m_FinalTextureFBO); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FinalTextureFBO); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FinalTexture->GetHandle(), 0); GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("MiniMapTexture Framebuffer object incomplete (A): 0x%04X", status); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } void CMiniMapTexture::DestroyTextures() { m_TerrainTexture.reset(); m_FinalTexture.reset(); SAFE_ARRAY_DELETE(m_TerrainData); } void CMiniMapTexture::RebuildTerrainTexture(const CTerrain* terrain) { const u32 x = 0; const u32 y = 0; const u32 width = m_MapSize - 1; const u32 height = m_MapSize - 1; m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; m_TerrainTextureDirty = false; for (u32 j = 0; j < height; ++j) { u32* dataPtr = m_TerrainData + ((y + j) * width) + x; for (u32 i = 0; i < width; ++i) { const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j) + terrain->GetVertexGroundLevel((int)i+1, (int)j) + terrain->GetVertexGroundLevel((int)i, (int)j+1) + terrain->GetVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) { // shallow water *dataPtr++ = 0xffc09870; } else if (avgHeight < m_WaterHeight) { // Set water as constant color for consistency on different maps *dataPtr++ = 0xffa07850; } else { int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0xFFFFFFFF; CMiniPatch* mp = terrain->GetTile(x + i, y + j); if (mp) { CTerrainTextureEntry* tex = mp->GetTextureEntry(); if (tex) { // If the texture can't be loaded yet, set the dirty flags // so we'll try regenerating the terrain texture again soon if(!tex->GetTexture()->TryLoad()) m_TerrainTextureDirty = true; color = tex->GetBaseColor(); } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture g_Renderer.BindTexture(0, m_TerrainTexture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData); g_Renderer.BindTexture(0, 0); } void CMiniMapTexture::RenderFinalTexture() { // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation const double currentTime = timer_Time(); const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty; if (doUpdate) m_LastFinalTextureUpdate = currentTime; else return; m_FinalTextureDirty = false; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FinalTextureFBO); const SViewPort oldViewPort = g_Renderer.GetViewport(); const SViewPort viewPort = { 0, 0, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE }; g_Renderer.SetViewport(viewPort); CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); ENSURE(cmpRangeManager); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize); const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; CShaderProgramPtr shader; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines); tech->BeginPass(); shader = tech->GetShader(); if (m_TerrainTexture) - shader->BindTexture(str_baseTex, m_TerrainTexture->GetHandle()); + shader->BindTexture(str_baseTex, m_TerrainTexture.get()); CMatrix3D baseTransform; baseTransform.SetIdentity(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); CMatrix3D terrainTransform; terrainTransform.SetIdentity(); terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, terrainTransform); if (m_TerrainTexture) DrawTexture(shader); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColorMask(1, 1, 1, 0); // Draw territory boundaries CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); shader->Uniform(str_transform, baseTransform); CMatrix3D territoryTransform = *territoryTexture.GetMinimapTextureMatrix(); shader->Uniform(str_textureTransform, territoryTransform); DrawTexture(shader); glDisable(GL_BLEND); glColorMask(0, 0, 0, 1); shader->BindTexture(str_baseTex, losTexture.GetTexture()); CMatrix3D losTransform = *losTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, losTransform); DrawTexture(shader); tech->EndPass(); glColorMask(1, 1, 1, 1); CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines); tech->BeginPass(); shader = tech->GetShader(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_pointSize, 9.0f); CMatrix3D unitMatrix; unitMatrix.SetIdentity(); // Convert world space coordinates into [0, 2]. const float unitScale = invTileMapSize; unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f); // Offset the coordinates to [-1, 1]. unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f)); shader->Uniform(str_transform, unitMatrix); CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap); if (doUpdate) { VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); if (currentTime > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = currentTime + m_HalfBlinkDuration; } entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer()); if (vis != LosVisibility::HIDDEN) { v.a = 255; v.x = posX.ToFloat(); v.y = posZ.ToFloat(); // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { addVertex(v, attrColor, attrPos); ++m_EntitiesDrawn; } } } } // Add the pinged vertices at the end, so they are drawn on top for (const MinimapUnitVertex& vertex : pingingVertices) { addVertex(vertex, attrColor, attrPos); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); m_VertexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { glEnable(GL_SCISSOR_TEST); glScissor(1, 1, FINAL_TEXTURE_SIZE - 1, FINAL_TEXTURE_SIZE - 1); #if !CONFIG2_GLES glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif u8* indexBase = m_IndexArray.Bind(); u8* base = m_VertexArray.Bind(); const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); shader->AssertPointersBound(); if (!g_Renderer.DoSkipSubmit()) glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); g_Renderer.GetStats().m_DrawCalls++; CVertexBuffer::Unbind(); #if !CONFIG2_GLES glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif glDisable(GL_SCISSOR_TEST); } tech->EndPass(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); g_Renderer.SetViewport(oldViewPort); } // static float CMiniMapTexture::GetShallowPassageHeight() { float shallowPassageHeight = 0.0f; CParamNode externalParamNode; CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); return shallowPassageHeight; } Index: ps/trunk/source/graphics/MiniMapTexture.h =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.h (revision 26142) +++ ps/trunk/source/graphics/MiniMapTexture.h (revision 26143) @@ -1,96 +1,96 @@ /* 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_MINIMAPTEXTURE #define INCLUDED_MINIMAPTEXTURE #include "lib/ogl.h" #include "renderer/backend/gl/Texture.h" #include "renderer/VertexArray.h" class CSimulation2; class CTerrain; class CMiniMapTexture { NONCOPYABLE(CMiniMapTexture); public: CMiniMapTexture(CSimulation2& simulation); ~CMiniMapTexture(); /** * Marks the texture as dirty if it's old enough to redraw it on Render. */ void Update(const float deltaRealTime); /** * Redraws the texture if it's dirty. */ void Render(); - GLuint GetTexture() const { return m_FinalTexture->GetHandle(); } + Renderer::Backend::GL::CTexture* GetTexture() const { return m_FinalTexture.get(); } /** * @return The maximum height for unit passage in water. */ static float GetShallowPassageHeight(); private: void CreateTextures(const CTerrain* terrain); void DestroyTextures(); void RebuildTerrainTexture(const CTerrain* terrain); void RenderFinalTexture(); CSimulation2& m_Simulation; bool m_TerrainTextureDirty = true; bool m_FinalTextureDirty = true; double m_LastFinalTextureUpdate = 0.0; // minimap texture handles std::unique_ptr m_TerrainTexture, m_FinalTexture; GLuint m_FinalTextureFBO = 0; // texture data u32* m_TerrainData = nullptr; // map size ssize_t m_MapSize = 0; // texture size GLsizei m_TextureSize = 0; // Maximal water height to allow the passage of a unit (for underwater shallows). float m_ShallowPassageHeight = 0.0f; float m_WaterHeight = 0.0f; VertexIndexArray m_IndexArray; VertexArray m_VertexArray; VertexArray::Attribute m_AttributePos; VertexArray::Attribute m_AttributeColor; size_t m_EntitiesDrawn = 0; double m_PingDuration = 25.0; double m_HalfBlinkDuration = 0.0; double m_NextBlinkTime = 0.0; bool m_BlinkState = false; }; #endif // INCLUDED_MINIMAPTEXTURE Index: ps/trunk/source/graphics/ShaderProgram.cpp =================================================================== --- ps/trunk/source/graphics/ShaderProgram.cpp (revision 26142) +++ ps/trunk/source/graphics/ShaderProgram.cpp (revision 26143) @@ -1,930 +1,940 @@ /* 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 "ShaderProgram.h" #include "graphics/Color.h" #include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "lib/timer.h" #include "lib/res/graphics/ogl_tex.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include #if !CONFIG2_GLES class CShaderProgramARB : public CShaderProgram { public: CShaderProgramARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) : CShaderProgram(streamflags), m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), m_Defines(defines), m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes) { glGenProgramsARB(1, &m_VertexProgram); glGenProgramsARB(1, &m_FragmentProgram); } ~CShaderProgramARB() { Unload(); glDeleteProgramsARB(1, &m_VertexProgram); glDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); glBindProgramARB(target, program); ogl_WarnIfError(); glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); if (ogl_SquelchError(GL_INVALID_OPERATION)) { GLint errPos = 0; glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1; char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr); return false; } glBindProgramARB(target, 0); ogl_WarnIfError(); return true; } void Reload() override { Unload(); CVFSFile vertexFile; if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) return; CVFSFile fragmentFile; if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) return; CPreprocessorWrapper preprocessor; preprocessor.AddDefines(m_Defines); CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString()); CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString()); if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, m_VertexFile, vertexCode)) return; if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, m_FragmentFile, fragmentCode)) return; m_IsValid = true; } void Unload() { m_IsValid = false; } void Bind() override { glEnable(GL_VERTEX_PROGRAM_ARB); glEnable(GL_FRAGMENT_PROGRAM_ARB); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); BindClientStates(); } void Unbind() override { glDisable(GL_VERTEX_PROGRAM_ARB); glDisable(GL_FRAGMENT_PROGRAM_ARB); glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); UnbindClientStates(); // TODO: should unbind textures, probably } int GetUniformVertexIndex(CStrIntern id) { std::map::iterator it = m_VertexIndexes.find(id); if (it == m_VertexIndexes.end()) return -1; return it->second; } frag_index_pair_t GetUniformFragmentIndex(CStrIntern id) { std::map::iterator it = m_FragmentIndexes.find(id); if (it == m_FragmentIndexes.end()) return std::make_pair(-1, 0); return it->second; } Binding GetTextureBinding(texture_id_t id) override { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index == -1) return Binding(); else return Binding((int)fPair.second, index); } void BindTexture(texture_id_t id, GLuint tex) override { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { glActiveTextureARB(GL_TEXTURE0 + index); glBindTexture(fPair.second, tex); } } void BindTexture(Binding id, GLuint tex) override { int index = id.second; if (index != -1) { glActiveTextureARB(GL_TEXTURE0 + index); glBindTexture(id.first, tex); } } Binding GetUniformBinding(uniform_id_t id) override { return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first); } void Uniform(Binding id, float v0, float v1, float v2, float v3) override { if (id.first != -1) glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3); if (id.second != -1) glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3); } void Uniform(Binding id, const CMatrix3D& v) override { if (id.first != -1) { glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14); glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24); glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34); glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44); } if (id.second != -1) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44); } } void Uniform(Binding id, size_t count, const CMatrix3D* v) override { ENSURE(count == 1); Uniform(id, v[0]); } void Uniform(Binding id, size_t count, const float* v) override { ENSURE(count == 4); Uniform(id, v[0], v[1], v[2], v[3]); } std::vector GetFileDependencies() const override { return {m_VertexFile, m_FragmentFile}; } private: VfsPath m_VertexFile; VfsPath m_FragmentFile; CShaderDefines m_Defines; GLuint m_VertexProgram; GLuint m_FragmentProgram; std::map m_VertexIndexes; // pair contains std::map m_FragmentIndexes; }; #endif // #if !CONFIG2_GLES ////////////////////////////////////////////////////////////////////////// TIMER_ADD_CLIENT(tc_ShaderGLSLCompile); TIMER_ADD_CLIENT(tc_ShaderGLSLLink); class CShaderProgramGLSL : public CShaderProgram { public: CShaderProgramGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) : CShaderProgram(streamflags), m_VertexFile(vertexFile), m_FragmentFile(fragmentFile), m_Defines(defines), m_VertexAttribs(vertexAttribs) { m_Program = 0; m_VertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER); m_FragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER); m_FileDependencies = {m_VertexFile, m_FragmentFile}; } ~CShaderProgramGLSL() { Unload(); glDeleteShader(m_VertexShader); glDeleteShader(m_FragmentShader); } bool Compile(GLhandleARB shader, const VfsPath& file, const CStr& code) { TIMER_ACCRUE(tc_ShaderGLSLCompile); ogl_WarnIfError(); const char* code_string = code.c_str(); GLint code_length = code.length(); glShaderSourceARB(shader, 1, &code_string, &code_length); glCompileShaderARB(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); // Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0 // (http://code.google.com/p/android/issues/detail?id=9953) if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetShaderInfoLog(shader, length, NULL, infolog); if (ok) LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog); else LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); return (ok ? true : false); } bool Link() { TIMER_ACCRUE(tc_ShaderGLSLLink); ENSURE(!m_Program); m_Program = glCreateProgramObjectARB(); glAttachObjectARB(m_Program, m_VertexShader); ogl_WarnIfError(); glAttachObjectARB(m_Program, m_FragmentShader); ogl_WarnIfError(); // Set up the attribute bindings explicitly, since apparently drivers // don't always pick the most efficient bindings automatically, // and also this lets us hardcode indexes into VertexPointer etc for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) glBindAttribLocationARB(m_Program, it->second, it->first.c_str()); glLinkProgramARB(m_Program); GLint ok = 0; glGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetProgramInfoLog(m_Program, length, NULL, infolog); if (ok) LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog); else LOGERROR("Failed to link program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); if (!ok) return false; m_Uniforms.clear(); m_Samplers.clear(); Bind(); ogl_WarnIfError(); GLint numUniforms = 0; glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms); ogl_WarnIfError(); for (GLint i = 0; i < numUniforms; ++i) { char name[256] = {0}; GLsizei nameLength = 0; GLint size = 0; GLenum type = 0; glGetActiveUniformARB(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); GLint loc = glGetUniformLocationARB(m_Program, name); CStrIntern nameIntern(name); m_Uniforms[nameIntern] = std::make_pair(loc, type); // Assign sampler uniforms to sequential texture units if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE #if !CONFIG2_GLES || type == GL_SAMPLER_2D_SHADOW #endif ) { int unit = (int)m_Samplers.size(); m_Samplers[nameIntern].first = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D); m_Samplers[nameIntern].second = unit; glUniform1iARB(loc, unit); // link uniform to unit ogl_WarnIfError(); } } // TODO: verify that we're not using more samplers than is supported Unbind(); ogl_WarnIfError(); return true; } void Reload() override { Unload(); CVFSFile vertexFile; if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK) return; CVFSFile fragmentFile; if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK) return; std::vector newFileDependencies = {m_VertexFile, m_FragmentFile}; CPreprocessorWrapper preprocessor([&newFileDependencies](const CStr& includePath, CStr& out) -> bool { const VfsPath includeFilePath(L"shaders/glsl/" + wstring_from_utf8(includePath)); // Add dependencies anyway to reload the shader when the file is // appeared. newFileDependencies.push_back(includeFilePath); CVFSFile includeFile; if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK) return false; out = includeFile.GetAsString(); return true; }); preprocessor.AddDefines(m_Defines); #if CONFIG2_GLES // GLES defines the macro "GL_ES" in its GLSL preprocessor, // but since we run our own preprocessor first, we need to explicitly // define it here preprocessor.AddDefine("GL_ES", "1"); #endif CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString()); CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString()); m_FileDependencies = std::move(newFileDependencies); if (vertexCode.empty()) LOGERROR("Failed to preprocess vertex shader: '%s'", m_VertexFile.string8()); if (fragmentCode.empty()) LOGERROR("Failed to preprocess fragment shader: '%s'", m_FragmentFile.string8()); #if CONFIG2_GLES // Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00, // and also to set default float precision for fragment shaders vertexCode.Replace("#version 110\n", "#version 100\n"); vertexCode.Replace("#version 110\r\n", "#version 100\n"); vertexCode.Replace("#version 120\n", "#version 100\n"); vertexCode.Replace("#version 120\r\n", "#version 100\n"); fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n"); #endif if (!Compile(m_VertexShader, m_VertexFile, vertexCode)) return; if (!Compile(m_FragmentShader, m_FragmentFile, fragmentCode)) return; if (!Link()) return; m_IsValid = true; } void Unload() { m_IsValid = false; if (m_Program) glDeleteProgram(m_Program); m_Program = 0; // The shader objects can be reused and don't need to be deleted here } void Bind() override { glUseProgramObjectARB(m_Program); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) glEnableVertexAttribArrayARB(it->second); } void Unbind() override { glUseProgramObjectARB(0); for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) glDisableVertexAttribArrayARB(it->second); // TODO: should unbind textures, probably } Binding GetTextureBinding(texture_id_t id) override { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return Binding(); else return Binding((int)it->second.first, it->second.second); } void BindTexture(texture_id_t id, GLuint tex) override { std::map >::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; glActiveTextureARB(GL_TEXTURE0 + it->second.second); glBindTexture(it->second.first, tex); } void BindTexture(Binding id, GLuint tex) override { if (id.second == -1) return; glActiveTextureARB(GL_TEXTURE0 + id.second); glBindTexture(id.first, tex); } Binding GetUniformBinding(uniform_id_t id) override { std::map >::iterator it = m_Uniforms.find(id); if (it == m_Uniforms.end()) return Binding(); else return Binding(it->second.first, (int)it->second.second); } void Uniform(Binding id, float v0, float v1, float v2, float v3) override { if (id.first != -1) { if (id.second == GL_FLOAT) glUniform1fARB(id.first, v0); else if (id.second == GL_FLOAT_VEC2) glUniform2fARB(id.first, v0, v1); else if (id.second == GL_FLOAT_VEC3) glUniform3fARB(id.first, v0, v1, v2); else if (id.second == GL_FLOAT_VEC4) glUniform4fARB(id.first, v0, v1, v2, v3); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)"); } } void Uniform(Binding id, const CMatrix3D& v) override { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) glUniformMatrix4fvARB(id.first, 1, GL_FALSE, &v._11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } void Uniform(Binding id, size_t count, const CMatrix3D* v) override { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) glUniformMatrix4fvARB(id.first, count, GL_FALSE, &v->_11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } void Uniform(Binding id, size_t count, const float* v) override { if (id.first != -1) { if (id.second == GL_FLOAT) glUniform1fvARB(id.first, count, v); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float)"); } } // Map the various fixed-function Pointer functions onto generic vertex attributes // (matching the attribute indexes from ShaderManager's ParseAttribSemantics): void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) override { glVertexAttribPointerARB(0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_POS; } void NormalPointer(GLenum type, GLsizei stride, const void* pointer) override { glVertexAttribPointerARB(2, 3, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer); m_ValidStreams |= STREAM_NORMAL; } void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) override { glVertexAttribPointerARB(3, size, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer); m_ValidStreams |= STREAM_COLOR; } void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) override { glVertexAttribPointerARB(8 + texture - GL_TEXTURE0, size, type, GL_FALSE, stride, pointer); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) override { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { glVertexAttribPointerARB(it->second, size, type, normalized, stride, pointer); } } void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer) override { std::map::iterator it = m_VertexAttribs.find(id); if (it != m_VertexAttribs.end()) { #if CONFIG2_GLES debug_warn(L"glVertexAttribIPointer not supported on GLES"); #else glVertexAttribIPointerEXT(it->second, size, type, stride, pointer); #endif } } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: VfsPath m_VertexFile; VfsPath m_FragmentFile; std::vector m_FileDependencies; CShaderDefines m_Defines; std::map m_VertexAttribs; GLhandleARB m_Program; GLhandleARB m_VertexShader; GLhandleARB m_FragmentShader; std::map > m_Uniforms; std::map > m_Samplers; // texture target & unit chosen for each uniform sampler }; ////////////////////////////////////////////////////////////////////////// CShaderProgram::CShaderProgram(int streamflags) : m_IsValid(false), m_StreamFlags(streamflags), m_ValidStreams(0) { } #if CONFIG2_GLES /*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& UNUSED(defines), const std::map& UNUSED(vertexIndexes), const std::map& UNUSED(fragmentIndexes), int UNUSED(streamflags)) { LOGERROR("CShaderProgram::ConstructARB: '%s'+'%s': ARB shaders not supported on this device", vertexFile.string8(), fragmentFile.string8()); return NULL; } #else /*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) { return new CShaderProgramARB(vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags); } #endif /*static*/ CShaderProgram* CShaderProgram::ConstructGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) { return new CShaderProgramGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamflags); } bool CShaderProgram::IsValid() const { return m_IsValid; } int CShaderProgram::GetStreamFlags() const { return m_StreamFlags; } void CShaderProgram::BindTexture(texture_id_t id, const CTexturePtr& tex) { GLuint h; ogl_tex_get_texture_id(tex->GetHandle(), &h); BindTexture(id, h); } void CShaderProgram::BindTexture(Binding id, const CTexturePtr& tex) { GLuint h; ogl_tex_get_texture_id(tex->GetHandle(), &h); BindTexture(id, h); } +void CShaderProgram::BindTexture(texture_id_t id, const Renderer::Backend::GL::CTexture* tex) +{ + BindTexture(id, tex->GetHandle()); +} + +void CShaderProgram::BindTexture(Binding id, const Renderer::Backend::GL::CTexture* tex) +{ + BindTexture(id, tex->GetHandle()); +} + void CShaderProgram::Uniform(Binding id, int v) { Uniform(id, (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(Binding id, float v) { Uniform(id, v, v, v, v); } void CShaderProgram::Uniform(Binding id, float v0, float v1) { Uniform(id, v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(Binding id, const CVector3D& v) { Uniform(id, v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(Binding id, const CColor& v) { Uniform(id, v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, int v) { Uniform(GetUniformBinding(id), (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(uniform_id_t id, float v) { Uniform(GetUniformBinding(id), v, v, v, v); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1) { Uniform(GetUniformBinding(id), v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CVector3D& v) { Uniform(GetUniformBinding(id), v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CColor& v) { Uniform(GetUniformBinding(id), v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1, float v2, float v3) { Uniform(GetUniformBinding(id), v0, v1, v2, v3); } void CShaderProgram::Uniform(uniform_id_t id, const CMatrix3D& v) { Uniform(GetUniformBinding(id), v); } void CShaderProgram::Uniform(uniform_id_t id, size_t count, const CMatrix3D* v) { Uniform(GetUniformBinding(id), count, v); } void CShaderProgram::Uniform(uniform_id_t id, size_t count, const float* v) { Uniform(GetUniformBinding(id), count, v); } // These should all be overridden by CShaderProgramGLSL, and not used // if a non-GLSL shader was loaded instead: void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type), GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribPointer"); } void CShaderProgram::VertexAttribIPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribIPointer"); } #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL // (GLES doesn't support any other types of shader program): void CShaderProgram::VertexPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::VertexPointer should be overridden"); } void CShaderProgram::NormalPointer(GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::NormalPointer should be overridden"); } void CShaderProgram::ColorPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::ColorPointer should be overridden"); } void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::TexCoordPointer should be overridden"); } #else // These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders // both use the fixed-function vertex attribute pointers so we'll share their // definitions here: void CShaderProgram::VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { glVertexPointer(size, type, stride, pointer); m_ValidStreams |= STREAM_POS; } void CShaderProgram::NormalPointer(GLenum type, GLsizei stride, const void* pointer) { glNormalPointer(type, stride, pointer); m_ValidStreams |= STREAM_NORMAL; } void CShaderProgram::ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) { glColorPointer(size, type, stride, pointer); m_ValidStreams |= STREAM_COLOR; } void CShaderProgram::TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) { glClientActiveTextureARB(texture); glTexCoordPointer(size, type, stride, pointer); glClientActiveTextureARB(GL_TEXTURE0); m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0); } void CShaderProgram::BindClientStates() { ENSURE(m_StreamFlags == (m_StreamFlags & (STREAM_POS|STREAM_NORMAL|STREAM_COLOR|STREAM_UV0|STREAM_UV1))); // Enable all the desired client states for non-GLSL rendering if (m_StreamFlags & STREAM_POS) glEnableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & STREAM_NORMAL) glEnableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & STREAM_COLOR) glEnableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & STREAM_UV0) { glClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { glClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } // Rendering code must subsequently call VertexPointer etc for all of the streams // that were activated in this function, else AssertPointersBound will complain // that some arrays were unspecified m_ValidStreams = 0; } void CShaderProgram::UnbindClientStates() { if (m_StreamFlags & STREAM_POS) glDisableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & STREAM_NORMAL) glDisableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & STREAM_COLOR) glDisableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & STREAM_UV0) { glClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & STREAM_UV1) { glClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } Index: ps/trunk/source/graphics/ShaderProgram.h =================================================================== --- ps/trunk/source/graphics/ShaderProgram.h (revision 26142) +++ ps/trunk/source/graphics/ShaderProgram.h (revision 26143) @@ -1,206 +1,209 @@ /* 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_SHADERPROGRAM #define INCLUDED_SHADERPROGRAM #include "graphics/ShaderProgramPtr.h" #include "graphics/Texture.h" #include "lib/ogl.h" #include "lib/file/vfs/vfs_path.h" +#include "renderer/backend/gl/Texture.h" #include #include struct CColor; class CMatrix3D; class CVector3D; class CShaderDefines; class CStrIntern; // Vertex data stream flags enum { STREAM_POS = (1 << 0), STREAM_NORMAL = (1 << 1), STREAM_COLOR = (1 << 2), STREAM_UV0 = (1 << 3), STREAM_UV1 = (1 << 4), STREAM_UV2 = (1 << 5), STREAM_UV3 = (1 << 6), STREAM_POSTOUV0 = (1 << 7), STREAM_POSTOUV1 = (1 << 8), STREAM_POSTOUV2 = (1 << 9), STREAM_POSTOUV3 = (1 << 10) }; /** * A compiled vertex+fragment shader program. * The implementation may use GL_ARB_{vertex,fragment}_program (ARB assembly syntax) * or GL_ARB_{vertex,fragment}_shader (GLSL), or may use hard-coded fixed-function * multitexturing setup code; the difference is hidden from the caller. * * Texture/uniform IDs are typically strings, corresponding to the names defined in * the shader .xml file. Alternatively (and more efficiently, if used very frequently), * call GetTextureBinding/GetUniformBinding and pass its return value as the ID. * Setting uniforms that the shader .xml doesn't support is harmless. * * For a high-level overview of shaders and materials, see * http://trac.wildfiregames.com/wiki/MaterialSystem */ class CShaderProgram { NONCOPYABLE(CShaderProgram); public: typedef CStrIntern attrib_id_t; typedef CStrIntern texture_id_t; typedef CStrIntern uniform_id_t; typedef std::pair frag_index_pair_t; /** * Construct based on ARB vertex/fragment program files. */ static CShaderProgram* ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags); /** * Construct based on GLSL vertex/fragment shader files. */ static CShaderProgram* ConstructGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags); /** * Represents a uniform attribute or texture binding. * For uniforms: * - ARB shaders store vertex location in 'first', fragment location in 'second'. * - GLSL shaders store uniform location in 'first', data type in 'second'. * For textures, all store texture target (e.g. GL_TEXTURE_2D) in 'first', texture unit in 'second'. * Non-existent bindings must store -1 in both. */ struct Binding { Binding(int a, int b) : first(a), second(b) { } Binding() : first(-1), second(-1) { } /** * Returns whether this uniform attribute is active in the shader. * If not then there's no point calling Uniform() to set its value. */ bool Active() const { return first != -1 || second != -1; } int first; int second; }; virtual ~CShaderProgram() { } virtual void Reload() = 0; /** * Returns whether this shader was successfully loaded. */ bool IsValid() const; /** * Binds the shader into the GL context. Call this before calling Uniform() * or trying to render with it. */ virtual void Bind() = 0; /** * Unbinds the shader from the GL context. Call this after rendering with it. */ virtual void Unbind() = 0; /** * Returns bitset of STREAM_* value, indicating what vertex data streams the * vertex shader needs (e.g. position, color, UV, ...). */ int GetStreamFlags() const; virtual Binding GetTextureBinding(texture_id_t id) = 0; // Variants of texture binding: void BindTexture(texture_id_t id, const CTexturePtr& tex); void BindTexture(Binding id, const CTexturePtr& tex); - virtual void BindTexture(texture_id_t id, GLuint tex) = 0; - virtual void BindTexture(Binding id, GLuint tex) = 0; - + void BindTexture(texture_id_t id, const Renderer::Backend::GL::CTexture* tex); + void BindTexture(Binding id, const Renderer::Backend::GL::CTexture* tex); virtual Binding GetUniformBinding(uniform_id_t id) = 0; // Uniform-setting methods that subclasses must define: virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) = 0; virtual void Uniform(Binding id, const CMatrix3D& v) = 0; virtual void Uniform(Binding id, size_t count, const CMatrix3D* v) = 0; virtual void Uniform(Binding id, size_t count, const float* v) = 0; // Convenient uniform-setting wrappers: void Uniform(Binding id, int v); void Uniform(Binding id, float v); void Uniform(Binding id, float v0, float v1); void Uniform(Binding id, const CVector3D& v); void Uniform(Binding id, const CColor& v); void Uniform(uniform_id_t id, int v); void Uniform(uniform_id_t id, float v); void Uniform(uniform_id_t id, float v0, float v1); void Uniform(uniform_id_t id, const CVector3D& v); void Uniform(uniform_id_t id, const CColor& v); void Uniform(uniform_id_t id, float v0, float v1, float v2, float v3); void Uniform(uniform_id_t id, const CMatrix3D& v); void Uniform(uniform_id_t id, size_t count, const CMatrix3D* v); void Uniform(uniform_id_t id, size_t count, const float* v); // Vertex attribute pointers (equivalent to glVertexPointer etc): virtual void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer); virtual void NormalPointer(GLenum type, GLsizei stride, const void* pointer); virtual void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer); virtual void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer); virtual void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); virtual void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer); /** * Checks that all the required vertex attributes have been set. * Call this before calling glDrawArrays/glDrawElements etc to avoid potential crashes. */ void AssertPointersBound(); virtual std::vector GetFileDependencies() const = 0; protected: CShaderProgram(int streamflags); + virtual void BindTexture(texture_id_t id, GLuint tex) = 0; + virtual void BindTexture(Binding id, GLuint tex) = 0; + bool m_IsValid; int m_StreamFlags; // Non-GLSL client state handling: void BindClientStates(); void UnbindClientStates(); int m_ValidStreams; // which streams have been specified via VertexPointer etc since the last Bind }; #endif // INCLUDED_SHADERPROGRAM Index: ps/trunk/source/graphics/TerritoryTexture.cpp =================================================================== --- ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26142) +++ ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26143) @@ -1,245 +1,245 @@ /* 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 "TerritoryTexture.h" #include "graphics/Color.h" #include "graphics/Terrain.h" #include "lib/bits.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include "simulation2/helpers/Grid.h" #include "simulation2/helpers/Pathfinding.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpTerritoryManager.h" // TODO: There's a lot of duplication with CLOSTexture - might be nice to refactor a bit CTerritoryTexture::CTerritoryTexture(CSimulation2& simulation) : - m_Simulation(simulation), m_DirtyID(0), m_MapSize(0), m_TextureSize(0) + m_Simulation(simulation), m_DirtyID(0), m_MapSize(0) { } CTerritoryTexture::~CTerritoryTexture() { DeleteTexture(); } void CTerritoryTexture::DeleteTexture() { m_Texture.reset(); } bool CTerritoryTexture::UpdateDirty() { CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); return cmpTerritoryManager && cmpTerritoryManager->NeedUpdateTexture(&m_DirtyID); } -GLuint CTerritoryTexture::GetTexture() +Renderer::Backend::GL::CTexture* CTerritoryTexture::GetTexture() { if (UpdateDirty()) RecomputeTexture(); - return m_Texture->GetHandle(); + return m_Texture.get(); } const float* CTerritoryTexture::GetTextureMatrix() { ENSURE(!UpdateDirty()); return &m_TextureMatrix._11; } const CMatrix3D* CTerritoryTexture::GetMinimapTextureMatrix() { ENSURE(!UpdateDirty()); return &m_MinimapTextureMatrix; } void CTerritoryTexture::ConstructTexture() { CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (!cmpTerrain) return; // Convert size from terrain tiles to territory tiles m_MapSize = cmpTerrain->GetMapSize() * Pathfinding::NAVCELL_SIZE_INT / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE; - m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); + const uint32_t textureSize = round_up_to_pow2(static_cast(m_MapSize)); m_Texture = Renderer::Backend::GL::CTexture::Create2D( - Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, + Renderer::Backend::Format::R8G8B8A8, textureSize, textureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); // Initialise texture with transparency, for the areas we don't // overwrite with glTexSubImage2D later - u8* texData = new u8[m_TextureSize * m_TextureSize * 4]; - memset(texData, 0x00, m_TextureSize * m_TextureSize * 4); + u8* texData = new u8[textureSize * textureSize * 4]; + memset(texData, 0x00, textureSize * textureSize * 4); g_Renderer.BindTexture(0, m_Texture->GetHandle()); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureSize, textureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); delete[] texData; { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. bottom-left of first tile) // onto texcoord (0, 0) (i.e. bottom-left of first texel); // world pos (mapsize*cellsize, y, mapsize*cellsize) (i.e. top-right of last tile) // onto texcoord (mapsize / texsize, mapsize / texsize) (i.e. top-right of last texel) - float s = 1.f / (float)(m_TextureSize * TERRAIN_TILE_SIZE); + float s = 1.f / static_cast(textureSize * TERRAIN_TILE_SIZE); float t = 0.f; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) - float s = m_MapSize / (float)m_TextureSize; + float s = m_MapSize / static_cast(textureSize); m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CTerritoryTexture::RecomputeTexture() { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide()) DeleteTexture(); } if (!m_Texture) ConstructTexture(); PROFILE("recompute territory texture"); CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); if (!cmpTerritoryManager) return; std::vector bitmap(m_MapSize * m_MapSize * 4); GenerateBitmap(cmpTerritoryManager->GetTerritoryGrid(), &bitmap[0], m_MapSize, m_MapSize); g_Renderer.BindTexture(0, m_Texture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize, m_MapSize, GL_RGBA, GL_UNSIGNED_BYTE, &bitmap[0]); } void CTerritoryTexture::GenerateBitmap(const Grid& territories, u8* bitmap, ssize_t w, ssize_t h) { int alphaMax = 0xC0; int alphaFalloff = 0x20; CmpPtr cmpPlayerManager(m_Simulation, SYSTEM_ENTITY); std::vector colors; i32 numPlayers = cmpPlayerManager->GetNumPlayers(); for (i32 p = 0; p < numPlayers; ++p) { CColor color(1, 0, 1, 1); CmpPtr cmpPlayer(m_Simulation, cmpPlayerManager->GetPlayerByID(p)); if (cmpPlayer) color = cmpPlayer->GetDisplayedColor(); colors.push_back(color); } u8* p = bitmap; for (ssize_t j = 0; j < h; ++j) for (ssize_t i = 0; i < w; ++i) { u8 val = territories.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK; CColor color(1, 0, 1, 1); if (val < colors.size()) color = colors[val]; *p++ = (int)(color.r * 255.f); *p++ = (int)(color.g * 255.f); *p++ = (int)(color.b * 255.f); // Use alphaMax for borders and gaia territory; these tiles will be deleted later if (val == 0 || (i > 0 && (territories.get(i-1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) || (i < w-1 && (territories.get(i+1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) || (j > 0 && (territories.get(i, j-1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) || (j < h-1 && (territories.get(i, j+1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val)) *p++ = alphaMax; else *p++ = 0x00; } // Do a low-quality cheap blur effect for (ssize_t j = 0; j < h; ++j) { int a; a = 0; for (ssize_t i = 0; i < w; ++i) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } a = 0; for (ssize_t i = w-1; i >= 0; --i) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } } for (ssize_t i = 0; i < w; ++i) { int a; a = 0; for (ssize_t j = 0; j < w; ++j) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } a = 0; for (ssize_t j = w-1; j >= 0; --j) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } } // Add a gap between the boundaries, by deleting the max-alpha tiles for (ssize_t j = 0; j < h; ++j) for (ssize_t i = 0; i < w; ++i) if (bitmap[(j*w+i)*4 + 3] == alphaMax) bitmap[(j*w+i)*4 + 3] = 0; } Index: ps/trunk/source/graphics/TerritoryTexture.h =================================================================== --- ps/trunk/source/graphics/TerritoryTexture.h (revision 26142) +++ ps/trunk/source/graphics/TerritoryTexture.h (revision 26143) @@ -1,82 +1,80 @@ /* 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 "lib/ogl.h" #include "maths/Matrix3D.h" #include "renderer/backend/gl/Texture.h" class CSimulation2; template class Grid; /** * Maintains the territory boundary texture, used for * rendering and for the minimap. */ class CTerritoryTexture { NONCOPYABLE(CTerritoryTexture); public: CTerritoryTexture(CSimulation2& simulation); ~CTerritoryTexture(); /** * Recomputes the territory texture if necessary, and returns the texture handle. * Also potentially switches the current active texture unit, and enables texturing on it. * The texture is in 32-bit BGRA format. */ - GLuint GetTexture(); + Renderer::Backend::GL::CTexture* GetTexture(); /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) texture * coordinates, in the form expected by a matrix uniform. * This must only be called after BindTexture. */ const float* GetTextureMatrix(); /** * Returns a matrix to map (0,0)-(1,1) texture coordinates onto texture * coordinates, in the form expected by a matrix uniform. * This must only be called after BindTexture. */ const CMatrix3D* GetMinimapTextureMatrix(); private: /** * Returns true if the territory state has changed since the last call to this function */ bool UpdateDirty(); void DeleteTexture(); void ConstructTexture(); void RecomputeTexture(); void GenerateBitmap(const Grid& territories, u8* bitmap, ssize_t w, ssize_t h); CSimulation2& m_Simulation; size_t m_DirtyID; std::unique_ptr m_Texture; ssize_t m_MapSize; // tiles per side - GLsizei m_TextureSize; // texels per side CMatrix3D m_TextureMatrix; CMatrix3D m_MinimapTextureMatrix; }; Index: ps/trunk/source/renderer/PatchRData.cpp =================================================================== --- ps/trunk/source/renderer/PatchRData.cpp (revision 26142) +++ ps/trunk/source/renderer/PatchRData.cpp (revision 26143) @@ -1,1445 +1,1445 @@ /* 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 "renderer/PatchRData.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TextRenderer.h" #include "lib/allocators/DynamicArena.h" #include "lib/allocators/STLAllocators.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/AlphaMapCalculator.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/TerrainRenderer.h" #include "renderer/WaterManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/Simulation2.h" #include #include #include const ssize_t BlendOffsets[9][2] = { { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 }, { 0, 0 } }; CPatchRData::CPatchRData(CPatch* patch, CSimulation2* simulation) : m_Patch(patch), m_VBSides(), m_VBBase(), m_VBBaseIndices(), m_VBBlends(), m_VBBlendIndices(), m_VBWater(), m_VBWaterIndices(), m_VBWaterShore(), m_VBWaterIndicesShore(), m_Simulation(simulation) { ENSURE(patch); Build(); } CPatchRData::~CPatchRData() = default; /** * Represents a blend for a single tile, texture and shape. */ struct STileBlend { CTerrainTextureEntry* m_Texture; int m_Priority; u16 m_TileMask; // bit n set if this blend contains neighbour tile BlendOffsets[n] struct DecreasingPriority { bool operator()(const STileBlend& a, const STileBlend& b) const { if (a.m_Priority > b.m_Priority) return true; if (a.m_Priority < b.m_Priority) return false; if (a.m_Texture && b.m_Texture) return a.m_Texture->GetTag() > b.m_Texture->GetTag(); return false; } }; struct CurrentTile { bool operator()(const STileBlend& a) const { return (a.m_TileMask & (1 << 8)) != 0; } }; }; /** * Represents the ordered collection of blends drawn on a particular tile. */ struct STileBlendStack { u8 i, j; std::vector blends; // back of vector is lowest-priority texture }; /** * Represents a batched collection of blends using the same texture. */ struct SBlendLayer { struct Tile { u8 i, j; u8 shape; }; CTerrainTextureEntry* m_Texture; std::vector m_Tiles; }; void CPatchRData::BuildBlends() { PROFILE3("build blends"); m_BlendSplats.clear(); std::vector blendVertices; std::vector blendIndices; CTerrain* terrain = m_Patch->m_Parent; std::vector blendStacks; blendStacks.reserve(PATCH_SIZE*PATCH_SIZE); std::vector blends; blends.reserve(9); // For each tile in patch .. for (ssize_t j = 0; j < PATCH_SIZE; ++j) { for (ssize_t i = 0; i < PATCH_SIZE; ++i) { ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; blends.clear(); // Compute a blend for every tile in the 3x3 square around this tile for (size_t n = 0; n < 9; ++n) { ssize_t ox = gx + BlendOffsets[n][1]; ssize_t oz = gz + BlendOffsets[n][0]; CMiniPatch* nmp = terrain->GetTile(ox, oz); if (!nmp) continue; STileBlend blend; blend.m_Texture = nmp->GetTextureEntry(); blend.m_Priority = nmp->GetPriority(); blend.m_TileMask = 1 << n; blends.push_back(blend); } // Sort the blends, highest priority first std::sort(blends.begin(), blends.end(), STileBlend::DecreasingPriority()); STileBlendStack blendStack; blendStack.i = i; blendStack.j = j; // Put the blends into the tile's stack, merging any adjacent blends with the same texture for (size_t k = 0; k < blends.size(); ++k) { if (!blendStack.blends.empty() && blendStack.blends.back().m_Texture == blends[k].m_Texture) blendStack.blends.back().m_TileMask |= blends[k].m_TileMask; else blendStack.blends.push_back(blends[k]); } // Remove blends that are after (i.e. lower priority than) the current tile // (including the current tile), since we don't want to render them on top of // the tile's base texture blendStack.blends.erase( std::find_if(blendStack.blends.begin(), blendStack.blends.end(), STileBlend::CurrentTile()), blendStack.blends.end()); blendStacks.push_back(blendStack); } } // Given the blend stack per tile, we want to batch together as many blends as possible. // Group them into a series of layers (each of which has a single texture): // (This is effectively a topological sort / linearisation of the partial order induced // by the per-tile stacks, preferring to make tiles with equal textures adjacent.) std::vector blendLayers; while (true) { if (!blendLayers.empty()) { // Try to grab as many tiles as possible that match our current layer, // from off the blend stacks of all the tiles CTerrainTextureEntry* tex = blendLayers.back().m_Texture; for (size_t k = 0; k < blendStacks.size(); ++k) { if (!blendStacks[k].blends.empty() && blendStacks[k].blends.back().m_Texture == tex) { SBlendLayer::Tile t = { blendStacks[k].i, blendStacks[k].j, (u8)blendStacks[k].blends.back().m_TileMask }; blendLayers.back().m_Tiles.push_back(t); blendStacks[k].blends.pop_back(); } // (We've already merged adjacent entries of the same texture in each stack, // so we don't need to bother looping to check the next entry in this stack again) } } // We've grabbed as many tiles as possible; now we need to start a new layer. // The new layer's texture could come from the back of any non-empty stack; // choose the longest stack as a heuristic to reduce the number of layers CTerrainTextureEntry* bestTex = NULL; size_t bestStackSize = 0; for (size_t k = 0; k < blendStacks.size(); ++k) { if (blendStacks[k].blends.size() > bestStackSize) { bestStackSize = blendStacks[k].blends.size(); bestTex = blendStacks[k].blends.back().m_Texture; } } // If all our stacks were empty, we're done if (bestStackSize == 0) break; // Otherwise add the new layer, then loop back and start filling it in SBlendLayer layer; layer.m_Texture = bestTex; blendLayers.push_back(layer); } // Now build outgoing splats m_BlendSplats.resize(blendLayers.size()); for (size_t k = 0; k < blendLayers.size(); ++k) { SSplat& splat = m_BlendSplats[k]; splat.m_IndexStart = blendIndices.size(); splat.m_Texture = blendLayers[k].m_Texture; for (size_t t = 0; t < blendLayers[k].m_Tiles.size(); ++t) { SBlendLayer::Tile& tile = blendLayers[k].m_Tiles[t]; AddBlend(blendVertices, blendIndices, tile.i, tile.j, tile.shape, splat.m_Texture); } splat.m_IndexCount = blendIndices.size() - splat.m_IndexStart; } // Release existing vertex buffer chunks m_VBBlends.Reset(); m_VBBlendIndices.Reset(); if (blendVertices.size()) { // Construct vertex buffer m_VBBlends = g_VBMan.AllocateChunk(sizeof(SBlendVertex), blendVertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBlends->m_Owner->UpdateChunkVertices(m_VBBlends.Get(), &blendVertices[0]); // Update the indices to include the base offset of the vertex data for (size_t k = 0; k < blendIndices.size(); ++k) blendIndices[k] += static_cast(m_VBBlends->m_Index); m_VBBlendIndices = g_VBMan.AllocateChunk(sizeof(u16), blendIndices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBlendIndices->m_Owner->UpdateChunkVertices(m_VBBlendIndices.Get(), &blendIndices[0]); } } void CPatchRData::AddBlend(std::vector& blendVertices, std::vector& blendIndices, u16 i, u16 j, u8 shape, CTerrainTextureEntry* texture) { CTerrain* terrain = m_Patch->m_Parent; ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; // uses the current neighbour texture BlendShape8 shape8; for (size_t m = 0; m < 8; ++m) shape8[m] = (shape & (1 << m)) ? 0 : 1; // calculate the required alphamap and the required rotation of the alphamap from blendshape unsigned int alphamapflags; int alphamap = CAlphaMapCalculator::Calculate(shape8, alphamapflags); // now actually render the blend tile (if we need one) if (alphamap == -1) return; float u0 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].u0; float u1 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].u1; float v0 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].v0; float v1 = texture->m_TerrainAlpha->second.m_AlphaMapCoords[alphamap].v1; if (alphamapflags & BLENDMAP_FLIPU) std::swap(u0, u1); if (alphamapflags & BLENDMAP_FLIPV) std::swap(v0, v1); int base = 0; if (alphamapflags & BLENDMAP_ROTATE90) base = 1; else if (alphamapflags & BLENDMAP_ROTATE180) base = 2; else if (alphamapflags & BLENDMAP_ROTATE270) base = 3; SBlendVertex vtx[4]; vtx[(base + 0) % 4].m_AlphaUVs[0] = u0; vtx[(base + 0) % 4].m_AlphaUVs[1] = v0; vtx[(base + 1) % 4].m_AlphaUVs[0] = u1; vtx[(base + 1) % 4].m_AlphaUVs[1] = v0; vtx[(base + 2) % 4].m_AlphaUVs[0] = u1; vtx[(base + 2) % 4].m_AlphaUVs[1] = v1; vtx[(base + 3) % 4].m_AlphaUVs[0] = u0; vtx[(base + 3) % 4].m_AlphaUVs[1] = v1; SBlendVertex dst; CVector3D normal; u16 index = static_cast(blendVertices.size()); terrain->CalcPosition(gx, gz, dst.m_Position); terrain->CalcNormal(gx, gz, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[0].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[0].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx + 1, gz, dst.m_Position); terrain->CalcNormal(gx + 1, gz, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[1].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[1].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx + 1, gz + 1, dst.m_Position); terrain->CalcNormal(gx + 1, gz + 1, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[2].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[2].m_AlphaUVs[1]; blendVertices.push_back(dst); terrain->CalcPosition(gx, gz + 1, dst.m_Position); terrain->CalcNormal(gx, gz + 1, normal); dst.m_Normal = normal; dst.m_AlphaUVs[0] = vtx[3].m_AlphaUVs[0]; dst.m_AlphaUVs[1] = vtx[3].m_AlphaUVs[1]; blendVertices.push_back(dst); bool dir = terrain->GetTriangulationDir(gx, gz); if (dir) { blendIndices.push_back(index+0); blendIndices.push_back(index+1); blendIndices.push_back(index+3); blendIndices.push_back(index+1); blendIndices.push_back(index+2); blendIndices.push_back(index+3); } else { blendIndices.push_back(index+0); blendIndices.push_back(index+1); blendIndices.push_back(index+2); blendIndices.push_back(index+2); blendIndices.push_back(index+3); blendIndices.push_back(index+0); } } void CPatchRData::BuildIndices() { PROFILE3("build indices"); CTerrain* terrain = m_Patch->m_Parent; ssize_t px = m_Patch->m_X * PATCH_SIZE; ssize_t pz = m_Patch->m_Z * PATCH_SIZE; // must have allocated some vertices before trying to build corresponding indices ENSURE(m_VBBase); // number of vertices in each direction in each patch ssize_t vsize=PATCH_SIZE+1; // PATCH_SIZE must be 2^8-2 or less to not overflow u16 indices buffer. Thankfully this is always true. ENSURE(vsize*vsize < 65536); std::vector indices; indices.reserve(PATCH_SIZE * PATCH_SIZE * 4); // release existing splats m_Splats.clear(); // build grid of textures on this patch std::vector textures; CTerrainTextureEntry* texgrid[PATCH_SIZE][PATCH_SIZE]; for (ssize_t j=0;jm_MiniPatches[j][i].GetTextureEntry(); texgrid[j][i]=tex; if (std::find(textures.begin(),textures.end(),tex)==textures.end()) { textures.push_back(tex); } } } // now build base splats from interior textures m_Splats.resize(textures.size()); // build indices for base splats size_t base=m_VBBase->m_Index; for (size_t k = 0; k < m_Splats.size(); ++k) { CTerrainTextureEntry* tex = textures[k]; SSplat& splat=m_Splats[k]; splat.m_Texture=tex; splat.m_IndexStart=indices.size(); for (ssize_t j = 0; j < PATCH_SIZE; j++) { for (ssize_t i = 0; i < PATCH_SIZE; i++) { if (texgrid[j][i] == tex) { bool dir = terrain->GetTriangulationDir(px+i, pz+j); if (dir) { indices.push_back(u16(((j+0)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); } else { indices.push_back(u16(((j+0)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+1))+base)); indices.push_back(u16(((j+1)*vsize+(i+0))+base)); indices.push_back(u16(((j+0)*vsize+(i+0))+base)); } } } } splat.m_IndexCount=indices.size()-splat.m_IndexStart; } // Release existing vertex buffer chunk m_VBBaseIndices.Reset(); ENSURE(indices.size()); // Construct vertex buffer m_VBBaseIndices = g_VBMan.AllocateChunk(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBaseIndices->m_Owner->UpdateChunkVertices(m_VBBaseIndices.Get(), &indices[0]); } void CPatchRData::BuildVertices() { PROFILE3("build vertices"); // create both vertices and lighting colors // number of vertices in each direction in each patch ssize_t vsize = PATCH_SIZE + 1; std::vector vertices; vertices.resize(vsize * vsize); // get index of this patch ssize_t px = m_Patch->m_X; ssize_t pz = m_Patch->m_Z; CTerrain* terrain = m_Patch->m_Parent; // build vertices for (ssize_t j = 0; j < vsize; ++j) { for (ssize_t i = 0; i < vsize; ++i) { ssize_t ix = px * PATCH_SIZE + i; ssize_t iz = pz * PATCH_SIZE + j; ssize_t v = j * vsize + i; // calculate vertex data terrain->CalcPosition(ix, iz, vertices[v].m_Position); CVector3D normal; terrain->CalcNormal(ix, iz, normal); vertices[v].m_Normal = normal; } } // upload to vertex buffer if (!m_VBBase) m_VBBase = g_VBMan.AllocateChunk(sizeof(SBaseVertex), vsize * vsize, GL_STATIC_DRAW, GL_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::TERRAIN); m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase.Get(), &vertices[0]); } void CPatchRData::BuildSide(std::vector& vertices, CPatchSideFlags side) { ssize_t vsize = PATCH_SIZE + 1; CTerrain* terrain = m_Patch->m_Parent; CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); for (ssize_t k = 0; k < vsize; k++) { ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; switch (side) { case CPATCH_SIDE_NEGX: gz += k; break; case CPATCH_SIDE_POSX: gx += PATCH_SIZE; gz += PATCH_SIZE-k; break; case CPATCH_SIDE_NEGZ: gx += PATCH_SIZE-k; break; case CPATCH_SIDE_POSZ: gz += PATCH_SIZE; gx += k; break; } CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Clamp the height to the water level float waterHeight = 0.f; if (cmpWaterManager) waterHeight = cmpWaterManager->GetExactWaterLevel(pos.X, pos.Z); pos.Y = std::max(pos.Y, waterHeight); SSideVertex v0, v1; v0.m_Position = pos; v1.m_Position = pos; v1.m_Position.Y = 0; // If this is the start of this tristrip, but we've already got a partial // tristrip, add a couple of degenerate triangles to join the strips properly if (k == 0 && !vertices.empty()) { vertices.push_back(vertices.back()); vertices.push_back(v1); } // Now add the new triangles vertices.push_back(v1); vertices.push_back(v0); } } void CPatchRData::BuildSides() { PROFILE3("build sides"); std::vector sideVertices; int sideFlags = m_Patch->GetSideFlags(); // If no sides are enabled, we don't need to do anything if (!sideFlags) return; // For each side, generate a tristrip by adding a vertex at ground/water // level and a vertex underneath at height 0. if (sideFlags & CPATCH_SIDE_NEGX) BuildSide(sideVertices, CPATCH_SIDE_NEGX); if (sideFlags & CPATCH_SIDE_POSX) BuildSide(sideVertices, CPATCH_SIDE_POSX); if (sideFlags & CPATCH_SIDE_NEGZ) BuildSide(sideVertices, CPATCH_SIDE_NEGZ); if (sideFlags & CPATCH_SIDE_POSZ) BuildSide(sideVertices, CPATCH_SIDE_POSZ); if (sideVertices.empty()) return; if (!m_VBSides) m_VBSides = g_VBMan.AllocateChunk(sizeof(SSideVertex), sideVertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::DEFAULT); m_VBSides->m_Owner->UpdateChunkVertices(m_VBSides.Get(), &sideVertices[0]); } void CPatchRData::Build() { BuildVertices(); BuildSides(); BuildIndices(); BuildBlends(); BuildWater(); } void CPatchRData::Update(CSimulation2* simulation) { m_Simulation = simulation; if (m_UpdateFlags!=0) { // TODO,RC 11/04/04 - need to only rebuild necessary bits of renderdata rather // than everything; it's complicated slightly because the blends are dependent // on both vertex and index data BuildVertices(); BuildSides(); BuildIndices(); BuildBlends(); BuildWater(); m_UpdateFlags=0; } } // Types used for glMultiDrawElements batching: // To minimise the cost of memory allocations, everything used for computing // batches uses a arena allocator. (All allocations are short-lived so we can // just throw away the whole arena at the end of each frame.) using Arena = Allocators::DynamicArena<1 * MiB>; // std::map types with appropriate arena allocators and default comparison operator template using PooledBatchMap = std::map, ProxyAllocator, Arena>>; // Equivalent to "m[k]", when it returns a arena-allocated std::map (since we can't // use the default constructor in that case) template typename M::mapped_type& PooledMapGet(M& m, const typename M::key_type& k, Arena& arena) { return m.insert(std::make_pair(k, typename M::mapped_type(typename M::mapped_type::key_compare(), typename M::mapped_type::allocator_type(arena)) )).first->second; } // Equivalent to "m[k]", when it returns a std::pair of arena-allocated std::vectors template typename M::mapped_type& PooledPairGet(M& m, const typename M::key_type& k, Arena& arena) { return m.insert(std::make_pair(k, std::make_pair( typename M::mapped_type::first_type(typename M::mapped_type::first_type::allocator_type(arena)), typename M::mapped_type::second_type(typename M::mapped_type::second_type::allocator_type(arena)) ))).first->second; } // Each multidraw batch has a list of index counts, and a list of pointers-to-first-indexes using BatchElements = std::pair>, std::vector>>; // Group batches by index buffer using IndexBufferBatches = PooledBatchMap; // Group batches by vertex buffer using VertexBufferBatches = PooledBatchMap; // Group batches by texture using TextureBatches = PooledBatchMap; // Group batches by shaders. using ShaderTechniqueBatches = PooledBatchMap; void CPatchRData::RenderBases( const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) { PROFILE3("render terrain bases"); Arena arena; ShaderTechniqueBatches batches(ShaderTechniqueBatches::key_compare(), (ShaderTechniqueBatches::allocator_type(arena))); PROFILE_START("compute batches"); // Collect all the patches' base splats into their appropriate batches for (size_t i = 0; i < patches.size(); ++i) { CPatchRData* patch = patches[i]; for (size_t j = 0; j < patch->m_Splats.size(); ++j) { SSplat& splat = patch->m_Splats[j]; const CMaterial& material = splat.m_Texture->GetMaterial(); if (material.GetShaderEffect().empty()) { LOGERROR("Terrain renderer failed to load shader effect.\n"); continue; } CShaderDefines defines = context; defines.SetMany(material.GetShaderDefines(0)); CShaderTechniquePtr techBase = g_Renderer.GetShaderManager().LoadEffect( material.GetShaderEffect(), defines); BatchElements& batch = PooledPairGet( PooledMapGet( PooledMapGet( PooledMapGet(batches, techBase, arena), splat.m_Texture, arena ), patch->m_VBBase->m_Owner, arena ), patch->m_VBBaseIndices->m_Owner, arena ); batch.first.push_back(splat.m_IndexCount); u8* indexBase = nullptr; batch.second.push_back(indexBase + sizeof(u16)*(patch->m_VBBaseIndices->m_Index + splat.m_IndexStart)); } } PROFILE_END("compute batches"); // Render each batch for (ShaderTechniqueBatches::iterator itTech = batches.begin(); itTech != batches.end(); ++itTech) { const CShaderTechniquePtr& techBase = itTech->first; const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { techBase->BeginPass(pass); const CShaderProgramPtr& shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(shader, shadow); TextureBatches& textureBatches = itTech->second; for (TextureBatches::iterator itt = textureBatches.begin(); itt != textureBatches.end(); ++itt) { if (itt->first->GetMaterial().GetSamplers().size() != 0) { const CMaterial::SamplersVector& samplers = itt->first->GetMaterial().GetSamplers(); for(const CMaterial::TextureSampler& samp : samplers) shader->BindTexture(samp.Name, samp.Sampler); itt->first->GetMaterial().GetStaticUniforms().BindUniforms(shader); float c = itt->first->GetTextureMatrix()[0]; float ms = itt->first->GetTextureMatrix()[8]; shader->Uniform(str_textureTransform, c, ms, -ms, 0.f); } else { shader->BindTexture(str_baseTex, g_Renderer.GetTextureManager().GetErrorTexture()); } for (VertexBufferBatches::iterator itv = itt->second.begin(); itv != itt->second.end(); ++itv) { GLsizei stride = sizeof(SBaseVertex); SBaseVertex *base = (SBaseVertex *)itv->first->Bind(); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); shader->NormalPointer(GL_FLOAT, stride, &base->m_Normal[0]); shader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, &base->m_Position[0]); shader->AssertPointersBound(); for (IndexBufferBatches::iterator it = itv->second.begin(); it != itv->second.end(); ++it) { it->first->Bind(); BatchElements& batch = it->second; if (!g_Renderer.m_SkipSubmit) { // Don't use glMultiDrawElements here since it doesn't have a significant // performance impact and it suffers from various driver bugs (e.g. it breaks // in Mesa 7.10 swrast with index VBOs) for (size_t i = 0; i < batch.first.size(); ++i) glDrawElements(GL_TRIANGLES, batch.first[i], GL_UNSIGNED_SHORT, batch.second[i]); } g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } techBase->EndPass(); } } CVertexBuffer::Unbind(); } /** * Helper structure for RenderBlends. */ struct SBlendBatch { SBlendBatch(Arena& arena) : m_Batches(VertexBufferBatches::key_compare(), VertexBufferBatches::allocator_type(arena)) { } CTerrainTextureEntry* m_Texture; CShaderTechniquePtr m_ShaderTech; VertexBufferBatches m_Batches; }; /** * Helper structure for RenderBlends. */ struct SBlendStackItem { SBlendStackItem(CVertexBuffer::VBChunk* v, CVertexBuffer::VBChunk* i, const std::vector& s, Arena& arena) : vertices(v), indices(i), splats(s.begin(), s.end(), SplatStack::allocator_type(arena)) { } using SplatStack = std::vector>; CVertexBuffer::VBChunk* vertices; CVertexBuffer::VBChunk* indices; SplatStack splats; }; void CPatchRData::RenderBlends( const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow) { PROFILE3("render terrain blends"); Arena arena; using BatchesStack = std::vector>; BatchesStack batches((BatchesStack::allocator_type(arena))); CShaderDefines contextBlend = context; contextBlend.Add(str_BLEND, str_1); PROFILE_START("compute batches"); // Reserve an arbitrary size that's probably big enough in most cases, // to avoid heavy reallocations batches.reserve(256); using BlendStacks = std::vector>; BlendStacks blendStacks((BlendStacks::allocator_type(arena))); blendStacks.reserve(patches.size()); // Extract all the blend splats from each patch for (size_t i = 0; i < patches.size(); ++i) { CPatchRData* patch = patches[i]; if (!patch->m_BlendSplats.empty()) { blendStacks.push_back(SBlendStackItem(patch->m_VBBlends.Get(), patch->m_VBBlendIndices.Get(), patch->m_BlendSplats, arena)); // Reverse the splats so the first to be rendered is at the back of the list std::reverse(blendStacks.back().splats.begin(), blendStacks.back().splats.end()); } } // Rearrange the collection of splats to be grouped by texture, preserving // order of splats within each patch: // (This is exactly the same algorithm used in CPatchRData::BuildBlends, // but applied to patch-sized splats rather than to tile-sized splats; // see that function for comments on the algorithm.) while (true) { if (!batches.empty()) { CTerrainTextureEntry* tex = batches.back().m_Texture; for (size_t k = 0; k < blendStacks.size(); ++k) { SBlendStackItem::SplatStack& splats = blendStacks[k].splats; if (!splats.empty() && splats.back().m_Texture == tex) { CVertexBuffer::VBChunk* vertices = blendStacks[k].vertices; CVertexBuffer::VBChunk* indices = blendStacks[k].indices; BatchElements& batch = PooledPairGet(PooledMapGet(batches.back().m_Batches, vertices->m_Owner, arena), indices->m_Owner, arena); batch.first.push_back(splats.back().m_IndexCount); u8* indexBase = nullptr; batch.second.push_back(indexBase + sizeof(u16)*(indices->m_Index + splats.back().m_IndexStart)); splats.pop_back(); } } } CTerrainTextureEntry* bestTex = NULL; size_t bestStackSize = 0; for (size_t k = 0; k < blendStacks.size(); ++k) { SBlendStackItem::SplatStack& splats = blendStacks[k].splats; if (splats.size() > bestStackSize) { bestStackSize = splats.size(); bestTex = splats.back().m_Texture; } } if (bestStackSize == 0) break; SBlendBatch layer(arena); layer.m_Texture = bestTex; if (!bestTex->GetMaterial().GetSamplers().empty()) { CShaderDefines defines = contextBlend; defines.SetMany(bestTex->GetMaterial().GetShaderDefines(0)); layer.m_ShaderTech = g_Renderer.GetShaderManager().LoadEffect( bestTex->GetMaterial().GetShaderEffect(), defines); } batches.push_back(layer); } PROFILE_END("compute batches"); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CVertexBuffer* lastVB = nullptr; CShaderProgramPtr previousShader; for (BatchesStack::iterator itTechBegin = batches.begin(), itTechEnd = batches.begin(); itTechBegin != batches.end(); itTechBegin = itTechEnd) { while (itTechEnd != batches.end() && itTechEnd->m_ShaderTech == itTechBegin->m_ShaderTech) ++itTechEnd; const CShaderTechniquePtr& techBase = itTechBegin->m_ShaderTech; const int numPasses = techBase->GetNumPasses(); for (int pass = 0; pass < numPasses; ++pass) { techBase->BeginPass(pass); const CShaderProgramPtr& shader = techBase->GetShader(pass); TerrainRenderer::PrepareShader(shader, shadow); Renderer::Backend::GL::CTexture* lastBlendTex = nullptr; for (BatchesStack::iterator itt = itTechBegin; itt != itTechEnd; ++itt) { if (itt->m_Texture->GetMaterial().GetSamplers().empty()) continue; if (itt->m_Texture) { const CMaterial::SamplersVector& samplers = itt->m_Texture->GetMaterial().GetSamplers(); for (const CMaterial::TextureSampler& samp : samplers) shader->BindTexture(samp.Name, samp.Sampler); Renderer::Backend::GL::CTexture* currentBlendTex = itt->m_Texture->m_TerrainAlpha->second.m_CompositeAlphaMap.get(); if (currentBlendTex != lastBlendTex) { - shader->BindTexture(str_blendTex, currentBlendTex->GetHandle()); + shader->BindTexture(str_blendTex, currentBlendTex); lastBlendTex = currentBlendTex; } itt->m_Texture->GetMaterial().GetStaticUniforms().BindUniforms(shader); float c = itt->m_Texture->GetTextureMatrix()[0]; float ms = itt->m_Texture->GetTextureMatrix()[8]; shader->Uniform(str_textureTransform, c, ms, -ms, 0.f); } else { shader->BindTexture(str_baseTex, g_Renderer.GetTextureManager().GetErrorTexture()); } for (VertexBufferBatches::iterator itv = itt->m_Batches.begin(); itv != itt->m_Batches.end(); ++itv) { // Rebind the VB only if it changed since the last batch if (itv->first != lastVB || shader != previousShader) { lastVB = itv->first; previousShader = shader; GLsizei stride = sizeof(SBlendVertex); SBlendVertex *base = (SBlendVertex *)itv->first->Bind(); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); shader->NormalPointer(GL_FLOAT, stride, &base->m_Normal[0]); shader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, &base->m_Position[0]); shader->TexCoordPointer(GL_TEXTURE1, 2, GL_FLOAT, stride, &base->m_AlphaUVs[0]); } shader->AssertPointersBound(); for (IndexBufferBatches::iterator it = itv->second.begin(); it != itv->second.end(); ++it) { it->first->Bind(); BatchElements& batch = it->second; if (!g_Renderer.m_SkipSubmit) { for (size_t i = 0; i < batch.first.size(); ++i) glDrawElements(GL_TRIANGLES, batch.first[i], GL_UNSIGNED_SHORT, batch.second[i]); } g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_BlendSplats++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } } techBase->EndPass(); } } glDisable(GL_BLEND); CVertexBuffer::Unbind(); } void CPatchRData::RenderStreams(const std::vector& patches, const CShaderProgramPtr& shader, int streamflags) { PROFILE3("render terrain streams"); // Each batch has a list of index counts, and a list of pointers-to-first-indexes using StreamBatchElements = std::pair, std::vector > ; // Group batches by index buffer using StreamIndexBufferBatches = std::map ; // Group batches by vertex buffer using StreamVertexBufferBatches = std::map ; StreamVertexBufferBatches batches; PROFILE_START("compute batches"); // Collect all the patches into their appropriate batches for (const CPatchRData* patch : patches) { StreamBatchElements& batch = batches[patch->m_VBBase->m_Owner][patch->m_VBBaseIndices->m_Owner]; batch.first.push_back(patch->m_VBBaseIndices->m_Count); u8* indexBase = nullptr; batch.second.push_back(indexBase + sizeof(u16)*(patch->m_VBBaseIndices->m_Index)); } PROFILE_END("compute batches"); ENSURE(!(streamflags & ~(STREAM_POS|STREAM_POSTOUV0|STREAM_POSTOUV1))); // Render each batch for (const std::pair& streamBatch : batches) { GLsizei stride = sizeof(SBaseVertex); SBaseVertex *base = (SBaseVertex *)streamBatch.first->Bind(); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position); if (streamflags & STREAM_POSTOUV0) shader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, &base->m_Position); if (streamflags & STREAM_POSTOUV1) shader->TexCoordPointer(GL_TEXTURE1, 3, GL_FLOAT, stride, &base->m_Position); shader->AssertPointersBound(); for (const std::pair& batchIndexBuffer : streamBatch.second) { batchIndexBuffer.first->Bind(); const StreamBatchElements& batch = batchIndexBuffer.second; if (!g_Renderer.m_SkipSubmit) { for (size_t i = 0; i < batch.first.size(); ++i) glDrawElements(GL_TRIANGLES, batch.first[i], GL_UNSIGNED_SHORT, batch.second[i]); } g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += std::accumulate(batch.first.begin(), batch.first.end(), 0) / 3; } } CVertexBuffer::Unbind(); } void CPatchRData::RenderOutline() { CTerrain* terrain = m_Patch->m_Parent; ssize_t gx = m_Patch->m_X * PATCH_SIZE; ssize_t gz = m_Patch->m_Z * PATCH_SIZE; CVector3D pos; std::vector line; for (ssize_t i = 0, j = 0; i <= PATCH_SIZE; ++i) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = PATCH_SIZE, j = 1; j <= PATCH_SIZE; ++j) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = PATCH_SIZE-1, j = PATCH_SIZE; i >= 0; --i) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } for (ssize_t i = 0, j = PATCH_SIZE-1; j >= 0; --j) { terrain->CalcPosition(gx + i, gz + j, pos); line.push_back(pos); } g_Renderer.GetDebugRenderer().DrawLine(line, CColor(0.0f, 0.0f, 1.0f, 1.0f), 0.1f); } void CPatchRData::RenderSides(const std::vector& patches, const CShaderProgramPtr& shader) { PROFILE3("render terrain sides"); glDisable(GL_CULL_FACE); CVertexBuffer* lastVB = nullptr; for (CPatchRData* patch : patches) { ENSURE(patch->m_UpdateFlags == 0); if (!patch->m_VBSides) continue; if (lastVB != patch->m_VBSides->m_Owner) { lastVB = patch->m_VBSides->m_Owner; SSideVertex *base = (SSideVertex*)patch->m_VBSides->m_Owner->Bind(); // setup data pointers GLsizei stride = sizeof(SSideVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base->m_Position); } shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_TRIANGLE_STRIP, patch->m_VBSides->m_Index, (GLsizei)patch->m_VBSides->m_Count); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris += patch->m_VBSides->m_Count - 2; } CVertexBuffer::Unbind(); glEnable(GL_CULL_FACE); } void CPatchRData::RenderPriorities(CTextRenderer& textRenderer) { CTerrain* terrain = m_Patch->m_Parent; const CCamera& camera = *(g_Game->GetView()->GetCamera()); for (ssize_t j = 0; j < PATCH_SIZE; ++j) { for (ssize_t i = 0; i < PATCH_SIZE; ++i) { ssize_t gx = m_Patch->m_X * PATCH_SIZE + i; ssize_t gz = m_Patch->m_Z * PATCH_SIZE + j; CVector3D pos; terrain->CalcPosition(gx, gz, pos); // Move a bit towards the center of the tile pos.X += TERRAIN_TILE_SIZE/4.f; pos.Z += TERRAIN_TILE_SIZE/4.f; float x, y; camera.GetScreenCoordinates(pos, x, y); textRenderer.PrintfAt(x, y, L"%d", m_Patch->m_MiniPatches[j][i].Priority); } } } // // Water build and rendering // // Build vertex buffer for water vertices over our patch void CPatchRData::BuildWater() { PROFILE3("build water"); // Number of vertices in each direction in each patch ENSURE(PATCH_SIZE % water_cell_size == 0); m_VBWater.Reset(); m_VBWaterIndices.Reset(); m_VBWaterShore.Reset(); m_VBWaterIndicesShore.Reset(); m_WaterBounds.SetEmpty(); // We need to use this to access the water manager or we may not have the // actual values but some compiled-in defaults CmpPtr cmpWaterManager(*m_Simulation, SYSTEM_ENTITY); if (!cmpWaterManager) return; // Build data for water std::vector water_vertex_data; std::vector water_indices; u16 water_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_index_map, 0xFF, sizeof(water_index_map)); // Build data for shore std::vector water_vertex_data_shore; std::vector water_indices_shore; u16 water_shore_index_map[PATCH_SIZE+1][PATCH_SIZE+1]; memset(water_shore_index_map, 0xFF, sizeof(water_shore_index_map)); WaterManager* WaterMgr = g_Renderer.GetWaterManager(); CPatch* patch = m_Patch; CTerrain* terrain = patch->m_Parent; ssize_t mapSize = terrain->GetVerticesPerSide(); // Top-left coordinates of our patch. ssize_t px = m_Patch->m_X * PATCH_SIZE; ssize_t pz = m_Patch->m_Z * PATCH_SIZE; // To whoever implements different water heights, this is a TODO: water height) float waterHeight = cmpWaterManager->GetExactWaterLevel(0.0f,0.0f); // The 4 points making a water tile. int moves[4][2] = { {0, 0}, {water_cell_size, 0}, {0, water_cell_size}, {water_cell_size, water_cell_size} }; // Where to look for when checking for water for shore tiles. int check[10][2] = { {0, 0}, {water_cell_size, 0}, {water_cell_size*2, 0}, {0, water_cell_size}, {0, water_cell_size*2}, {water_cell_size, water_cell_size}, {water_cell_size*2, water_cell_size*2}, {-water_cell_size, 0}, {0, -water_cell_size}, {-water_cell_size, -water_cell_size} }; // build vertices, uv, and shader varying for (ssize_t z = 0; z < PATCH_SIZE; z += water_cell_size) { for (ssize_t x = 0; x < PATCH_SIZE; x += water_cell_size) { // Check that this tile is close to water bool nearWater = false; for (size_t test = 0; test < 10; ++test) if (terrain->GetVertexGroundLevel(x + px + check[test][0], z + pz + check[test][1]) < waterHeight) nearWater = true; if (!nearWater) continue; // This is actually lying and I should call CcmpTerrain /*if (!terrain->IsOnMap(x+x1, z+z1) && !terrain->IsOnMap(x+x1, z+z1 + water_cell_size) && !terrain->IsOnMap(x+x1 + water_cell_size, z+z1) && !terrain->IsOnMap(x+x1 + water_cell_size, z+z1 + water_cell_size)) continue;*/ for (int i = 0; i < 4; ++i) { if (water_index_map[z+moves[i][1]][x+moves[i][0]] != 0xFFFF) continue; ssize_t xx = x + px + moves[i][0]; ssize_t zz = z + pz + moves[i][1]; SWaterVertex vertex; terrain->CalcPosition(xx,zz, vertex.m_Position); float depth = waterHeight - vertex.m_Position.Y; vertex.m_Position.Y = waterHeight; m_WaterBounds += vertex.m_Position; vertex.m_WaterData = CVector2D(WaterMgr->m_WindStrength[xx + zz*mapSize], depth); water_index_map[z+moves[i][1]][x+moves[i][0]] = static_cast(water_vertex_data.size()); water_vertex_data.push_back(vertex); } water_indices.push_back(water_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices.push_back(water_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices.push_back(water_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices.push_back(water_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices.push_back(water_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices.push_back(water_index_map[z + moves[2][1]][x + moves[2][0]]); // Check id this tile is partly over land. // If so add a square over the terrain. This is necessary to render waves that go on shore. if (terrain->GetVertexGroundLevel(x+px, z+pz) < waterHeight && terrain->GetVertexGroundLevel(x+px + water_cell_size, z+pz) < waterHeight && terrain->GetVertexGroundLevel(x+px, z+pz+water_cell_size) < waterHeight && terrain->GetVertexGroundLevel(x+px + water_cell_size, z+pz+water_cell_size) < waterHeight) continue; for (int i = 0; i < 4; ++i) { if (water_shore_index_map[z+moves[i][1]][x+moves[i][0]] != 0xFFFF) continue; ssize_t xx = x + px + moves[i][0]; ssize_t zz = z + pz + moves[i][1]; SWaterVertex vertex; terrain->CalcPosition(xx,zz, vertex.m_Position); vertex.m_Position.Y += 0.02f; m_WaterBounds += vertex.m_Position; vertex.m_WaterData = CVector2D(0.0f, -5.0f); water_shore_index_map[z+moves[i][1]][x+moves[i][0]] = static_cast(water_vertex_data_shore.size()); water_vertex_data_shore.push_back(vertex); } if (terrain->GetTriangulationDir(x + px, z + pz)) { water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); } else { water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[2][1]][x + moves[2][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[3][1]][x + moves[3][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[0][1]][x + moves[0][0]]); water_indices_shore.push_back(water_shore_index_map[z + moves[1][1]][x + moves[1][0]]); } } } // No vertex buffers if no data generated if (!water_indices.empty()) { m_VBWater = g_VBMan.AllocateChunk(sizeof(SWaterVertex), water_vertex_data.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::WATER); m_VBWater->m_Owner->UpdateChunkVertices(m_VBWater.Get(), &water_vertex_data[0]); m_VBWaterIndices = g_VBMan.AllocateChunk(sizeof(GLushort), water_indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::WATER); m_VBWaterIndices->m_Owner->UpdateChunkVertices(m_VBWaterIndices.Get(), &water_indices[0]); } if (!water_indices_shore.empty()) { m_VBWaterShore = g_VBMan.AllocateChunk(sizeof(SWaterVertex), water_vertex_data_shore.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::WATER); m_VBWaterShore->m_Owner->UpdateChunkVertices(m_VBWaterShore.Get(), &water_vertex_data_shore[0]); // Construct indices buffer m_VBWaterIndicesShore = g_VBMan.AllocateChunk(sizeof(GLushort), water_indices_shore.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER, nullptr, CVertexBufferManager::Group::WATER); m_VBWaterIndicesShore->m_Owner->UpdateChunkVertices(m_VBWaterIndicesShore.Get(), &water_indices_shore[0]); } } void CPatchRData::RenderWater(CShaderProgramPtr& shader, bool onlyShore, bool fixedPipeline) { ASSERT(m_UpdateFlags==0); if (g_Renderer.m_SkipSubmit || (!m_VBWater && !m_VBWaterShore)) return; #if !CONFIG2_GLES if (g_Renderer.GetWaterRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); #endif if (m_VBWater && !onlyShore) { SWaterVertex *base=(SWaterVertex *)m_VBWater->m_Owner->Bind(); // setup data pointers GLsizei stride = sizeof(SWaterVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base[m_VBWater->m_Index].m_Position); if (!fixedPipeline) shader->VertexAttribPointer(str_a_waterInfo, 2, GL_FLOAT, false, stride, &base[m_VBWater->m_Index].m_WaterData); shader->AssertPointersBound(); u8* indexBase = m_VBWaterIndices->m_Owner->Bind(); glDrawElements(GL_TRIANGLES, (GLsizei) m_VBWaterIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_VBWaterIndices->m_Index)); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndices->m_Count / 3; } if (m_VBWaterShore && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB && g_Renderer.GetWaterManager()->m_WaterEffects && g_Renderer.GetWaterManager()->m_WaterFancyEffects) { SWaterVertex *base=(SWaterVertex *)m_VBWaterShore->m_Owner->Bind(); GLsizei stride = sizeof(SWaterVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base[m_VBWaterShore->m_Index].m_Position); if (!fixedPipeline) shader->VertexAttribPointer(str_a_waterInfo, 2, GL_FLOAT, false, stride, &base[m_VBWaterShore->m_Index].m_WaterData); shader->AssertPointersBound(); u8* indexBase = m_VBWaterIndicesShore->m_Owner->Bind(); glDrawElements(GL_TRIANGLES, (GLsizei) m_VBWaterIndicesShore->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_VBWaterIndicesShore->m_Index)); g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndicesShore->m_Count / 3; } CVertexBuffer::Unbind(); #if !CONFIG2_GLES if (g_Renderer.GetWaterRenderMode() == WIREFRAME) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif } Index: ps/trunk/source/renderer/PostprocManager.cpp =================================================================== --- ps/trunk/source/renderer/PostprocManager.cpp (revision 26142) +++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26143) @@ -1,848 +1,852 @@ /* 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 "renderer/PostprocManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "lib/bits.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "ps/ConfigDB.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "tools/atlas/GameInterface/GameLoop.h" #if !CONFIG2_GLES CPostprocManager::CPostprocManager() : m_IsInitialized(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"default"), m_BloomFbo(0), m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleFBO(0), m_MultisampleCount(0) { } CPostprocManager::~CPostprocManager() { Cleanup(); } void CPostprocManager::Cleanup() { if (!m_IsInitialized) // Only cleanup if previously used return; if (m_PingFbo) glDeleteFramebuffersEXT(1, &m_PingFbo); if (m_PongFbo) glDeleteFramebuffersEXT(1, &m_PongFbo); if (m_BloomFbo) glDeleteFramebuffersEXT(1, &m_BloomFbo); m_PingFbo = m_PongFbo = m_BloomFbo = 0; m_ColorTex1.reset(); m_ColorTex2.reset(); m_DepthTex.reset(); m_BlurTex2a.reset(); m_BlurTex2b.reset(); m_BlurTex4a.reset(); m_BlurTex4b.reset(); m_BlurTex8a.reset(); m_BlurTex8b.reset(); } void CPostprocManager::Initialize() { if (m_IsInitialized) return; GLint maxSamples = 0; glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); const GLsizei possibleSampleCounts[] = {2, 4, 8, 16}; std::copy_if( std::begin(possibleSampleCounts), std::end(possibleSampleCounts), std::back_inserter(m_AllowedSampleCounts), [maxSamples](const GLsizei sampleCount) { return sampleCount <= maxSamples; } ); // The screen size starts out correct and then must be updated with Resize() m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); RecreateBuffers(); m_IsInitialized = true; // Once we have initialised the buffers, we can update the techniques. UpdateAntiAliasingTechnique(); UpdateSharpeningTechnique(); UpdateSharpnessFactor(); // This might happen after the map is loaded and the effect chosen SetPostEffect(m_PostProcEffect); } void CPostprocManager::Resize() { m_Width = g_Renderer.GetWidth(); m_Height = g_Renderer.GetHeight(); // If the buffers were intialized, recreate them to the new size. if (m_IsInitialized) RecreateBuffers(); } void CPostprocManager::RecreateBuffers() { Cleanup(); #define GEN_BUFFER_RGBA(name, w, h) \ name = Renderer::Backend::GL::CTexture::Create2D( \ Renderer::Backend::Format::R8G8B8A8, w, h, \ Renderer::Backend::Sampler::MakeDefaultSampler( \ Renderer::Backend::Sampler::Filter::LINEAR, \ Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); \ glBindTexture(GL_TEXTURE_2D, name->GetHandle()); \ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); // Two fullscreen ping-pong textures. GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height); GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height); // Textures for several blur sizes. It would be possible to reuse // m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given // that these are fairly small it's probably not worth complicating the coordinates passed // to the blur helper functions. GEN_BUFFER_RGBA(m_BlurTex2a, m_Width / 2, m_Height / 2); GEN_BUFFER_RGBA(m_BlurTex2b, m_Width / 2, m_Height / 2); GEN_BUFFER_RGBA(m_BlurTex4a, m_Width / 4, m_Height / 4); GEN_BUFFER_RGBA(m_BlurTex4b, m_Width / 4, m_Height / 4); GEN_BUFFER_RGBA(m_BlurTex8a, m_Width / 8, m_Height / 8); GEN_BUFFER_RGBA(m_BlurTex8b, m_Width / 8, m_Height / 8); #undef GEN_BUFFER_RGBA // Allocate the Depth/Stencil texture. m_DepthTex = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::D24_S8, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); glBindTexture(GL_TEXTURE_2D, m_DepthTex->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8_EXT, m_Width, m_Height, 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glBindTexture(GL_TEXTURE_2D, 0); // Set up the framebuffers with some initial textures. glGenFramebuffersEXT(1, &m_PingFbo); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ColorTex1->GetHandle(), 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0); GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete (A): 0x%04X", status); } glGenFramebuffersEXT(1, &m_PongFbo); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ColorTex2->GetHandle(), 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0); status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete (B): 0x%04X", status); } glGenFramebuffersEXT(1, &m_BloomFbo); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); /* glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_BloomTex1, 0); status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete (B): 0x%04X", status); } */ if (m_UsingMultisampleBuffer) { DestroyMultisampleBuffer(); CreateMultisampleBuffer(); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } -void CPostprocManager::ApplyBlurDownscale2x(GLuint inTex, GLuint outTex, int inWidth, int inHeight) +void CPostprocManager::ApplyBlurDownscale2x( + Renderer::Backend::GL::CTexture* inTex, Renderer::Backend::GL::CTexture* outTex, int inWidth, int inHeight) { // Bind inTex to framebuffer for rendering. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, outTex, 0); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, outTex->GetHandle(), 0); // Get bloom shader with instructions to simply copy texels. CShaderDefines defines; defines.Add(str_BLOOM_NOP, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); - GLuint renderedTex = inTex; - // Cheat by creating high quality mipmaps for inTex, so the copying operation actually // produces good scaling due to hardware filtering. - glBindTexture(GL_TEXTURE_2D, renderedTex); + glBindTexture(GL_TEXTURE_2D, inTex->GetHandle()); glGenerateMipmapEXT(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); - shader->BindTexture(str_renderedTex, renderedTex); + shader->BindTexture(str_renderedTex, inTex); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, inWidth / 2, inHeight / 2 }; g_Renderer.SetViewport(vp); - float quadVerts[] = { + float quadVerts[] = + { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; - float quadTex[] = { + float quadTex[] = + { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); } -void CPostprocManager::ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight) +void CPostprocManager::ApplyBlurGauss( + Renderer::Backend::GL::CTexture* inOutTex, Renderer::Backend::GL::CTexture* tempTex, int inWidth, int inHeight) { // Set tempTex as our rendering target. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tempTex, 0); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tempTex->GetHandle(), 0); // Get bloom shader, for a horizontal Gaussian blur pass. CShaderDefines defines2; defines2.Add(str_BLOOM_PASS_H, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines2); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->BindTexture(str_renderedTex, inOutTex); shader->Uniform(str_texSize, inWidth, inHeight, 0.0f, 0.0f); const SViewPort oldVp = g_Renderer.GetViewport(); const SViewPort vp = { 0, 0, inWidth, inHeight }; g_Renderer.SetViewport(vp); - float quadVerts[] = { + float quadVerts[] = + { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; - float quadTex[] = { + float quadTex[] = + { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); // Set result texture as our render target. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo); - glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, inOutTex, 0); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, inOutTex->GetHandle(), 0); // Get bloom shader, for a vertical Gaussian blur pass. CShaderDefines defines3; defines3.Add(str_BLOOM_PASS_V, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines3); tech->BeginPass(); shader = tech->GetShader(); // Our input texture to the shader is the output of the horizontal pass. shader->BindTexture(str_renderedTex, tempTex); shader->Uniform(str_texSize, inWidth, inHeight, 0.0f, 0.0f); g_Renderer.SetViewport(vp); shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); } void CPostprocManager::ApplyBlur() { glDisable(GL_BLEND); int width = m_Width, height = m_Height; #define SCALE_AND_BLUR(tex1, tex2, temptex) \ - ApplyBlurDownscale2x((tex1)->GetHandle(), (tex2)->GetHandle(), width, height); \ + ApplyBlurDownscale2x((tex1).get(), (tex2).get(), width, height); \ width /= 2; \ height /= 2; \ - ApplyBlurGauss((tex2)->GetHandle(), (temptex)->GetHandle(), width, height); + ApplyBlurGauss((tex2).get(), (temptex).get(), width, height); // We do the same thing for each scale, incrementally adding more and more blur. SCALE_AND_BLUR(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2, m_BlurTex2a, m_BlurTex2b); SCALE_AND_BLUR(m_BlurTex2a, m_BlurTex4a, m_BlurTex4b); SCALE_AND_BLUR(m_BlurTex4a, m_BlurTex8a, m_BlurTex8b); #undef SCALE_AND_BLUR } void CPostprocManager::CaptureRenderOutput() { ENSURE(m_IsInitialized); // Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }; glDrawBuffers(1, buffers); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); glDrawBuffers(1, buffers); m_WhichBuffer = true; if (m_UsingMultisampleBuffer) { glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO); glDrawBuffers(1, buffers); } } void CPostprocManager::ReleaseRenderOutput() { ENSURE(m_IsInitialized); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // we blit to screen from the previous active buffer if (m_WhichBuffer) glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_PingFbo); else glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_PongFbo); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); glBlitFramebufferEXT(0, 0, m_Width, m_Height, 0, 0, m_Width, m_Height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } void CPostprocManager::ApplyEffect(CShaderTechniquePtr &shaderTech1, int pass) { // select the other FBO for rendering if (!m_WhichBuffer) glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); else glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); shaderTech1->BeginPass(pass); CShaderProgramPtr shader = shaderTech1->GetShader(pass); shader->Bind(); // Use the textures from the current FBO as input to the shader. // We also bind a bunch of other textures and parameters, but since // this only happens once per frame the overhead is negligible. if (m_WhichBuffer) - shader->BindTexture(str_renderedTex, m_ColorTex1->GetHandle()); + shader->BindTexture(str_renderedTex, m_ColorTex1.get()); else - shader->BindTexture(str_renderedTex, m_ColorTex2->GetHandle()); + shader->BindTexture(str_renderedTex, m_ColorTex2.get()); - shader->BindTexture(str_depthTex, m_DepthTex->GetHandle()); + shader->BindTexture(str_depthTex, m_DepthTex.get()); - shader->BindTexture(str_blurTex2, m_BlurTex2a->GetHandle()); - shader->BindTexture(str_blurTex4, m_BlurTex4a->GetHandle()); - shader->BindTexture(str_blurTex8, m_BlurTex8a->GetHandle()); + shader->BindTexture(str_blurTex2, m_BlurTex2a.get()); + shader->BindTexture(str_blurTex4, m_BlurTex4a.get()); + shader->BindTexture(str_blurTex8, m_BlurTex8a.get()); shader->Uniform(str_width, m_Width); shader->Uniform(str_height, m_Height); shader->Uniform(str_zNear, m_NearPlane); shader->Uniform(str_zFar, m_FarPlane); shader->Uniform(str_sharpness, m_Sharpness); shader->Uniform(str_brightness, g_LightEnv.m_Brightness); shader->Uniform(str_hdr, g_LightEnv.m_Contrast); shader->Uniform(str_saturation, g_LightEnv.m_Saturation); shader->Uniform(str_bloom, g_LightEnv.m_Bloom); float quadVerts[] = { 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f }; float quadTex[] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(2, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); shader->Unbind(); shaderTech1->EndPass(pass); glDepthMask(GL_TRUE); glEnable(GL_DEPTH_TEST); m_WhichBuffer = !m_WhichBuffer; } void CPostprocManager::ApplyPostproc() { ENSURE(m_IsInitialized); // Don't do anything if we are using the default effect and no AA. const bool hasEffects = m_PostProcEffect != L"default"; const bool hasARB = g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB; const bool hasAA = m_AATech && !hasARB; const bool hasSharp = m_SharpTech && !hasARB; if (!hasEffects && !hasAA && !hasSharp) return; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0); GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT }; glDrawBuffers(1, buffers); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0); glDrawBuffers(1, buffers); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); if (hasEffects) { // First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied! // (This may need to change depending on future usage, however that will have a fps hit) ApplyBlur(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass) ApplyEffect(m_PostProcTech, pass); } if (hasAA) { for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass) ApplyEffect(m_AATech, pass); } if (hasSharp) { for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass) ApplyEffect(m_SharpTech, pass); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0); } // Generate list of available effect-sets std::vector CPostprocManager::GetPostEffects() { std::vector effects; const VfsPath folder(L"shaders/effects/postproc/"); VfsPaths pathnames; if (vfs::GetPathnames(g_VFS, folder, 0, pathnames) < 0) LOGERROR("Error finding Post effects in '%s'", folder.string8()); for (const VfsPath& path : pathnames) if (path.Extension() == L".xml") effects.push_back(path.Basename().string()); // Add the default "null" effect to the list. effects.push_back(L"default"); sort(effects.begin(), effects.end()); return effects; } void CPostprocManager::SetPostEffect(const CStrW& name) { if (m_IsInitialized) { if (name != L"default") { CStrW n = L"postproc/" + name; m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8())); } } m_PostProcEffect = name; } void CPostprocManager::UpdateAntiAliasingTechnique() { if (g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB || !m_IsInitialized) return; CStr newAAName; CFG_GET_VAL("antialiasing", newAAName); if (m_AAName == newAAName) return; m_AAName = newAAName; m_AATech.reset(); if (m_UsingMultisampleBuffer) { m_UsingMultisampleBuffer = false; DestroyMultisampleBuffer(); } // We have to hardcode names in the engine, because anti-aliasing // techinques strongly depend on the graphics pipeline. // We might use enums in future though. const CStr msaaPrefix = "msaa"; if (m_AAName == "fxaa") { m_AATech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("fxaa")); } else if (m_AAName.size() > msaaPrefix.size() && m_AAName.substr(0, msaaPrefix.size()) == msaaPrefix) { #if !CONFIG2_GLES // We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas. if (g_AtlasGameLoop && g_AtlasGameLoop->running) return; const bool is_msaa_supported = ogl_HaveVersion(3, 3) && ogl_HaveExtension("GL_ARB_multisample") && ogl_HaveExtension("GL_ARB_texture_multisample") && !m_AllowedSampleCounts.empty(); if (!is_msaa_supported) { LOGWARNING("MSAA is unsupported."); return; } std::stringstream ss(m_AAName.substr(msaaPrefix.size())); ss >> m_MultisampleCount; if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) == std::end(m_AllowedSampleCounts)) { m_MultisampleCount = 4; LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str()); } m_UsingMultisampleBuffer = true; CreateMultisampleBuffer(); #else #warning TODO: implement and test MSAA for GLES LOGWARNING("MSAA is unsupported."); #endif } } void CPostprocManager::UpdateSharpeningTechnique() { if (g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB || !m_IsInitialized) return; CStr newSharpName; CFG_GET_VAL("sharpening", newSharpName); if (m_SharpName == newSharpName) return; m_SharpName = newSharpName; m_SharpTech.reset(); if (m_SharpName == "cas") { m_SharpTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(m_SharpName)); } } void CPostprocManager::UpdateSharpnessFactor() { CFG_GET_VAL("sharpness", m_Sharpness); } void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane) { m_NearPlane = nearPlane; m_FarPlane = farPlane; } void CPostprocManager::CreateMultisampleBuffer() { glEnable(GL_MULTISAMPLE); m_MultisampleColorTex = Renderer::Backend::GL::CTexture::Create( Renderer::Backend::GL::CTexture::Type::TEXTURE_2D_MULTISAMPLE, Renderer::Backend::Format::R8G8B8A8, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleColorTex->GetHandle()); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleCount, GL_RGBA, m_Width, m_Height, GL_TRUE); // Allocate the Depth/Stencil texture. m_MultisampleDepthTex = Renderer::Backend::GL::CTexture::Create( Renderer::Backend::GL::CTexture::Type::TEXTURE_2D_MULTISAMPLE, Renderer::Backend::Format::D24_S8, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleDepthTex->GetHandle()); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleCount, GL_DEPTH24_STENCIL8_EXT, m_Width, m_Height, GL_TRUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); ogl_WarnIfError(); // Set up the framebuffers with some initial textures. glGenFramebuffersEXT(1, &m_MultisampleFBO); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleColorTex->GetHandle(), 0); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleDepthTex->GetHandle(), 0); GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Multisample framebuffer object incomplete (A): 0x%04X", status); m_UsingMultisampleBuffer = false; DestroyMultisampleBuffer(); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); glBindTexture(GL_TEXTURE_2D, 0); } void CPostprocManager::DestroyMultisampleBuffer() { if (m_UsingMultisampleBuffer) return; if (m_MultisampleFBO) glDeleteFramebuffersEXT(1, &m_MultisampleFBO); m_MultisampleColorTex.reset(); m_MultisampleDepthTex.reset(); glDisable(GL_MULTISAMPLE); } bool CPostprocManager::IsMultisampleEnabled() const { return m_UsingMultisampleBuffer; } void CPostprocManager::ResolveMultisampleFramebuffer() { if (!m_UsingMultisampleBuffer) return; glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, m_PingFbo); glBlitFramebufferEXT(0, 0, m_Width, m_Height, 0, 0, m_Width, m_Height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo); } #else #warning TODO: implement PostprocManager for GLES void ApplyBlurDownscale2x(GLuint UNUSED(inTex), GLuint UNUSED(outTex), int UNUSED(inWidth), int UNUSED(inHeight)) { } void CPostprocManager::ApplyBlurGauss(GLuint UNUSED(inOutTex), GLuint UNUSED(tempTex), int UNUSED(inWidth), int UNUSED(inHeight)) { } void CPostprocManager::ApplyEffect(CShaderTechniquePtr &UNUSED(shaderTech1), int UNUSED(pass)) { } CPostprocManager::CPostprocManager() { } CPostprocManager::~CPostprocManager() { } void CPostprocManager::Initialize() { } void CPostprocManager::Resize() { } void CPostprocManager::Cleanup() { } void CPostprocManager::RecreateBuffers() { } std::vector CPostprocManager::GetPostEffects() { return std::vector(); } void CPostprocManager::SetPostEffect(const CStrW& UNUSED(name)) { } void CPostprocManager::SetDepthBufferClipPlanes(float UNUSED(nearPlane), float UNUSED(farPlane)) { } void CPostprocManager::UpdateAntiAliasingTechnique() { } void CPostprocManager::UpdateSharpeningTechnique() { } void CPostprocManager::UpdateSharpnessFactor() { } void CPostprocManager::CaptureRenderOutput() { } void CPostprocManager::ApplyPostproc() { } void CPostprocManager::ReleaseRenderOutput() { } void CPostprocManager::CreateMultisampleBuffer() { } void CPostprocManager::DestroyMultisampleBuffer() { } bool CPostprocManager::IsMultisampleEnabled() const { return false; } void CPostprocManager::ResolveMultisampleFramebuffer() { } #endif Index: ps/trunk/source/renderer/PostprocManager.h =================================================================== --- ps/trunk/source/renderer/PostprocManager.h (revision 26142) +++ ps/trunk/source/renderer/PostprocManager.h (revision 26143) @@ -1,153 +1,153 @@ /* 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_POSTPROCMANAGER #define INCLUDED_POSTPROCMANAGER #include "graphics/ShaderTechniquePtr.h" #include "lib/ogl.h" #include "ps/CStr.h" #include "renderer/backend/gl/Texture.h" #include class CPostprocManager { public: CPostprocManager(); ~CPostprocManager(); // Create all buffers/textures in GPU memory and set default effect. // @note Must be called before using in the renderer. May be called multiple times. void Initialize(); // Update the size of the screen void Resize(); // Returns a list of xml files found in shaders/effects/postproc. static std::vector GetPostEffects(); // Returns the name of the current effect. const CStrW& GetPostEffect() const { return m_PostProcEffect; } // Sets the current effect. void SetPostEffect(const CStrW& name); // Triggers update of shaders and FBO if needed. void UpdateAntiAliasingTechnique(); void UpdateSharpeningTechnique(); void UpdateSharpnessFactor(); void SetDepthBufferClipPlanes(float nearPlane, float farPlane); // Clears the two color buffers and depth buffer, and redirects all rendering // to our textures instead of directly to the system framebuffer. // @note CPostprocManager must be initialized first void CaptureRenderOutput(); // First renders blur textures, then calls ApplyEffect for each effect pass, // ping-ponging the buffers at each step. // @note CPostprocManager must be initialized first void ApplyPostproc(); // Blits the final postprocessed texture to the system framebuffer. The system framebuffer // is selected as the output buffer. Should be called before silhouette rendering. // @note CPostprocManager must be initialized first void ReleaseRenderOutput(); // Returns true if we render main scene in the MSAA framebuffer. bool IsMultisampleEnabled() const; // Resolves the MSAA framebuffer into the regular one. void ResolveMultisampleFramebuffer(); private: void CreateMultisampleBuffer(); void DestroyMultisampleBuffer(); // Two framebuffers, that we flip between at each shader pass. GLuint m_PingFbo, m_PongFbo; // Unique color textures for the framebuffers. std::unique_ptr m_ColorTex1, m_ColorTex2; // The framebuffers share a depth/stencil texture. std::unique_ptr m_DepthTex; float m_NearPlane, m_FarPlane; // A framebuffer and textures x2 for each blur level we render. GLuint m_BloomFbo; std::unique_ptr m_BlurTex2a, m_BlurTex2b, m_BlurTex4a, m_BlurTex4b, m_BlurTex8a, m_BlurTex8b; // Indicates which of the ping-pong buffers is used for reading and which for drawing. bool m_WhichBuffer; // The name and shader technique we are using. "default" name means no technique is used // (i.e. while we do allocate the buffers, no effects are rendered). CStrW m_PostProcEffect; CShaderTechniquePtr m_PostProcTech; CStr m_SharpName; CShaderTechniquePtr m_SharpTech; float m_Sharpness; CStr m_AAName; CShaderTechniquePtr m_AATech; bool m_UsingMultisampleBuffer; GLuint m_MultisampleFBO; std::unique_ptr m_MultisampleColorTex, m_MultisampleDepthTex; GLsizei m_MultisampleCount; std::vector m_AllowedSampleCounts; // The current screen dimensions in pixels. int m_Width, m_Height; // Is the postproc manager initialized? Buffers created? Default effect loaded? bool m_IsInitialized; // Creates blur textures at various scales, for bloom, DOF, etc. void ApplyBlur(); // High quality GPU image scaling to half size. outTex must have exactly half the size // of inTex. inWidth and inHeight are the dimensions of inTex in texels. - void ApplyBlurDownscale2x(GLuint inTex, GLuint outTex, int inWidth, int inHeight); + void ApplyBlurDownscale2x(Renderer::Backend::GL::CTexture* inTex, Renderer::Backend::GL::CTexture* outTex, int inWidth, int inHeight); // GPU-based Gaussian blur in two passes. inOutTex contains the input image and will be filled // with the blurred image. tempTex must have the same size as inOutTex. // inWidth and inHeight are the dimensions of the images in texels. - void ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight); + void ApplyBlurGauss(Renderer::Backend::GL::CTexture* inOutTex, Renderer::Backend::GL::CTexture* tempTex, int inWidth, int inHeight); // Applies a pass of a given effect to the entire current framebuffer. The shader is // provided with a number of general-purpose variables, including the rendered screen so far, // the depth buffer, a number of blur textures, the screen size, the zNear/zFar planes and // some other parameters used by the optional bloom/HDR pass. void ApplyEffect(CShaderTechniquePtr &shaderTech1, int pass); // Delete all allocated buffers/textures from GPU memory. void Cleanup(); // Delete existing buffers/textures and create them again, using a new screen size if needed. // (the textures are also attached to the framebuffers) void RecreateBuffers(); }; #endif // INCLUDED_POSTPROCMANAGER Index: ps/trunk/source/renderer/ShadowMap.cpp =================================================================== --- ps/trunk/source/renderer/ShadowMap.cpp (revision 26142) +++ ps/trunk/source/renderer/ShadowMap.cpp (revision 26143) @@ -1,838 +1,838 @@ /* 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 "ShadowMap.h" #include "graphics/Camera.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "gui/GUIMatrix.h" #include "lib/bits.h" #include "lib/ogl.h" #include "maths/BoundingBoxAligned.h" #include "maths/Brush.h" #include "maths/Frustum.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Profile.h" #include "ps/VideoMode.h" #include "renderer/backend/gl/Texture.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include namespace { constexpr int MAX_CASCADE_COUNT = 4; constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f; constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f; } // anonymous namespace /** * Struct ShadowMapInternals: Internal data for the ShadowMap implementation */ struct ShadowMapInternals { // the EXT_framebuffer_object framebuffer GLuint Framebuffer; // handle of shadow map std::unique_ptr Texture; // bit depth for the depth texture int DepthTextureBits; // width, height of shadow map int Width, Height; // Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High) int QualityLevel; // used width, height of shadow map int EffectiveWidth, EffectiveHeight; // Transform world space into light space; calculated on SetupFrame CMatrix3D LightTransform; // transform light space into world space CMatrix3D InvLightTransform; CBoundingBoxAligned ShadowReceiverBound; int CascadeCount; float CascadeDistanceRatio; float ShadowsCutoffDistance; bool ShadowsCoverMap; struct Cascade { // transform light space into projected light space // in projected light space, the shadowbound box occupies the [-1..1] cube // calculated on BeginRender, after the final shadow bounds are known CMatrix3D LightProjection; float Distance; CBoundingBoxAligned FrustumBBAA; CBoundingBoxAligned ConvexBounds; CBoundingBoxAligned ShadowRenderBound; // Bounding box of shadowed objects in the light space. CBoundingBoxAligned ShadowCasterBound; // Transform world space into texture space of the shadow map; // calculated on BeginRender, after the final shadow bounds are known CMatrix3D TextureMatrix; // View port of the shadow texture where the cascade should be rendered. SViewPort ViewPort; }; std::array Cascades; // Camera transformed into light space CCamera LightspaceCamera; // Some drivers (at least some Intel Mesa ones) appear to handle alpha testing // incorrectly when the FBO has only a depth attachment. // When m_ShadowAlphaFix is true, we use DummyTexture to store a useless // alpha texture which is attached to the FBO as a workaround. std::unique_ptr DummyTexture; // Copy of renderer's standard view camera, saved between // BeginRender and EndRender while we replace it with the shadow camera CCamera SavedViewCamera; // Save the caller's FBO so it can be restored GLint SavedViewFBO; void CalculateShadowMatrices(const int cascade); void CreateTexture(); void UpdateCascadesParameters(); }; void ShadowMapInternals::UpdateCascadesParameters() { CascadeCount = 1; CFG_GET_VAL("shadowscascadecount", CascadeCount); if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB) CascadeCount = 1; ShadowsCoverMap = false; CFG_GET_VAL("shadowscovermap", ShadowsCoverMap); } void CalculateBoundsForCascade( const CCamera& camera, const CMatrix3D& lightTransform, const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa, CBoundingBoxAligned* frustumBBAA) { frustumBBAA->SetEmpty(); // We need to calculate a circumscribed sphere for the camera to // create a rotation stable bounding box. const CVector3D cameraIn = camera.m_Orientation.GetIn(); const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation(); const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane; const CVector3D centerDist = cameraTranslation + cameraIn * farPlane; // We can solve 3D problem in 2D space, because the frustum is // symmetric by 2 planes. Than means we can use only one corner // to find a circumscribed sphere. CCamera::Quad corners; camera.GetViewQuad(nearPlane, corners); for (CVector3D& corner : corners) corner = camera.GetOrientation().Transform(corner); const CVector3D cornerNear = corners[0]; for (const CVector3D& corner : corners) *frustumBBAA += lightTransform.Transform(corner); camera.GetViewQuad(farPlane, corners); for (CVector3D& corner : corners) corner = camera.GetOrientation().Transform(corner); const CVector3D cornerDist = corners[0]; for (const CVector3D& corner : corners) *frustumBBAA += lightTransform.Transform(corner); // We solve 2D case for the right trapezoid. const float firstBase = (cornerNear - centerNear).Length(); const float secondBase = (cornerDist - centerDist).Length(); const float height = (centerDist - centerNear).Length(); const float distanceToCenter = (height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height; CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter); const float radius = (cornerNear - position).Length(); // We need to convert the bounding box to the light space. position = lightTransform.Rotate(position); const float insets = 0.2f; *bbaa = CBoundingBoxAligned(position, position); bbaa->Expand(radius); bbaa->Expand(insets); } ShadowMap::ShadowMap() { m = new ShadowMapInternals; m->Framebuffer = 0; m->Width = 0; m->Height = 0; m->QualityLevel = 0; m->EffectiveWidth = 0; m->EffectiveHeight = 0; m->DepthTextureBits = 0; // DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX; // but they're very much slower on Radeon 9800. // In both cases, the default (no specified depth) is fast, so we just use // that by default and hope it's alright. (Otherwise, we'd probably need to // do some kind of hardware detection to work out what to use.) // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first m->LightTransform.SetIdentity(); m->UpdateCascadesParameters(); } ShadowMap::~ShadowMap() { m->Texture.reset(); m->DummyTexture.reset(); if (m->Framebuffer) glDeleteFramebuffersEXT(1, &m->Framebuffer); delete m; } // Force the texture/buffer/etc to be recreated, particularly when the renderer's // size has changed void ShadowMap::RecreateTexture() { m->Texture.reset(); m->DummyTexture.reset(); if (m->Framebuffer) glDeleteFramebuffersEXT(1, &m->Framebuffer); m->Framebuffer = 0; m->UpdateCascadesParameters(); // (Texture will be constructed in next SetupFrame) } // SetupFrame: camera and light direction for this frame void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) { if (!m->Texture) m->CreateTexture(); CVector3D x(0, 1, 0), eyepos; CVector3D z = lightdir; z.Normalize(); x -= z * z.Dot(x); if (x.Length() < 0.001) { // this is invoked if the camera and light directions almost coincide // assumption: light direction has a significant Z component x = CVector3D(1.0, 0.0, 0.0); x -= z * z.Dot(x); } x.Normalize(); CVector3D y = z.Cross(x); // X axis perpendicular to light direction, flowing along with view direction m->LightTransform._11 = x.X; m->LightTransform._12 = x.Y; m->LightTransform._13 = x.Z; // Y axis perpendicular to light and view direction m->LightTransform._21 = y.X; m->LightTransform._22 = y.Y; m->LightTransform._23 = y.Z; // Z axis is in direction of light m->LightTransform._31 = z.X; m->LightTransform._32 = z.Y; m->LightTransform._33 = z.Z; // eye is at the origin of the coordinate system m->LightTransform._14 = -x.Dot(eyepos); m->LightTransform._24 = -y.Dot(eyepos); m->LightTransform._34 = -z.Dot(eyepos); m->LightTransform._41 = 0.0; m->LightTransform._42 = 0.0; m->LightTransform._43 = 0.0; m->LightTransform._44 = 1.0; m->LightTransform.GetInverse(m->InvLightTransform); m->ShadowReceiverBound.SetEmpty(); m->LightspaceCamera = camera; m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation; m->LightspaceCamera.UpdateFrustum(); m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE; m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO; CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance); CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio); m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f); m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance; for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade) m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio; if (GetCascadeCount() == 1 || m->ShadowsCoverMap) { m->Cascades[0].ViewPort = SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2}; if (m->ShadowsCoverMap) m->Cascades[0].Distance = camera.GetFarPlane(); } else { for (int cascade = 0; cascade < GetCascadeCount(); ++cascade) { const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0; const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0; m->Cascades[cascade].ViewPort = SViewPort{offsetX + 1, offsetY + 1, m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2}; } } for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx) { ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx]; const float nearPlane = cascadeIdx > 0 ? m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane(); const float farPlane = cascade.Distance; CalculateBoundsForCascade(camera, m->LightTransform, nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA); cascade.ShadowCasterBound.SetEmpty(); } } // AddShadowedBound: add a world-space bounding box to the bounds of shadowed // objects void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds) { CBoundingBoxAligned lightspacebounds; bounds.Transform(m->LightTransform, lightspacebounds); m->Cascades[cascade].ShadowCasterBound += lightspacebounds; } void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds) { CBoundingBoxAligned lightspacebounds; bounds.Transform(m->LightTransform, lightspacebounds); m->ShadowReceiverBound += lightspacebounds; } CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade) { // Get the bounds of all objects that can receive shadows CBoundingBoxAligned bound = m->ShadowReceiverBound; // Intersect with the camera frustum, so the shadow map doesn't have to get // stretched to cover the off-screen parts of large models bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum()); // ShadowBound might have been empty to begin with, producing an empty result if (bound.IsEmpty()) { // CFrustum can't easily represent nothingness, so approximate it with // a single point which won't match many objects bound += CVector3D(0.0f, 0.0f, 0.0f); return bound.ToFrustum(); } // Extend the bounds a long way towards the light source, to encompass // all objects that might cast visible shadows. // (The exact constant was picked entirely arbitrarily.) bound[0].Z -= 1000.f; CFrustum frustum = bound.ToFrustum(); frustum.Transform(m->InvLightTransform); return frustum; } // CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's // projection and transformation matrices void ShadowMapInternals::CalculateShadowMatrices(const int cascade) { CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound; shadowRenderBound = Cascades[cascade].ConvexBounds; if (ShadowsCoverMap) { // Start building the shadow map to cover all objects that will receive shadows CBoundingBoxAligned receiverBound = ShadowReceiverBound; // Intersect with the camera frustum, so the shadow map doesn't have to get // stretched to cover the off-screen parts of large models receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); // Intersect with the shadow caster bounds, because there's no point // wasting space around the edges of the shadow map that we're not going // to draw into shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X); shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y); shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X); shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y); } else if (CascadeCount > 1) { // We need to offset the cascade to its place on the texture. const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f; if (!(cascade & 0x1)) shadowRenderBound[1].X += size.X * 2.0f; else shadowRenderBound[0].X -= size.X * 2.0f; if (!(cascade & 0x2)) shadowRenderBound[1].Y += size.Y * 2.0f; else shadowRenderBound[0].Y -= size.Y * 2.0f; } // Set the near and far planes to include just the shadow casters, // so we make full use of the depth texture's range. Add a bit of a // delta so we don't accidentally clip objects that are directly on // the planes. shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f; shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f; // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0]; CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5; if (scale.X < 1.0) scale.X = 1.0; if (scale.Y < 1.0) scale.Y = 1.0; if (scale.Z < 1.0) scale.Z = 1.0; scale.X = 2.0 / scale.X; scale.Y = 2.0 / scale.Y; scale.Z = 2.0 / scale.Z; // make sure a given world position falls on a consistent shadowmap texel fractional offset float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth)); float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight)); CMatrix3D& lightProjection = Cascades[cascade].LightProjection; lightProjection.SetZero(); lightProjection._11 = scale.X; lightProjection._14 = (shift.X + offsetX) * scale.X; lightProjection._22 = scale.Y; lightProjection._24 = (shift.Y + offsetY) * scale.Y; lightProjection._33 = scale.Z; lightProjection._34 = shift.Z * scale.Z; lightProjection._44 = 1.0; // Calculate texture matrix by creating the clip space to texture coordinate matrix // and then concatenating all matrices that have been calculated so far float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width; float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height; float texscalez = scale.Z * 0.5f; CMatrix3D lightToTex; lightToTex.SetZero(); lightToTex._11 = texscalex; lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex; lightToTex._22 = texscaley; lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley; lightToTex._33 = texscalez; lightToTex._34 = -shadowRenderBound[0].Z * texscalez; lightToTex._44 = 1.0; Cascades[cascade].TextureMatrix = lightToTex * LightTransform; } // Create the shadow map void ShadowMapInternals::CreateTexture() { // Cleanup Texture.reset(); DummyTexture.reset(); if (Framebuffer) { glDeleteFramebuffersEXT(1, &Framebuffer); Framebuffer = 0; } // save the caller's FBO glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &SavedViewFBO); glGenFramebuffersEXT(1, &Framebuffer); CFG_GET_VAL("shadowquality", QualityLevel); // Get shadow map size as next power of two up from view width/height. int shadowMapSize; switch (QualityLevel) { // Low case -1: shadowMapSize = 512; break; // High case 1: shadowMapSize = 2048; break; // Ultra case 2: shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())) * 4, 4096); break; // Medium as is default: shadowMapSize = 1024; break; } // Clamp to the maximum texture size. shadowMapSize = std::min(shadowMapSize, static_cast(ogl_max_tex_size)); Width = Height = shadowMapSize; // Since we're using a framebuffer object, the whole texture is available EffectiveWidth = Width; EffectiveHeight = Height; GLenum format; const char* formatName; Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED; #if CONFIG2_GLES format = GL_DEPTH_COMPONENT; formatName = "DEPTH_COMPONENT"; backendFormat = Renderer::Backend::Format::D24; #else switch (DepthTextureBits) { case 16: format = GL_DEPTH_COMPONENT16; formatName = "DEPTH_COMPONENT16"; backendFormat = Renderer::Backend::Format::D16; break; case 24: format = GL_DEPTH_COMPONENT24; formatName = "DEPTH_COMPONENT24"; backendFormat = Renderer::Backend::Format::D24; break; case 32: format = GL_DEPTH_COMPONENT32; formatName = "DEPTH_COMPONENT32"; backendFormat = Renderer::Backend::Format::D32; break; default: format = GL_DEPTH_COMPONENT; formatName = "DEPTH_COMPONENT"; backendFormat = Renderer::Backend::Format::D24; break; } #endif ENSURE(formatName); LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)", Width, Height, formatName); if (g_RenderingOptions.GetShadowAlphaFix()) { DummyTexture = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::R8G8B8A8, Width, Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); g_Renderer.BindTexture(0, DummyTexture->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } Texture = Renderer::Backend::GL::CTexture::Create2D( backendFormat, Width, Height, Renderer::Backend::Sampler::MakeDefaultSampler( #if CONFIG2_GLES // GLES doesn't do depth comparisons, so treat it as a // basic unfiltered depth texture Renderer::Backend::Sampler::Filter::NEAREST, #else // Use GL_LINEAR to trigger automatic PCF on some devices Renderer::Backend::Sampler::Filter::LINEAR, #endif Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); g_Renderer.BindTexture(0, Texture->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); // GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT #if !CONFIG2_GLES // Enable automatic depth comparisons glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); #endif ogl_WarnIfError(); // bind to framebuffer object glBindTexture(GL_TEXTURE_2D, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Framebuffer); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Texture->GetHandle(), 0); if (g_RenderingOptions.GetShadowAlphaFix()) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture->GetHandle(), 0); } else { #if CONFIG2_GLES #warning TODO: figure out whether the glDrawBuffer/glReadBuffer stuff is needed, since it is not supported by GLES #else glDrawBuffer(GL_NONE); #endif } #if !CONFIG2_GLES glReadBuffer(GL_NONE); #endif ogl_WarnIfError(); GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { LOGWARNING("Framebuffer object incomplete: 0x%04X", status); // Disable shadow rendering (but let the user try again if they want) g_RenderingOptions.SetShadows(false); } } // Set up to render into shadow map texture void ShadowMap::BeginRender() { { PROFILE("bind framebuffer"); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->Framebuffer); } // clear buffers { PROFILE("clear depth texture"); // In case we used m_ShadowAlphaFix, we ought to clear the unused // color buffer too, else Mali 400 drivers get confused. // Might as well clear stencil too for completeness. if (g_RenderingOptions.GetShadowAlphaFix()) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); } else glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } m->SavedViewCamera = g_Renderer.GetViewCamera(); glEnable(GL_SCISSOR_TEST); } void ShadowMap::PrepareCamera(const int cascade) { m->CalculateShadowMatrices(cascade); const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight }; g_Renderer.SetViewport(vp); CCamera camera = m->SavedViewCamera; camera.SetProjection(m->Cascades[cascade].LightProjection); camera.GetOrientation() = m->InvLightTransform; g_Renderer.SetViewCamera(camera); const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort; glScissor( cascadeViewPort.m_X, cascadeViewPort.m_Y, cascadeViewPort.m_Width, cascadeViewPort.m_Height); } // Finish rendering into shadow map texture void ShadowMap::EndRender() { glDisable(GL_SCISSOR_TEST); g_Renderer.SetViewCamera(m->SavedViewCamera); { PROFILE("unbind framebuffer"); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() }; g_Renderer.SetViewport(vp); if (g_RenderingOptions.GetShadowAlphaFix()) glColorMask(1, 1, 1, 1); } void ShadowMap::BindTo(const CShaderProgramPtr& shader) const { if (!shader->GetTextureBinding(str_shadowTex).Active() || !m->Texture) return; - shader->BindTexture(str_shadowTex, m->Texture->GetHandle()); + shader->BindTexture(str_shadowTex, m->Texture.get()); shader->Uniform(str_shadowScale, m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height); const CVector3D cameraForward = g_Renderer.GetCullCamera().GetOrientation().GetIn(); shader->Uniform(str_cameraForward, cameraForward.X, cameraForward.Y, cameraForward.Z, cameraForward.Dot(g_Renderer.GetCullCamera().GetOrientation().GetTranslation())); if (GetCascadeCount() == 1) { shader->Uniform(str_shadowTransform, m->Cascades[0].TextureMatrix); shader->Uniform(str_shadowDistance, m->Cascades[0].Distance); } else { std::vector shadowDistances; std::vector shadowTransforms; for (const ShadowMapInternals::Cascade& cascade : m->Cascades) { shadowDistances.emplace_back(cascade.Distance); shadowTransforms.emplace_back(cascade.TextureMatrix); } shader->Uniform(str_shadowTransforms_0, GetCascadeCount(), shadowTransforms.data()); shader->Uniform(str_shadowTransforms, GetCascadeCount(), shadowTransforms.data()); shader->Uniform(str_shadowDistances_0, GetCascadeCount(), shadowDistances.data()); shader->Uniform(str_shadowDistances, GetCascadeCount(), shadowDistances.data()); } } // Depth texture bits int ShadowMap::GetDepthTextureBits() const { return m->DepthTextureBits; } void ShadowMap::SetDepthTextureBits(int bits) { if (bits != m->DepthTextureBits) { m->Texture.reset(); m->Width = m->Height = 0; m->DepthTextureBits = bits; } } void ShadowMap::RenderDebugBounds() { glDepthMask(0); glDisable(GL_CULL_FACE); // Render various shadow bounds: // Yellow = bounds of objects in view frustum that receive shadows // Red = culling frustum used to find potential shadow casters // Blue = frustum used for rendering the shadow map const CMatrix3D transform = g_Renderer.GetViewCamera().GetViewProjection() * m->InvLightTransform; glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform); for (int cascade = 0; cascade < GetCascadeCount(); ++cascade) { glEnable(GL_BLEND); g_Renderer.GetDebugRenderer().DrawBoundingBox(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform); g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform); glDisable(GL_BLEND); const CFrustum frustum = GetShadowCasterCullFrustum(cascade); // We don't have a function to create a brush directly from a frustum, so use // the ugly approach of creating a large cube and then intersecting with the frustum const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4)); CBrush brush(dummy); CBrush frustumBrush; brush.Intersect(frustum, frustumBrush); glEnable(GL_BLEND); g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f)); g_Renderer.GetDebugRenderer().DrawBrushOutline(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.5f)); glDisable(GL_BLEND); } glEnable(GL_CULL_FACE); glDepthMask(1); ogl_WarnIfError(); } void ShadowMap::RenderDebugTexture() { glDepthMask(0); glDisable(GL_DEPTH_TEST); #if !CONFIG2_GLES g_Renderer.BindTexture(0, m->Texture->GetHandle()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); #endif CShaderTechniquePtr texTech = g_Renderer.GetShaderManager().LoadEffect(str_canvas2d); texTech->BeginPass(); CShaderProgramPtr texShader = texTech->GetShader(); texShader->Uniform(str_transform, GetDefaultGuiMatrix()); - texShader->BindTexture(str_tex, m->Texture->GetHandle()); + texShader->BindTexture(str_tex, m->Texture.get()); texShader->Uniform(str_colorAdd, CColor(0.0f, 0.0f, 0.0f, 1.0f)); texShader->Uniform(str_colorMul, CColor(1.0f, 1.0f, 1.0f, 0.0f)); texShader->Uniform(str_grayscaleFactor, 0.0f); float s = 256.f; float boxVerts[] = { 0,0, 0,s, s,0, s,0, 0,s, s,s }; float boxUV[] = { 0,0, 0,1, 1,0, 1,0, 0,1, 1,1 }; texShader->VertexPointer(2, GL_FLOAT, 0, boxVerts); texShader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, boxUV); texShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); texTech->EndPass(); #if !CONFIG2_GLES g_Renderer.BindTexture(0, m->Texture->GetHandle()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); #endif glEnable(GL_DEPTH_TEST); glDepthMask(1); ogl_WarnIfError(); } int ShadowMap::GetCascadeCount() const { #if CONFIG2_GLES return 1; #else return m->ShadowsCoverMap ? 1 : m->CascadeCount; #endif } Index: ps/trunk/source/renderer/SkyManager.cpp =================================================================== --- ps/trunk/source/renderer/SkyManager.cpp (revision 26142) +++ ps/trunk/source/renderer/SkyManager.cpp (revision 26143) @@ -1,316 +1,316 @@ /* 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 . */ /* * Sky settings, texture management and rendering. */ #include "precompiled.h" #include "renderer/SkyManager.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/bits.h" #include "lib/ogl.h" #include "lib/tex/tex.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/CStr.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Loader.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include SkyManager::SkyManager() : m_RenderSky(true) { CFG_GET_VAL("showsky", m_RenderSky); } /////////////////////////////////////////////////////////////////// // Load all sky textures void SkyManager::LoadSkyTextures() { OGL_SCOPED_DEBUG_GROUP("Load Sky Textures"); static const CStrW images[NUMBER_OF_TEXTURES + 1] = { L"front", L"back", L"right", L"left", L"top", L"top" }; /*for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i) { VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(s_imageNames[i])+L".dds"); CTextureProperties textureProps(path); textureProps.SetWrap(GL_CLAMP_TO_EDGE); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_SkyTexture[i] = texture; }*/ /////////////////////////////////////////////////////////////////////////// // HACK: THE HORRIBLENESS HERE IS OVER 9000. The following code is a HUGE hack and will be removed completely // as soon as all the hardcoded GL_TEXTURE_2D references are corrected in the TextureManager/OGL/tex libs. Tex textures[NUMBER_OF_TEXTURES + 1]; for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i) { VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds"); std::shared_ptr file; size_t fileSize; if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK) { path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds.cached.dds"); if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK) { LOGERROR("Error creating sky cubemap '%s', can't load file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str()); return; } } textures[i].decode(file, fileSize); textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)); if (!is_pow2(textures[i].m_Width) || !is_pow2(textures[i].m_Height)) { LOGERROR("Error creating sky cubemap '%s', cube textures should have power of 2 sizes.", m_SkySet.ToUTF8().c_str()); return; } if (textures[i].m_Width != textures[0].m_Width || textures[i].m_Height != textures[0].m_Height) { LOGERROR("Error creating sky cubemap '%s', cube textures have different sizes.", m_SkySet.ToUTF8().c_str()); return; } } m_SkyCubeMap = Renderer::Backend::GL::CTexture::Create(Renderer::Backend::GL::CTexture::Type::TEXTURE_CUBE, Renderer::Backend::Format::R8G8B8A8, textures[0].m_Width, textures[0].m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1); glBindTexture(GL_TEXTURE_CUBE_MAP, m_SkyCubeMap->GetHandle()); static const int types[] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y }; std::vector rotated; for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i) { u8* data = textures[i].get_data(); if (types[i] == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y || types[i] == GL_TEXTURE_CUBE_MAP_POSITIVE_Y) { rotated.resize(textures[i].m_DataSize); for (size_t y = 0; y < textures[i].m_Height; ++y) { for (size_t x = 0; x < textures[i].m_Width; ++x) { const size_t invX = y; const size_t invY = textures[i].m_Width - x - 1; rotated[(y * textures[i].m_Width + x) * 4 + 0] = data[(invY * textures[i].m_Width + invX) * 4 + 0]; rotated[(y * textures[i].m_Width + x) * 4 + 1] = data[(invY * textures[i].m_Width + invX) * 4 + 1]; rotated[(y * textures[i].m_Width + x) * 4 + 2] = data[(invY * textures[i].m_Width + invX) * 4 + 2]; rotated[(y * textures[i].m_Width + x) * 4 + 3] = data[(invY * textures[i].m_Width + invX) * 4 + 3]; } } glTexImage2D(types[i], 0, GL_RGBA, textures[i].m_Width, textures[i].m_Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &rotated[0]); } else { glTexImage2D(types[i], 0, GL_RGBA, textures[i].m_Width, textures[i].m_Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); } } glBindTexture(GL_TEXTURE_CUBE_MAP, 0); /////////////////////////////////////////////////////////////////////////// } /////////////////////////////////////////////////////////////////// // Switch to a different sky set (while the game is running) void SkyManager::SetSkySet(const CStrW& newSet) { if (newSet == m_SkySet) return; m_SkyCubeMap.reset(); m_SkySet = newSet; LoadSkyTextures(); } /////////////////////////////////////////////////////////////////// // Generate list of available skies std::vector SkyManager::GetSkySets() const { std::vector skies; // Find all subdirectories in art/textures/skies const VfsPath path(L"art/textures/skies/"); DirectoryNames subdirectories; if (g_VFS->GetDirectoryEntries(path, 0, &subdirectories) != INFO::OK) { LOGERROR("Error opening directory '%s'", path.string8()); return std::vector(1, GetSkySet()); // just return what we currently have } for(size_t i = 0; i < subdirectories.size(); i++) skies.push_back(subdirectories[i].string()); sort(skies.begin(), skies.end()); return skies; } /////////////////////////////////////////////////////////////////// // Render sky void SkyManager::RenderSky() { OGL_SCOPED_DEBUG_GROUP("Render Sky"); #if CONFIG2_GLES #warning TODO: implement SkyManager::RenderSky for GLES #else if (!m_RenderSky) return; // Draw the sky as a small box around the map, with depth write enabled. // This will be done before anything else is drawn so we'll be overlapped by // everything else. // Do nothing unless SetSkySet was called if (m_SkySet.empty() || !m_SkyCubeMap) return; glDepthMask(GL_FALSE); const CCamera& camera = g_Renderer.GetViewCamera(); CShaderTechniquePtr skytech = g_Renderer.GetShaderManager().LoadEffect(str_sky_simple); skytech->BeginPass(); CShaderProgramPtr shader = skytech->GetShader(); - shader->BindTexture(str_baseTex, m_SkyCubeMap->GetHandle()); + shader->BindTexture(str_baseTex, m_SkyCubeMap.get()); // Translate so the sky center is at the camera space origin. CMatrix3D translate; translate.SetTranslation(camera.GetOrientation().GetTranslation()); // Currently we have a hardcoded near plane in the projection matrix. CMatrix3D scale; scale.SetScaling(10.0f, 10.0f, 10.0f); // Rotate so that the "left" face, which contains the brightest part of // each skymap, is in the direction of the sun from our light // environment. CMatrix3D rotate; rotate.SetYRotation(M_PI + g_Renderer.GetLightEnv().GetRotation()); shader->Uniform( str_transform, camera.GetViewProjection() * translate * rotate * scale); std::vector vertexData; // 6 sides of cube with 4 vertices with 6 floats (3 uv and 3 position). vertexData.reserve(6 * 4 * 6); #define ADD_VERTEX(U, V, W, X, Y, Z) \ STMT( \ vertexData.push_back(X); \ vertexData.push_back(Y); \ vertexData.push_back(Z); \ vertexData.push_back(U); \ vertexData.push_back(V); \ vertexData.push_back(W);) // GL_TEXTURE_CUBE_MAP_NEGATIVE_X ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); // GL_TEXTURE_CUBE_MAP_POSITIVE_X ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); // GL_TEXTURE_CUBE_MAP_NEGATIVE_Y ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); // GL_TEXTURE_CUBE_MAP_POSITIVE_Y ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); // GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); // GL_TEXTURE_CUBE_MAP_POSITIVE_Z ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); #undef ADD_VERTEX shader->VertexPointer(3, GL_FLOAT, sizeof(GLfloat) * 6, &vertexData[0]); shader->TexCoordPointer( GL_TEXTURE0, 3, GL_FLOAT, sizeof(GLfloat) * 6, &vertexData[3]); shader->AssertPointersBound(); glDrawArrays(GL_QUADS, 0, 6 * 4); skytech->EndPass(); glDepthMask(GL_TRUE); #endif } Index: ps/trunk/source/renderer/SkyManager.h =================================================================== --- ps/trunk/source/renderer/SkyManager.h (revision 26142) +++ ps/trunk/source/renderer/SkyManager.h (revision 26143) @@ -1,103 +1,103 @@ /* 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 . */ /* * Sky settings and texture management */ #ifndef INCLUDED_SKYMANAGER #define INCLUDED_SKYMANAGER #include "graphics/Texture.h" #include "renderer/backend/gl/Texture.h" #include /** * Class SkyManager: Maintain sky settings and textures, and render the sky. */ class SkyManager { public: SkyManager(); /** * Render the sky. */ void RenderSky(); /** * Return the currently selected sky set name. */ inline const CStrW& GetSkySet() const { return m_SkySet; } - GLuint GetSkyCube() + Renderer::Backend::GL::CTexture* GetSkyCube() { - return m_SkyCubeMap->GetHandle(); + return m_SkyCubeMap.get(); } /** * Set the sky set name, potentially loading the textures. */ void SetSkySet(const CStrW& name); /** * Return a sorted list of available sky sets, in a form suitable * for passing to SetSkySet. */ std::vector GetSkySets() const; bool GetRenderSky() const { return m_RenderSky; } void SetRenderSky(bool value) { m_RenderSky = value; } private: void LoadSkyTextures(); bool m_RenderSky; /// Name of current skyset (a directory within art/textures/skies) CStrW m_SkySet; // Indices into m_SkyTexture enum { FRONT, BACK, RIGHT, LEFT, TOP, NUMBER_OF_TEXTURES }; // Sky textures CTexturePtr m_SkyTexture[NUMBER_OF_TEXTURES]; std::unique_ptr m_SkyCubeMap; }; #endif // INCLUDED_SKYMANAGER Index: ps/trunk/source/renderer/TerrainOverlay.cpp =================================================================== --- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26142) +++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26143) @@ -1,390 +1,391 @@ /* 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 "TerrainOverlay.h" #include "graphics/Color.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgram.h" #include "graphics/Terrain.h" #include "lib/bits.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/TerrainRenderer.h" #include "simulation2/system/SimContext.h" #include // Global overlay list management: static std::vector > g_TerrainOverlayList; ITerrainOverlay::ITerrainOverlay(int priority) { // Add to global list of overlays g_TerrainOverlayList.emplace_back(this, priority); // Sort by overlays by priority. Do stable sort so that adding/removing // overlays doesn't randomly disturb all the existing ones (which would // be noticeable if they have the same priority and overlap). std::stable_sort(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(), [](const std::pair& a, const std::pair& b) { return a.second < b.second; }); } ITerrainOverlay::~ITerrainOverlay() { std::vector >::iterator newEnd = std::remove_if(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(), [this](const std::pair& a) { return a.first == this; }); g_TerrainOverlayList.erase(newEnd, g_TerrainOverlayList.end()); } void ITerrainOverlay::RenderOverlaysBeforeWater() { if (g_TerrainOverlayList.empty()) return; PROFILE3_GPU("terrain overlays (before)"); for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i) g_TerrainOverlayList[i].first->RenderBeforeWater(); } void ITerrainOverlay::RenderOverlaysAfterWater(int cullGroup) { if (g_TerrainOverlayList.empty()) return; PROFILE3_GPU("terrain overlays (after)"); for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i) g_TerrainOverlayList[i].first->RenderAfterWater(cullGroup); } ////////////////////////////////////////////////////////////////////////// TerrainOverlay::TerrainOverlay(const CSimContext& simContext, int priority /* = 100 */) : ITerrainOverlay(priority), m_Terrain(&simContext.GetTerrain()) { } void TerrainOverlay::StartRender() { } void TerrainOverlay::EndRender() { } void TerrainOverlay::GetTileExtents( ssize_t& min_i_inclusive, ssize_t& min_j_inclusive, ssize_t& max_i_inclusive, ssize_t& max_j_inclusive) { // Default to whole map min_i_inclusive = min_j_inclusive = 0; max_i_inclusive = max_j_inclusive = m_Terrain->GetTilesPerSide()-1; } void TerrainOverlay::RenderBeforeWater() { if (!m_Terrain) return; // should never happen, but let's play it safe #if CONFIG2_GLES #warning TODO: implement TerrainOverlay::RenderOverlays for GLES #else glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(GL_FALSE); // To ensure that outlines are drawn on top of the terrain correctly (and // don't Z-fight and flicker nastily), draw them as QUADS with the LINE // PolygonMode, and use PolygonOffset to pull them towards the camera. // (See e.g. http://www.opengl.org/resources/faq/technical/polygonoffset.htm) glPolygonOffset(-1.f, -1.f); //glEnable(GL_POLYGON_OFFSET_LINE); glEnable(GL_POLYGON_OFFSET_FILL); glActiveTextureARB(GL_TEXTURE0); StartRender(); ssize_t min_i, min_j, max_i, max_j; GetTileExtents(min_i, min_j, max_i, max_j); // Clamp the min to 0, but the max to -1 - so tile -1 can never be rendered, // but if unclamped_max<0 then no tiles at all will be rendered. And the same // for the upper limit. min_i = Clamp(min_i, 0, m_Terrain->GetTilesPerSide()); min_j = Clamp(min_j, 0, m_Terrain->GetTilesPerSide()); max_i = Clamp(max_i, -1, m_Terrain->GetTilesPerSide()-1); max_j = Clamp(max_j, -1, m_Terrain->GetTilesPerSide()-1); for (m_j = min_j; m_j <= max_j; ++m_j) for (m_i = min_i; m_i <= max_i; ++m_i) ProcessTile(m_i, m_j); EndRender(); // Clean up state changes glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); //glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_POLYGON_OFFSET_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glDepthMask(GL_TRUE); glDisable(GL_BLEND); #endif } void TerrainOverlay::RenderTile(const CColor& color, bool draw_hidden) { RenderTile(color, draw_hidden, m_i, m_j); } void TerrainOverlay::RenderTile(const CColor& color, bool draw_hidden, ssize_t i, ssize_t j) { // TODO: unnecessary computation calls has been removed but we should use // a vertex buffer or a vertex shader with a texture. // Not sure if it's possible on old OpenGL. if (draw_hidden) { glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); } else { glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); } #if CONFIG2_GLES #warning TODO: implement TerrainOverlay::RenderTile for GLES #else CVector3D pos[2][2]; for (int di = 0; di < 2; ++di) for (int dj = 0; dj < 2; ++dj) m_Terrain->CalcPosition(i + di, j + dj, pos[di][dj]); std::vector vertices; #define ADD(position) \ vertices.emplace_back((position).X); \ vertices.emplace_back((position).Y); \ vertices.emplace_back((position).Z); if (m_Terrain->GetTriangulationDir(i, j)) { ADD(pos[0][0]); ADD(pos[1][0]); ADD(pos[0][1]); ADD(pos[1][0]); ADD(pos[1][1]); ADD(pos[0][1]); } else { ADD(pos[0][0]); ADD(pos[1][0]); ADD(pos[1][1]); ADD(pos[1][1]); ADD(pos[0][1]); ADD(pos[0][0]); } #undef ADD CShaderTechniquePtr overlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); overlayTech->BeginPass(); CShaderProgramPtr overlayShader = overlayTech->GetShader(); overlayShader->Bind(); overlayShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); overlayShader->Uniform(str_color, color); overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data()); overlayShader->AssertPointersBound(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 3); overlayShader->Unbind(); overlayTech->EndPass(); #endif } void TerrainOverlay::RenderTileOutline(const CColor& color, int line_width, bool draw_hidden) { RenderTileOutline(color, line_width, draw_hidden, m_i, m_j); } void TerrainOverlay::RenderTileOutline(const CColor& color, int line_width, bool draw_hidden, ssize_t i, ssize_t j) { if (draw_hidden) { glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); } else { glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); } #if CONFIG2_GLES #warning TODO: implement TerrainOverlay::RenderTileOutline for GLES #else glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); if (line_width != 1) glLineWidth((float)line_width); std::vector vertices; #define ADD(i, j) \ m_Terrain->CalcPosition(i, j, position); \ vertices.emplace_back(position.X); \ vertices.emplace_back(position.Y); \ vertices.emplace_back(position.Z); CVector3D position; ADD(i, j); ADD(i+1, j); ADD(i+1, j+1); ADD(i, j+1); #undef ADD CShaderTechniquePtr overlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); overlayTech->BeginPass(); CShaderProgramPtr overlayShader = overlayTech->GetShader(); overlayShader->Bind(); overlayShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); overlayShader->Uniform(str_color, color); overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data()); overlayShader->AssertPointersBound(); glDrawArrays(GL_QUADS, 0, vertices.size() / 3); overlayShader->Unbind(); overlayTech->EndPass(); if (line_width != 1) glLineWidth(1.0f); #endif } ////////////////////////////////////////////////////////////////////////// TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) : ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile) { } TerrainTextureOverlay::~TerrainTextureOverlay() = default; void TerrainTextureOverlay::RenderAfterWater(int cullGroup) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile); ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile); const uint32_t requiredWidth = round_up_to_pow2(w); const uint32_t requiredHeight = round_up_to_pow2(h); glActiveTextureARB(GL_TEXTURE0); // Recreate the texture with new size if necessary if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight) { m_Texture = Renderer::Backend::GL::CTexture::Create2D( Renderer::Backend::Format::R8G8B8A8, requiredWidth, requiredHeight, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); glBindTexture(GL_TEXTURE_2D, m_Texture->GetHandle()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_Texture->GetWidth(), m_Texture->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } u8* data = (u8*)calloc(w * h, 4); BuildTextureRGBA(data, w, h); glBindTexture(GL_TEXTURE_2D, m_Texture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data); free(data); CMatrix3D matrix; matrix.SetZero(); matrix._11 = m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE); matrix._23 = m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE); matrix._44 = 1; - g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(cullGroup, matrix, m_Texture->GetHandle()); + g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(cullGroup, matrix, m_Texture.get()); } SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const { - static u8 colors[][3] = { + static u8 colors[][3] = + { { 255, 0, 0 }, { 0, 255, 0 }, { 0, 0, 255 }, { 255, 255, 0 }, { 255, 0, 255 }, { 0, 255, 255 }, { 255, 255, 255 }, { 127, 0, 0 }, { 0, 127, 0 }, { 0, 0, 127 }, { 127, 127, 0 }, { 127, 0, 127 }, { 0, 127, 127 }, { 127, 127, 127}, { 255, 127, 0 }, { 127, 255, 0 }, { 255, 0, 127 }, { 127, 0, 255}, { 0, 255, 127 }, { 0, 127, 255}, { 255, 127, 127}, { 127, 255, 127}, { 127, 127, 255}, { 127, 255, 255 }, { 255, 127, 255 }, { 255, 255, 127 }, }; size_t c = idx % ARRAY_SIZE(colors); return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha); } Index: ps/trunk/source/renderer/TerrainRenderer.cpp =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26142) +++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26143) @@ -1,604 +1,605 @@ /* 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 "renderer/TerrainRenderer.h" #include "graphics/Camera.h" #include "graphics/Canvas2D.h" #include "graphics/Decal.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/Patch.h" #include "graphics/Model.h" #include "graphics/ShaderManager.h" #include "graphics/TerritoryTexture.h" #include "graphics/TextRenderer.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/DecalRData.h" #include "renderer/PatchRData.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/ShadowMap.h" #include "renderer/SkyManager.h" #include "renderer/VertexArray.h" #include "renderer/WaterManager.h" /** * TerrainRenderer keeps track of which phase it is in, to detect * when Submit, PrepareForRendering etc. are called in the wrong order. */ enum Phase { Phase_Submit, Phase_Render }; /** * Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class. */ struct TerrainRendererInternals { /// Which phase (submitting or rendering patches) are we in right now? Phase phase; /// Patches that were submitted for this frame std::vector visiblePatches[CRenderer::CULL_MAX]; /// Decals that were submitted for this frame std::vector visibleDecals[CRenderer::CULL_MAX]; /// Fancy water shader CShaderTechniquePtr fancyWaterTech; CSimulation2* simulation; }; /////////////////////////////////////////////////////////////////// // Construction/Destruction TerrainRenderer::TerrainRenderer() { m = new TerrainRendererInternals(); m->phase = Phase_Submit; } TerrainRenderer::~TerrainRenderer() { delete m; } void TerrainRenderer::SetSimulation(CSimulation2* simulation) { m->simulation = simulation; } /////////////////////////////////////////////////////////////////// // Submit a patch for rendering void TerrainRenderer::Submit(int cullGroup, CPatch* patch) { ENSURE(m->phase == Phase_Submit); CPatchRData* data = (CPatchRData*)patch->GetRenderData(); if (data == 0) { // no renderdata for patch, create it now data = new CPatchRData(patch, m->simulation); patch->SetRenderData(data); } data->Update(m->simulation); m->visiblePatches[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Submit a decal for rendering void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal) { ENSURE(m->phase == Phase_Submit); CDecalRData* data = (CDecalRData*)decal->GetRenderData(); if (data == 0) { // no renderdata for decal, create it now data = new CDecalRData(decal, m->simulation); decal->SetRenderData(data); } data->Update(m->simulation); m->visibleDecals[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Prepare for rendering void TerrainRenderer::PrepareForRendering() { ENSURE(m->phase == Phase_Submit); m->phase = Phase_Render; } /////////////////////////////////////////////////////////////////// // Clear submissions lists void TerrainRenderer::EndFrame() { ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit); for (int i = 0; i < CRenderer::CULL_MAX; ++i) { m->visiblePatches[i].clear(); m->visibleDecals[i].clear(); } m->phase = Phase_Submit; } -void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix, GLuint texture) +void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix, + Renderer::Backend::GL::CTexture* texture) { #if CONFIG2_GLES #warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES UNUSED2(cullGroup); UNUSED2(textureMatrix); UNUSED2(texture); #else ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(0); glDisable(GL_DEPTH_TEST); CShaderTechniquePtr debugOverlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_overlay); debugOverlayTech->BeginPass(); CShaderProgramPtr debugOverlayShader = debugOverlayTech->GetShader(); debugOverlayShader->Bind(); debugOverlayShader->BindTexture(str_baseTex, texture); debugOverlayShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); debugOverlayShader->Uniform(str_textureTransform, textureMatrix); CPatchRData::RenderStreams(visiblePatches, debugOverlayShader, STREAM_POS | STREAM_POSTOUV0); glEnable(GL_DEPTH_TEST); // To make the overlay visible over water, render an additional map-sized // water-height patch. CBoundingBoxAligned waterBounds; for (CPatchRData* data : visiblePatches) waterBounds += data->GetWaterBounds(); if (!waterBounds.IsEmpty()) { // Add a delta to avoid z-fighting. const float height = g_Renderer.GetWaterManager()->m_WaterHeight + 0.05f; const float waterPos[] = { waterBounds[0].X, height, waterBounds[0].Z, waterBounds[1].X, height, waterBounds[0].Z, waterBounds[0].X, height, waterBounds[1].Z, waterBounds[1].X, height, waterBounds[1].Z }; const GLsizei stride = sizeof(float) * 3; debugOverlayShader->VertexPointer(3, GL_FLOAT, stride, waterPos); debugOverlayShader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, waterPos); debugOverlayShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } debugOverlayShader->Unbind(); debugOverlayTech->EndPass(); glDepthMask(1); glDisable(GL_BLEND); #endif } /////////////////////////////////////////////////////////////////// /** * Set up all the uniforms for a shader pass. */ void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow) { shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); shader->Uniform(str_cameraPos, g_Renderer.GetViewCamera().GetOrientation().GetTranslation()); const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); if (shadow) shadow->BindTo(shader); CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); shader->BindTexture(str_losTex, los.GetTextureSmooth()); shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); shader->Uniform(str_ambient, lightEnv.m_AmbientColor); shader->Uniform(str_sunColor, lightEnv.m_SunColor); shader->Uniform(str_sunDir, lightEnv.GetSunDir()); shader->Uniform(str_fogColor, lightEnv.m_FogColor); shader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f); } void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; std::vector& visibleDecals = m->visibleDecals[cullGroup]; if (visiblePatches.empty() && visibleDecals.empty()) return; // render the solid black sides of the map first CShaderTechniquePtr techSolid = g_Renderer.GetShaderManager().LoadEffect(str_solid); techSolid->BeginPass(); CShaderProgramPtr shaderSolid = techSolid->GetShader(); shaderSolid->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); shaderSolid->Uniform(str_color, 0.0f, 0.0f, 0.0f, 1.0f); CPatchRData::RenderSides(visiblePatches, shaderSolid); techSolid->EndPass(); CPatchRData::RenderBases(visiblePatches, context, shadow); // no need to write to the depth buffer a second time glDepthMask(0); // render blend passes for each patch CPatchRData::RenderBlends(visiblePatches, context, shadow); CDecalRData::RenderDecals(visibleDecals, context, shadow); // restore OpenGL state g_Renderer.BindTexture(1, 0); g_Renderer.BindTexture(2, 0); g_Renderer.BindTexture(3, 0); glDepthMask(1); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_BLEND); } /////////////////////////////////////////////////////////////////// // Render un-textured patches as polygons void TerrainRenderer::RenderPatches(int cullGroup, const CColor& color) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; #if CONFIG2_GLES #warning TODO: implement TerrainRenderer::RenderPatches for GLES #else CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_dummy); dummyTech->BeginPass(); CShaderProgramPtr dummyShader = dummyTech->GetShader(); dummyShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); dummyShader->Uniform(str_color, color); CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS); dummyTech->EndPass(); #endif } /////////////////////////////////////////////////////////////////// // Render outlines of submitted patches as lines void TerrainRenderer::RenderOutlines(int cullGroup) { ENSURE(m->phase == Phase_Render); std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; for (size_t i = 0; i < visiblePatches.size(); ++i) visiblePatches[i]->RenderOutline(); } /////////////////////////////////////////////////////////////////// // Scissor rectangle of water patches CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CCamera& camera) { CBoundingBoxAligned scissor; for (const CPatchRData* data : m->visiblePatches[cullGroup]) { const CBoundingBoxAligned& waterBounds = data->GetWaterBounds(); if (waterBounds.IsEmpty()) continue; const CBoundingBoxAligned waterBoundsInViewPort = camera.GetBoundsInViewPort(waterBounds); if (!waterBoundsInViewPort.IsEmpty()) scissor += waterBoundsInViewPort; } return CBoundingBoxAligned( CVector3D(Clamp(scissor[0].X, -1.0f, 1.0f), Clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f), CVector3D(Clamp(scissor[1].X, -1.0f, 1.0f), Clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f)); } // Render fancy water bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { PROFILE3_GPU("fancy water"); OGL_SCOPED_DEBUG_GROUP("Render Fancy Water"); WaterManager* waterManager = g_Renderer.GetWaterManager(); CShaderDefines defines = context; // If we're using fancy water, make sure its shader is loaded if (!m->fancyWaterTech || waterManager->m_NeedsReloading) { if (waterManager->m_WaterRealDepth) defines.Add(str_USE_REAL_DEPTH, str_1); if (waterManager->m_WaterFancyEffects) defines.Add(str_USE_FANCY_EFFECTS, str_1); if (waterManager->m_WaterRefraction) defines.Add(str_USE_REFRACTION, str_1); if (waterManager->m_WaterReflection) defines.Add(str_USE_REFLECTION, str_1); m->fancyWaterTech = g_Renderer.GetShaderManager().LoadEffect(str_water_high, defines); if (!m->fancyWaterTech) { LOGERROR("Failed to load water shader. Falling back to a simple water.\n"); waterManager->m_RenderWater = false; return false; } waterManager->m_NeedsReloading = false; } CLOSTexture& losTexture = g_Renderer.GetScene().GetLOSTexture(); // Calculating the advanced informations about Foam and all if the quality calls for it. /*if (WaterMgr->m_NeedInfoUpdate && (WaterMgr->m_WaterFoam || WaterMgr->m_WaterCoastalWaves)) { WaterMgr->m_NeedInfoUpdate = false; WaterMgr->CreateSuperfancyInfo(); }*/ const double time = waterManager->m_WaterTexTimer; const float repeatPeriod = waterManager->m_RepeatPeriod; // Render normals and foam to a framebuffer if we're in fancy effects if (waterManager->m_WaterFancyEffects) { // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, waterManager->m_FancyEffectsFBO); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); // Overwrite waves that would be behind the ground. CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_solid); dummyTech->BeginPass(); CShaderProgramPtr dummyShader = dummyTech->GetShader(); dummyShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); dummyShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.0f); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWater(dummyShader, true, true); } dummyTech->EndPass(); glEnable(GL_CULL_FACE); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); m->fancyWaterTech->BeginPass(); CShaderProgramPtr fancyWaterShader = m->fancyWaterTech->GetShader(); const CCamera& camera = g_Renderer.GetViewCamera(); const double period = 8.0; fancyWaterShader->BindTexture(str_normalMap, waterManager->m_NormalMap[waterManager->GetCurrentTextureIndex(period)]); fancyWaterShader->BindTexture(str_normalMap2, waterManager->m_NormalMap[waterManager->GetNextTextureIndex(period)]); if (waterManager->m_WaterFancyEffects) { - fancyWaterShader->BindTexture(str_waterEffectsTex, waterManager->m_FancyTexture->GetHandle()); + fancyWaterShader->BindTexture(str_waterEffectsTex, waterManager->m_FancyTexture.get()); } if (waterManager->m_WaterRefraction && waterManager->m_WaterRealDepth) { - fancyWaterShader->BindTexture(str_depthTex, waterManager->m_RefrFboDepthTexture->GetHandle()); + fancyWaterShader->BindTexture(str_depthTex, waterManager->m_RefrFboDepthTexture.get()); fancyWaterShader->Uniform(str_projInvTransform, waterManager->m_RefractionProjInvMatrix); fancyWaterShader->Uniform(str_viewInvTransform, waterManager->m_RefractionViewInvMatrix); } if (waterManager->m_WaterRefraction) - fancyWaterShader->BindTexture(str_refractionMap, waterManager->m_RefractionTexture->GetHandle()); + fancyWaterShader->BindTexture(str_refractionMap, waterManager->m_RefractionTexture.get()); if (waterManager->m_WaterReflection) - fancyWaterShader->BindTexture(str_reflectionMap, waterManager->m_ReflectionTexture->GetHandle()); + fancyWaterShader->BindTexture(str_reflectionMap, waterManager->m_ReflectionTexture.get()); fancyWaterShader->BindTexture(str_losTex, losTexture.GetTextureSmooth()); const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); fancyWaterShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); fancyWaterShader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube()); // TODO: check that this rotates in the right direction. CMatrix3D skyBoxRotation; skyBoxRotation.SetIdentity(); skyBoxRotation.RotateY(M_PI + lightEnv.GetRotation()); fancyWaterShader->Uniform(str_skyBoxRot, skyBoxRotation); if (waterManager->m_WaterRefraction) fancyWaterShader->Uniform(str_refractionMatrix, waterManager->m_RefractionMatrix); if (waterManager->m_WaterReflection) fancyWaterShader->Uniform(str_reflectionMatrix, waterManager->m_ReflectionMatrix); fancyWaterShader->Uniform(str_ambient, lightEnv.m_AmbientColor); fancyWaterShader->Uniform(str_sunDir, lightEnv.GetSunDir()); fancyWaterShader->Uniform(str_sunColor, lightEnv.m_SunColor); fancyWaterShader->Uniform(str_color, waterManager->m_WaterColor); fancyWaterShader->Uniform(str_tint, waterManager->m_WaterTint); fancyWaterShader->Uniform(str_waviness, waterManager->m_Waviness); fancyWaterShader->Uniform(str_murkiness, waterManager->m_Murkiness); fancyWaterShader->Uniform(str_windAngle, waterManager->m_WindAngle); fancyWaterShader->Uniform(str_repeatScale, 1.0f / repeatPeriod); fancyWaterShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f); fancyWaterShader->Uniform(str_cameraPos, camera.GetOrientation().GetTranslation()); fancyWaterShader->Uniform(str_fogColor, lightEnv.m_FogColor); fancyWaterShader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f); fancyWaterShader->Uniform(str_time, (float)time); fancyWaterShader->Uniform(str_screenSize, (float)g_Renderer.GetWidth(), (float)g_Renderer.GetHeight(), 0.0f, 0.0f); if (waterManager->m_WaterType == L"clap") { fancyWaterShader->Uniform(str_waveParams1, 30.0f,1.5f,20.0f,0.03f); fancyWaterShader->Uniform(str_waveParams2, 0.5f,0.0f,0.0f,0.0f); } else if (waterManager->m_WaterType == L"lake") { fancyWaterShader->Uniform(str_waveParams1, 8.5f,1.5f,15.0f,0.03f); fancyWaterShader->Uniform(str_waveParams2, 0.2f,0.0f,0.0f,0.07f); } else { fancyWaterShader->Uniform(str_waveParams1, 15.0f,0.8f,10.0f,0.1f); fancyWaterShader->Uniform(str_waveParams2, 0.3f,0.0f,0.1f,0.3f); } if (shadow) shadow->BindTo(fancyWaterShader); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWater(fancyWaterShader); } m->fancyWaterTech->EndPass(); glDepthFunc(GL_LEQUAL); glDisable(GL_BLEND); return true; } void TerrainRenderer::RenderSimpleWater(int cullGroup) { #if CONFIG2_GLES UNUSED2(cullGroup); #else PROFILE3_GPU("simple water"); OGL_SCOPED_DEBUG_GROUP("Render Simple Water"); WaterManager* WaterMgr = g_Renderer.GetWaterManager(); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); double time = WaterMgr->m_WaterTexTimer; double period = 1.6f; int curTex = (int)(time*60/period) % 60; CShaderTechniquePtr waterSimpleTech = g_Renderer.GetShaderManager().LoadEffect(str_water_simple); waterSimpleTech->BeginPass(); CShaderProgramPtr waterSimpleShader = waterSimpleTech->GetShader(); waterSimpleShader->Bind(); waterSimpleShader->BindTexture(str_baseTex, WaterMgr->m_WaterTexture[curTex]); waterSimpleShader->BindTexture(str_losTex, losTexture.GetTextureSmooth()); waterSimpleShader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); waterSimpleShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f); waterSimpleShader->Uniform(str_time, static_cast(time)); waterSimpleShader->Uniform(str_color, WaterMgr->m_WaterColor); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) { CPatchRData* data = visiblePatches[i]; data->RenderWater(waterSimpleShader, false, true); } waterSimpleShader->Unbind(); g_Renderer.BindTexture(1, 0); glActiveTextureARB(GL_TEXTURE0_ARB); waterSimpleTech->EndPass(); #endif } /////////////////////////////////////////////////////////////////// // Render water that is part of the terrain void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { WaterManager* WaterMgr = g_Renderer.GetWaterManager(); WaterMgr->UpdateQuality(); if (!WaterMgr->WillRenderFancyWater()) RenderSimpleWater(cullGroup); else RenderFancyWater(context, cullGroup, shadow); } void TerrainRenderer::RenderPriorities(int cullGroup) { PROFILE("priorities"); ENSURE(m->phase == Phase_Render); CCanvas2D canvas; CTextRenderer textRenderer; textRenderer.SetCurrentFont(CStrIntern("mono-stroke-10")); textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 1.0f)); std::vector& visiblePatches = m->visiblePatches[cullGroup]; for (size_t i = 0; i < visiblePatches.size(); ++i) visiblePatches[i]->RenderPriorities(textRenderer); canvas.DrawText(textRenderer); } Index: ps/trunk/source/renderer/TerrainRenderer.h =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.h (revision 26142) +++ ps/trunk/source/renderer/TerrainRenderer.h (revision 26143) @@ -1,166 +1,167 @@ /* 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 . */ /* * Terrain rendering (everything related to patches and water) is * encapsulated in TerrainRenderer */ #ifndef INCLUDED_TERRAINRENDERER #define INCLUDED_TERRAINRENDERER #include "graphics/Color.h" #include "maths/BoundingBoxAligned.h" +#include "renderer/backend/gl/Texture.h" class CCamera; class CMatrix3D; class CModelDecal; class CPatch; class CShaderDefines; class CSimulation2; class ShadowMap; struct TerrainRendererInternals; /** * Class TerrainRenderer: Render everything related to the terrain, * especially patches and water. */ class TerrainRenderer { friend class CPatchRData; friend class CDecalRData; public: TerrainRenderer(); ~TerrainRenderer(); /** * Set the simulation context for this frame. * Call at start of frame, before any other Submits. */ void SetSimulation(CSimulation2* simulation); /** * Submit: Add a patch for rendering in this frame. * * preconditions : PrepareForRendering must not have been called * for this frame yet. * The patch must not have been submitted in this frame yet (i.e. you * can only submit a frame once). * * @param patch the patch */ void Submit(int cullGroup, CPatch* patch); /** * Submit: Add a terrain decal for rendering in this frame. */ void Submit(int cullGroup, CModelDecal* decal); /** * PrepareForRendering: Prepare internal data structures like vertex * buffers for rendering. * * All patches must have been submitted before the call to * PrepareForRendering. * PrepareForRendering must be called before any rendering calls. */ void PrepareForRendering(); /** * EndFrame: Remove all patches from the list of submitted patches. */ void EndFrame(); /** * Render textured terrain (including blends between * different terrain types). * * preconditions : PrepareForRendering must have been called this * frame before calling RenderTerrain. * * @param shadow A prepared shadow map, in case rendering with shadows is enabled. */ void RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * RenderPatches: Render all patches un-textured as polygons. * * preconditions : PrepareForRendering must have been called this * frame before calling RenderPatches. * * @param filtered If true then only render objects that passed CullPatches. * @param color Fill color of the patches. */ void RenderPatches(int cullGroup, const CColor& color = CColor(0.0f, 0.0f, 0.0f, 1.0f)); /** * RenderOutlines: Render the outline of patches as lines. * * preconditions : PrepareForRendering must have been called this * frame before calling RenderOutlines. * * @param filtered If true then only render objects that passed CullPatches. */ void RenderOutlines(int cullGroup); /** * RenderWater: Render water for all patches that have been submitted * this frame. * * preconditions : PrepareForRendering must have been called this * frame before calling RenderWater. */ void RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * Calculate a scissor rectangle for the visible water patches. */ CBoundingBoxAligned ScissorWater(int cullGroup, const CCamera& camera); /** * Render priority text for all submitted patches, for debugging. */ void RenderPriorities(int cullGroup); /** * Render texture unit 0 over the terrain mesh, with UV coords calculated * by the given texture matrix. * Intended for use by TerrainTextureOverlay. */ - void RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix, GLuint texture); + void RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix, Renderer::Backend::GL::CTexture* texture); private: TerrainRendererInternals* m; /** * RenderFancyWater: internal rendering method for fancy water. * Returns false if unable to render with fancy water. */ bool RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * RenderSimpleWater: internal rendering method for water */ void RenderSimpleWater(int cullGroup); static void PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow); }; #endif // INCLUDED_TERRAINRENDERER