Index: ps/trunk/source/renderer/PatchRData.cpp =================================================================== --- ps/trunk/source/renderer/PatchRData.cpp (revision 26214) +++ ps/trunk/source/renderer/PatchRData.cpp (revision 26215) @@ -1,1437 +1,1423 @@ /* Copyright (C) 2022 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 "graphics/TextureManager.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/SceneRenderer.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) + m_Patch(patch), 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; // 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); 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; 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; 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(); 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)); const WaterManager& waterManager = g_Renderer.GetSceneRenderer().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(waterManager.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) +void CPatchRData::RenderWaterSurface(CShaderProgramPtr& shader) { - ASSERT(m_UpdateFlags==0); + ASSERT(m_UpdateFlags == 0); - if (!m_VBWater && !m_VBWaterShore) + if (!m_VBWater) return; -#if !CONFIG2_GLES - if (g_Renderer.GetSceneRenderer().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); + SWaterVertex* base = reinterpret_cast(m_VBWater->m_Owner->Bind()); - shader->AssertPointersBound(); + // Setup data pointers. + const GLsizei stride = sizeof(SWaterVertex); + shader->VertexPointer(3, GL_FLOAT, stride, &base[m_VBWater->m_Index].m_Position); + 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, static_cast(m_VBWaterIndices->m_Count), + GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_VBWaterIndices->m_Index)); - 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; - g_Renderer.m_Stats.m_DrawCalls++; - g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndices->m_Count / 3; - } + CVertexBuffer::Unbind(); +} - if (m_VBWaterShore && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB && - g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterEffects && - g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterFancyEffects) - { - SWaterVertex *base=(SWaterVertex *)m_VBWaterShore->m_Owner->Bind(); +void CPatchRData::RenderWaterShore(CShaderProgramPtr& shader) +{ + ASSERT(m_UpdateFlags == 0); - 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); + if (!m_VBWaterShore) + return; - shader->AssertPointersBound(); + SWaterVertex* base = reinterpret_cast(m_VBWaterShore->m_Owner->Bind()); - 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)); + const GLsizei stride = sizeof(SWaterVertex); + shader->VertexPointer(3, GL_FLOAT, stride, &base[m_VBWaterShore->m_Index].m_Position); + shader->VertexAttribPointer(str_a_waterInfo, 2, GL_FLOAT, false, stride, &base[m_VBWaterShore->m_Index].m_WaterData); - g_Renderer.m_Stats.m_DrawCalls++; - g_Renderer.m_Stats.m_WaterTris += m_VBWaterIndicesShore->m_Count / 3; - } + shader->AssertPointersBound(); - CVertexBuffer::Unbind(); + u8* indexBase = m_VBWaterIndicesShore->m_Owner->Bind(); + glDrawElements(GL_TRIANGLES, static_cast(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; -#if !CONFIG2_GLES - if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME) - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); -#endif + CVertexBuffer::Unbind(); } Index: ps/trunk/source/renderer/PatchRData.h =================================================================== --- ps/trunk/source/renderer/PatchRData.h (revision 26214) +++ ps/trunk/source/renderer/PatchRData.h (revision 26215) @@ -1,180 +1,181 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 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_PATCHRDATA #define INCLUDED_PATCHRDATA #include "graphics/Patch.h" #include "graphics/RenderableObject.h" #include "graphics/ShaderProgramPtr.h" #include "maths/Vector2D.h" #include "maths/Vector3D.h" #include "renderer/VertexBufferManager.h" #include class CPatch; class CShaderDefines; class CSimulation2; class CTerrainTextureEntry; class CTextRenderer; class ShadowMap; ////////////////////////////////////////////////////////////////////////////////////////////////// // CPatchRData: class encapsulating logic for rendering terrain patches; holds per // patch data, plus some supporting static functions for batching, etc class CPatchRData : public CRenderData { public: CPatchRData(CPatch* patch, CSimulation2* simulation); ~CPatchRData(); void Update(CSimulation2* simulation); void RenderOutline(); void RenderPriorities(CTextRenderer& textRenderer); - void RenderWater(CShaderProgramPtr& shader, bool onlyShore = false, bool fixedPipeline = false); + void RenderWaterSurface(CShaderProgramPtr& shader); + void RenderWaterShore(CShaderProgramPtr& shader); CPatch* GetPatch() { return m_Patch; } const CBoundingBoxAligned& GetWaterBounds() const { return m_WaterBounds; } static void RenderBases( const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow); static void RenderBlends( const std::vector& patches, const CShaderDefines& context, ShadowMap* shadow); static void RenderStreams(const std::vector& patches, const CShaderProgramPtr& shader, int streamflags); static void RenderSides(const std::vector& patches, const CShaderProgramPtr& shader); static void PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow); private: friend struct SBlendStackItem; struct SSplat { SSplat() : m_Texture(0), m_IndexCount(0) {} // texture to apply during splat CTerrainTextureEntry* m_Texture; // offset into the index array for this patch where splat starts size_t m_IndexStart; // number of indices used by splat size_t m_IndexCount; }; struct SBaseVertex { // vertex position CVector3D m_Position; CVector3D m_Normal; // pad to a power of two u8 m_Padding[8]; }; cassert(sizeof(SBaseVertex) == 32); struct SSideVertex { // vertex position CVector3D m_Position; // pad to a power of two u8 m_Padding[4]; }; cassert(sizeof(SSideVertex) == 16); struct SBlendVertex { // vertex position CVector3D m_Position; // vertex uvs for alpha texture float m_AlphaUVs[2]; CVector3D m_Normal; }; cassert(sizeof(SBlendVertex) == 32); // Mixed Fancy/Simple water vertex description data structure struct SWaterVertex { // vertex position CVector3D m_Position; CVector2D m_WaterData; // pad to a power of two u8 m_Padding[12]; }; cassert(sizeof(SWaterVertex) == 32); // build this renderdata object void Build(); void AddBlend(std::vector& blendVertices, std::vector& blendIndices, u16 i, u16 j, u8 shape, CTerrainTextureEntry* texture); void BuildBlends(); void BuildIndices(); void BuildVertices(); void BuildSides(); void BuildSide(std::vector& vertices, CPatchSideFlags side); // owner patch CPatch* m_Patch; // vertex buffer handle for side vertices CVertexBufferManager::Handle m_VBSides; // vertex buffer handle for base vertices CVertexBufferManager::Handle m_VBBase; // vertex buffer handle for base vertex indices CVertexBufferManager::Handle m_VBBaseIndices; // vertex buffer handle for blend vertices CVertexBufferManager::Handle m_VBBlends; // vertex buffer handle for blend vertex indices CVertexBufferManager::Handle m_VBBlendIndices; // list of base splats to apply to this patch std::vector m_Splats; // splats used in blend pass std::vector m_BlendSplats; // boundary of water in this patch CBoundingBoxAligned m_WaterBounds; // Water vertex buffer CVertexBufferManager::Handle m_VBWater; CVertexBufferManager::Handle m_VBWaterShore; // Water indices buffer CVertexBufferManager::Handle m_VBWaterIndices; CVertexBufferManager::Handle m_VBWaterIndicesShore; CSimulation2* m_Simulation; // Build water vertices and indices (vertex buffer and data vector) void BuildWater(); // parameter allowing a varying number of triangles per patch for LOD // MUST be an exact divisor of PATCH_SIZE // compiled const for the moment until/if dynamic water LOD is offered // savings would be mostly beneficial for GPU or simple water static const ssize_t water_cell_size = 1; }; #endif // INCLUDED_PATCHRDATA Index: ps/trunk/source/renderer/TerrainRenderer.cpp =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26214) +++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26215) @@ -1,602 +1,619 @@ /* Copyright (C) 2022 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/SceneRenderer.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[CSceneRenderer::CULL_MAX]; /// Decals that were submitted for this frame std::vector visibleDecals[CSceneRenderer::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 < CSceneRenderer::CULL_MAX; ++i) { m->visiblePatches[i].clear(); m->visibleDecals[i].clear(); } m->phase = Phase_Submit; } 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->BindTexture(str_baseTex, texture); debugOverlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().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.GetSceneRenderer().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); } 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) { CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer(); shader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection()); shader->Uniform(str_cameraPos, sceneRenderer.GetViewCamera().GetOrientation().GetTranslation()); const CLightEnv& lightEnv = sceneRenderer.GetLightEnv(); if (shadow) shadow->BindTo(shader); CLOSTexture& los = sceneRenderer.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.GetSceneRenderer().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 UNUSED2(color); #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.GetSceneRenderer().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"); CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer(); WaterManager& waterManager = sceneRenderer.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 = sceneRenderer.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; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); +#if !CONFIG2_GLES + if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME) + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif + m->fancyWaterTech->BeginPass(); CShaderProgramPtr fancyWaterShader = m->fancyWaterTech->GetShader(); const CCamera& camera = g_Renderer.GetSceneRenderer().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.get()); } if (waterManager.m_WaterRefraction && waterManager.m_WaterRealDepth) { 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.get()); if (waterManager.m_WaterReflection) fancyWaterShader->BindTexture(str_reflectionMap, waterManager.m_ReflectionTexture.get()); fancyWaterShader->BindTexture(str_losTex, losTexture.GetTextureSmooth()); const CLightEnv& lightEnv = sceneRenderer.GetLightEnv(); fancyWaterShader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection()); fancyWaterShader->BindTexture(str_skyCube, sceneRenderer.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); + data->RenderWaterSurface(fancyWaterShader); + data->RenderWaterShore(fancyWaterShader); } m->fancyWaterTech->EndPass(); +#if !CONFIG2_GLES + if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + 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"); const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager(); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); + if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME) + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + const double time = waterManager.m_WaterTexTimer; CShaderTechniquePtr waterSimpleTech = g_Renderer.GetShaderManager().LoadEffect(str_water_simple); waterSimpleTech->BeginPass(); CShaderProgramPtr waterSimpleShader = waterSimpleTech->GetShader(); waterSimpleShader->BindTexture(str_baseTex, waterManager.m_WaterTexture[waterManager.GetCurrentTextureIndex(1.6)]); waterSimpleShader->BindTexture(str_losTex, losTexture.GetTextureSmooth()); waterSimpleShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().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, waterManager.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); + data->RenderWaterSurface(waterSimpleShader); } g_Renderer.BindTexture(1, 0); glActiveTextureARB(GL_TEXTURE0_ARB); waterSimpleTech->EndPass(); + + if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif } /////////////////////////////////////////////////////////////////// // Render water that is part of the terrain void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager(); if (!waterManager.WillRenderFancyWater()) RenderSimpleWater(cullGroup); else RenderFancyWater(context, cullGroup, shadow); } void TerrainRenderer::RenderWaterFoamOccluders(int cullGroup) { CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer(); const WaterManager& waterManager = sceneRenderer.GetWaterManager(); if (!waterManager.WillRenderFancyWater()) return; // Render normals and foam to a framebuffer if we're using fancy effects. 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, sceneRenderer.GetViewCamera().GetViewProjection()); dummyShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.0f); for (CPatchRData* data : m->visiblePatches[cullGroup]) - data->RenderWater(dummyShader, true, true); + data->RenderWaterShore(dummyShader); dummyTech->EndPass(); glEnable(GL_CULL_FACE); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } 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/WaterManager.h =================================================================== --- ps/trunk/source/renderer/WaterManager.h (revision 26214) +++ ps/trunk/source/renderer/WaterManager.h (revision 26215) @@ -1,204 +1,204 @@ /* Copyright (C) 2022 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 . */ /* * Water settings (speed, height) and texture management */ #ifndef INCLUDED_WATERMANAGER #define INCLUDED_WATERMANAGER #include "graphics/Color.h" #include "graphics/Texture.h" #include "maths/Matrix3D.h" #include "maths/Vector2D.h" #include "renderer/backend/gl/Texture.h" #include "renderer/VertexBufferManager.h" #include #include class CFrustum; struct WaveObject; /** * Class WaterManager: Maintain rendering-related water settings and textures * Anything that affects gameplay should go in CcmpWaterManager.cpp and passed to this (possibly as copy). */ class WaterManager { public: CTexturePtr m_WaterTexture[60]; CTexturePtr m_NormalMap[60]; // How strong the waves are at point X. % of waviness. std::unique_ptr m_WindStrength; // How far from the shore a point is. Manhattan. std::unique_ptr m_DistanceHeightmap; // Waves vertex buffers // TODO: measure storing value instead of pointer. std::vector> m_ShoreWaves; // Waves indices buffer. Only one since All Wave Objects have the same. CVertexBufferManager::Handle m_ShoreWavesVBIndices; size_t m_MapSize; ssize_t m_TexSize; CTexturePtr m_WaveTex; CTexturePtr m_FoamTex; std::unique_ptr m_FancyTexture; std::unique_ptr m_FancyTextureDepth; std::unique_ptr m_ReflFboDepthTexture; std::unique_ptr m_RefrFboDepthTexture; // used to know what to update when updating parts of the terrain only. u32 m_updatei0; u32 m_updatej0; u32 m_updatei1; u32 m_updatej1; bool m_RenderWater; - // If disabled, force the use of the fixed function for rendering. + // If disabled, force the use of the simple water shader for rendering. bool m_WaterEffects; // Those variables register the current quality level. If there is a change, I have to recompile the shader. // Use real depth or use the fake precomputed one. bool m_WaterRealDepth; // Use fancy shore effects and show trails behind ships bool m_WaterFancyEffects; // Use refractions instead of simply making the water more or less transparent. bool m_WaterRefraction; // Use complete reflections instead of showing merely the sky. bool m_WaterReflection; bool m_NeedsReloading; // requires also recreating the super fancy information. bool m_NeedInfoUpdate; float m_WaterHeight; double m_WaterTexTimer; float m_RepeatPeriod; // Reflection and refraction textures for fancy water std::unique_ptr m_ReflectionTexture; std::unique_ptr m_RefractionTexture; size_t m_RefTextureSize; // framebuffer objects GLuint m_RefractionFbo; GLuint m_ReflectionFbo; GLuint m_FancyEffectsFBO; // Model-view-projection matrices for reflected & refracted cameras // (used to let the vertex shader do projective texturing) CMatrix3D m_ReflectionMatrix; CMatrix3D m_RefractionMatrix; CMatrix3D m_RefractionProjInvMatrix; CMatrix3D m_RefractionViewInvMatrix; // Water parameters std::wstring m_WaterType; // Which texture to use. CColor m_WaterColor; // Color of the water without refractions. This is what you're seeing when the water's deep or murkiness high. CColor m_WaterTint; // Tint of refraction in the water. float m_Waviness; // How big the waves are. float m_Murkiness; // How murky the water is. float m_WindAngle; // In which direction the water waves go. public: WaterManager(); ~WaterManager(); /** * LoadWaterTextures: Load water textures from within the * progressive load framework. * * @return 0 if loading has completed, a value from 1 to 100 (in percent of completion) * if more textures need to be loaded and a negative error value on failure. */ int LoadWaterTextures(); /** * Resize: Updates the fancy water textures so that water will render correctly * with fancy water. */ void Resize(); /** * ReloadWaterNormalTextures: Reload the normal textures so that changing * water type in Atlas will actually do the right thing. */ void ReloadWaterNormalTextures(); /** * UnloadWaterTextures: Free any loaded water textures and reset the internal state * so that another call to LoadWaterTextures will begin progressive loading. */ void UnloadWaterTextures(); /** * RecomputeWaterData: calculates all derived data from the waterheight */ void RecomputeWaterData(); /** * RecomputeWindStrength: calculates the intensity of waves */ void RecomputeWindStrength(); /** * RecomputeDistanceHeightmap: recalculates (or calculates) the distance heightmap. */ void RecomputeDistanceHeightmap(); /** * CreateWaveMeshes: Creates the waves objects (and meshes). */ void CreateWaveMeshes(); /** * Updates the map size. Will trigger a complete recalculation of fancy water information the next turn. */ void SetMapSize(size_t size); /** * Updates the settings to the one from the renderer, and sets m_NeedsReloading. */ void UpdateQuality(); /** * Returns true if fancy water shaders will be used (i.e. the hardware is capable * and it hasn't been configured off) */ bool WillRenderFancyWater() const; void RenderWaves(const CFrustum& frustrum); /** * Returns an index of the current texture that should be used for rendering * water. */ size_t GetCurrentTextureIndex(const double& period) const; size_t GetNextTextureIndex(const double& period) const; }; #endif // INCLUDED_WATERMANAGER