Index: ps/trunk/source/graphics/FontManager.cpp =================================================================== --- ps/trunk/source/graphics/FontManager.cpp (revision 26587) +++ ps/trunk/source/graphics/FontManager.cpp (revision 26588) @@ -1,148 +1,148 @@ /* 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 "FontManager.h" #include "graphics/Font.h" #include "graphics/TextureManager.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "renderer/Renderer.h" #include std::shared_ptr CFontManager::LoadFont(CStrIntern fontName) { FontsMap::iterator it = m_Fonts.find(fontName); if (it != m_Fonts.end()) return it->second; std::shared_ptr font(new CFont()); if (!ReadFont(font.get(), fontName)) { // Fall back to default font (unless this is the default font) if (fontName == str_sans_10) font.reset(); else font = LoadFont(str_sans_10); } m_Fonts[fontName] = font; return font; } bool CFontManager::ReadFont(CFont* font, CStrIntern fontName) { const VfsPath path(L"fonts/"); // Read font definition file into a stringstream std::shared_ptr buffer; size_t size; const VfsPath fntName(fontName.string() + ".fnt"); if (g_VFS->LoadFile(path / fntName, buffer, size) < 0) { LOGERROR("Failed to open font file %s", (path / fntName).string8()); return false; } std::istringstream fontStream( std::string(reinterpret_cast(buffer.get()), size)); int version; fontStream >> version; // Make sure this is from a recent version of the font builder. if (version != 101) { LOGERROR("Font %s has invalid version", fontName.c_str()); return false; } int textureWidth, textureHeight; fontStream >> textureWidth >> textureHeight; std::string format; fontStream >> format; if (format == "rgba") font->m_HasRGB = true; else if (format == "a") font->m_HasRGB = false; else { LOGWARNING("Invalid .fnt format string"); return false; } int mumberOfGlyphs; fontStream >> mumberOfGlyphs; fontStream >> font->m_LineSpacing; fontStream >> font->m_Height; font->m_BoundsX0 = std::numeric_limits::max(); font->m_BoundsY0 = std::numeric_limits::max(); font->m_BoundsX1 = -std::numeric_limits::max(); font->m_BoundsY1 = -std::numeric_limits::max(); for (int i = 0; i < mumberOfGlyphs; ++i) { int codepoint, textureX, textureY, width, height, offsetX, offsetY, advance; fontStream >> codepoint >> textureX >> textureY >> width >> height >> offsetX >> offsetY >> advance; if (codepoint < 0 || codepoint > 0xFFFF) { LOGWARNING("Font %s has invalid codepoint 0x%x", fontName.c_str(), codepoint); continue; } const float u = static_cast(textureX) / textureWidth; const float v = static_cast(textureY) / textureHeight; const float w = static_cast(width) / textureWidth; const float h = static_cast(height) / textureHeight; CFont::GlyphData g = { u, -v, u + w, -v + h, static_cast(offsetX), static_cast(-offsetY), static_cast(offsetX + width), static_cast(-offsetY + height), static_cast(advance) }; font->m_Glyphs.set(static_cast(codepoint), g); font->m_BoundsX0 = std::min(font->m_BoundsX0, static_cast(g.x0)); font->m_BoundsY0 = std::min(font->m_BoundsY0, static_cast(g.y0)); font->m_BoundsX1 = std::max(font->m_BoundsX1, static_cast(g.x1)); font->m_BoundsY1 = std::max(font->m_BoundsY1, static_cast(g.y1)); } // Ensure the height has been found (which should always happen if the font includes an 'I'). ENSURE(font->m_Height); // Load glyph texture const VfsPath imageName(fontName.string() + ".png"); CTextureProperties textureProps(path / imageName, - font->m_HasRGB ? Renderer::Backend::Format::R8G8B8A8 : Renderer::Backend::Format::A8); + font->m_HasRGB ? Renderer::Backend::Format::R8G8B8A8_UNORM : Renderer::Backend::Format::A8_UNORM); textureProps.SetIgnoreQuality(true); font->m_Texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); return true; } Index: ps/trunk/source/graphics/LOSTexture.cpp =================================================================== --- ps/trunk/source/graphics/LOSTexture.cpp (revision 26587) +++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26588) @@ -1,428 +1,428 @@ /* 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 "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/backend/gl/Device.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 texture uploading. // 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) { if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) CreateShader(); } CLOSTexture::~CLOSTexture() { m_SmoothFramebuffers[0].reset(); m_SmoothFramebuffers[1].reset(); if (m_Texture) DeleteTexture(); } // Create the LOS texture engine. Should be ran only once. bool CLOSTexture::CreateShader() { m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp); CShaderProgramPtr shader = m_SmoothTech->GetShader(); m_ShaderInitialized = m_SmoothTech && shader; if (!m_ShaderInitialized) { LOGERROR("Failed to load SmoothLOS shader, disabling."); g_RenderingOptions.SetSmoothLOS(false); return false; } return true; } void CLOSTexture::DeleteTexture() { m_Texture.reset(); m_SmoothTextures[0].reset(); m_SmoothTextures[1].reset(); } void CLOSTexture::MakeDirty() { m_Dirty = true; } Renderer::Backend::GL::CTexture* CLOSTexture::GetTextureSmooth() { if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS()) return GetTexture(); else return m_SmoothTextures[m_WhichTexture].get(); } void CLOSTexture::InterpolateLOS(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS(); if (!skipSmoothLOS && !m_ShaderInitialized) { if (!CreateShader()) return; // RecomputeTexture will not cause the ConstructTexture to run. // Force the textures to be created. DeleteTexture(); ConstructTexture(deviceCommandContext); m_Dirty = true; } if (m_Dirty) { RecomputeTexture(deviceCommandContext); m_Dirty = false; } if (skipSmoothLOS) return; GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture"); deviceCommandContext->SetFramebuffer(m_SmoothFramebuffers[m_WhichTexture].get()); m_SmoothTech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState( m_SmoothTech->GetGraphicsPipelineStateDesc()); const CShaderProgramPtr& shader = m_SmoothTech->GetShader(); shader->BindTexture(str_losTex1, m_Texture.get()); shader->BindTexture(str_losTex2, m_SmoothTextures[m_WhichTexture].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, static_cast(m_Texture->GetWidth()), static_cast(m_Texture->GetHeight()) }; 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(); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); m_SmoothTech->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); m_WhichTexture = 1u - m_WhichTexture; } Renderer::Backend::GL::CTexture* CLOSTexture::GetTexture() { ENSURE(!m_Dirty); 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(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY); if (!cmpRangeManager) return; m_MapSize = cmpRangeManager->GetVerticesPerSide(); const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment)); Renderer::Backend::GL::CDevice* backendDevice = deviceCommandContext->GetDevice(); const Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); m_Texture = backendDevice->CreateTexture2D("LOSTexture", - Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc); + Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with SoD color, for the areas we don't // overwrite with uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize); memset(texData.get(), 0x00, textureSize * textureSize); if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS()) { m_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0", - Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc); + Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1", - Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc); + Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc); m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer0", m_SmoothTextures[0].get(), nullptr); m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer1", m_SmoothTextures[1].get(), nullptr); if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1]) { LOGERROR("Failed to create LOS framebuffers"); g_RenderingOptions.SetSmoothLOS(false); } deviceCommandContext->UploadTexture( - m_SmoothTextures[0].get(), Renderer::Backend::Format::A8, + m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, texData.get(), textureSize * textureSize); deviceCommandContext->UploadTexture( - m_SmoothTextures[1].get(), Renderer::Backend::Format::A8, + m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, texData.get(), textureSize * textureSize); } deviceCommandContext->UploadTexture( - m_Texture.get(), Renderer::Backend::Format::A8, + m_Texture.get(), Renderer::Backend::Format::A8_UNORM, texData.get(), textureSize * textureSize); texData.reset(); { // 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) / static_cast(textureSize * (m_MapSize-1) * LOS_TILE_SIZE); float t = 0.5f / 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)textureSize; m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CLOSTexture::RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { // 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(deviceCommandContext); recreated = true; } PROFILE("recompute LOS texture"); size_t pitch; const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch); ENSURE(pitch * m_MapSize <= dataSize); std::unique_ptr losData = std::make_unique(dataSize); 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) { deviceCommandContext->UploadTextureRegion( - m_SmoothTextures[0].get(), Renderer::Backend::Format::A8, losData.get(), + m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, losData.get(), pitch * m_MapSize, 0, 0, pitch, m_MapSize); deviceCommandContext->UploadTextureRegion( - m_SmoothTextures[1].get(), Renderer::Backend::Format::A8, losData.get(), + m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, losData.get(), pitch * m_MapSize, 0, 0, pitch, m_MapSize); } deviceCommandContext->UploadTextureRegion( - m_Texture.get(), Renderer::Backend::Format::A8, losData.get(), + m_Texture.get(), Renderer::Backend::Format::A8_UNORM, losData.get(), pitch * m_MapSize, 0, 0, pitch, m_MapSize); } 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/MiniMapTexture.cpp =================================================================== --- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26587) +++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26588) @@ -1,566 +1,566 @@ /* 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 "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 "graphics/TextureManager.h" #include "lib/bits.h" #include "lib/timer.h" #include "maths/Vector2D.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/backend/gl/Device.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.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 64K / 4 for now, which is more than enough. // 4 is the number of vertices per entity. // TODO: we should be cleverer about drawing them to reduce clutter, // f.e. use instancing. const size_t MAX_ENTITIES_DRAWN = 65536 / 4; 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( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const 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(); deviceCommandContext->Draw(0, 6); } struct MinimapUnitVertex { // This struct is copyable for convenience and because to move is to copy for primitives. u8 r, g, b, a; CVector2D position; }; // Adds a vertex to the passed VertexArray inline void AddEntity(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos, const float entityRadius) { const CVector2D offsets[4] = { {-entityRadius, 0.0f}, {0.0f, -entityRadius}, {entityRadius, 0.0f}, {0.0f, entityRadius} }; for (const CVector2D& offset : offsets) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.position.X + offset.X; (*attrPos)[1] = v.position.Y + offset.Y; ++attrPos; } } } // anonymous namespace CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation) : m_Simulation(simulation), m_IndexArray(false), m_VertexArray(Renderer::Backend::GL::CBuffer::Type::VERTEX, true) { // 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.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4); m_VertexArray.Layout(); m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i) *index++ = 0; m_IndexArray.Upload(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++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.GetSceneRenderer().GetWaterManager().m_WaterHeight) { m_TerrainTextureDirty = true; m_FinalTextureDirty = true; } } void CMiniMapTexture::Render(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain) return; if (!m_TerrainTexture) CreateTextures(deviceCommandContext, terrain); if (m_TerrainTextureDirty) RebuildTerrainTexture(deviceCommandContext, terrain); RenderFinalTexture(deviceCommandContext); } void CMiniMapTexture::CreateTextures( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CTerrain* terrain) { DestroyTextures(); m_MapSize = terrain->GetVerticesPerSide(); const size_t textureSize = 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); Renderer::Backend::GL::CDevice* backendDevice = deviceCommandContext->GetDevice(); // Create terrain texture m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture", - Renderer::Backend::Format::R8G8B8A8, textureSize, textureSize, defaultSamplerDesc); + Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, defaultSamplerDesc); // Initialise texture with solid black, for the areas we don't // overwrite with uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize); for (size_t i = 0; i < textureSize * textureSize; ++i) texData[i] = 0xFF000000; deviceCommandContext->UploadTexture( - m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8, + m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, texData.get(), textureSize * textureSize * 4); texData.reset(); m_TerrainData = std::make_unique((m_MapSize - 1) * (m_MapSize - 1)); m_FinalTexture = backendDevice->CreateTexture2D("MiniMapFinalTexture", - Renderer::Backend::Format::R8G8B8A8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc); + Renderer::Backend::Format::R8G8B8A8_UNORM, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc); m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer("MiniMapFinalFramebuffer", m_FinalTexture.get(), nullptr); ENSURE(m_FinalTextureFramebuffer); } void CMiniMapTexture::DestroyTextures() { m_TerrainTexture.reset(); m_FinalTexture.reset(); m_TerrainData.reset(); } void CMiniMapTexture::RebuildTerrainTexture( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, 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.GetSceneRenderer().GetWaterManager().m_WaterHeight; m_TerrainTextureDirty = false; for (u32 j = 0; j < height; ++j) { u32* dataPtr = m_TerrainData.get() + ((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 deviceCommandContext->UploadTextureRegion( - m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8, + m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, m_TerrainData.get(), width * height * 4, 0, 0, width, height); } void CMiniMapTexture::RenderFinalTexture( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { // 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; GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture"); deviceCommandContext->SetFramebuffer(m_FinalTextureFramebuffer.get()); 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 = m_TerrainTexture ? static_cast(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f; CShaderProgramPtr shader; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc(); tech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); shader = tech->GetShader(); if (m_TerrainTexture) 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(deviceCommandContext, shader); pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.blendState.colorWriteMask = Renderer::Backend::ColorWriteMask::RED | Renderer::Backend::ColorWriteMask::GREEN | Renderer::Backend::ColorWriteMask::BLUE; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); // Draw territory boundaries CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, territoryTexture.GetMinimapTextureMatrix()); DrawTexture(deviceCommandContext, shader); pipelineStateDesc.blendState.enabled = false; pipelineStateDesc.blendState.colorWriteMask = Renderer::Backend::ColorWriteMask::ALPHA; deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); shader->BindTexture(str_baseTex, losTexture.GetTexture()); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, losTexture.GetMinimapTextureMatrix()); DrawTexture(deviceCommandContext, shader); tech->EndPass(); CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines); tech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); shader = tech->GetShader(); shader->Uniform(str_transform, baseTransform); 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); // We might scale entities properly in the vertex shader but it requires // additional space in the vertex buffer. So we assume that we don't need // to change an entity size so often. const float entityRadius = static_cast(m_MapSize) / 128.0f * 6.0f; 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.position.X = posX.ToFloat(); v.position.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 { AddEntity(v, attrColor, attrPos, entityRadius); ++m_EntitiesDrawn; } } } } // Add the pinged vertices at the end, so they are drawn on top for (const MinimapUnitVertex& vertex : pingingVertices) { AddEntity(vertex, attrColor, attrPos, entityRadius); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); VertexArrayIterator index = m_IndexArray.GetIterator(); for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex) { index[entityIndex * 6 + 0] = static_cast(entityIndex * 4 + 0); index[entityIndex * 6 + 1] = static_cast(entityIndex * 4 + 1); index[entityIndex * 6 + 2] = static_cast(entityIndex * 4 + 2); index[entityIndex * 6 + 3] = static_cast(entityIndex * 4 + 0); index[entityIndex * 6 + 4] = static_cast(entityIndex * 4 + 2); index[entityIndex * 6 + 5] = static_cast(entityIndex * 4 + 3); } m_VertexArray.Upload(); m_IndexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { Renderer::Backend::GL::CDeviceCommandContext::Rect scissorRect; scissorRect.x = scissorRect.y = 1; scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2; deviceCommandContext->SetScissors(1, &scissorRect); m_IndexArray.UploadIfNeeded(deviceCommandContext); u8* base = m_VertexArray.Bind(deviceCommandContext); 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(); deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer()); deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0); g_Renderer.GetStats().m_DrawCalls++; CVertexBuffer::Unbind(deviceCommandContext); deviceCommandContext->SetScissors(0, nullptr); } tech->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); 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/TerrainTextureManager.cpp =================================================================== --- ps/trunk/source/graphics/TerrainTextureManager.cpp (revision 26587) +++ ps/trunk/source/graphics/TerrainTextureManager.cpp (revision 26588) @@ -1,338 +1,338 @@ /* 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 "TerrainTextureManager.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainProperties.h" #include "graphics/TextureManager.h" #include "lib/allocators/shared_ptr.h" #include "lib/bits.h" #include "lib/ogl.h" #include "lib/tex/tex.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/VideoMode.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/gl/Device.h" #include "renderer/Renderer.h" #include #include #include CTerrainTextureManager::CTerrainTextureManager() : m_LastGroupIndex(0) { if (!VfsDirectoryExists(L"art/terrains/")) return; if (!CXeromyces::AddValidator(g_VFS, "terrain", "art/terrains/terrain.rng")) LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain.rng'"); if (!CXeromyces::AddValidator(g_VFS, "terrain_texture", "art/terrains/terrain_texture.rng")) LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain_texture.rng'"); } CTerrainTextureManager::~CTerrainTextureManager() { UnloadTerrainTextures(); for (std::pair& ta : m_TerrainAlphas) ta.second.m_CompositeAlphaMap.reset(); } void CTerrainTextureManager::UnloadTerrainTextures() { for (CTerrainTextureEntry* const& te : m_TextureEntries) delete te; m_TextureEntries.clear(); for (const std::pair& tg : m_TerrainGroups) delete tg.second; m_TerrainGroups.clear(); m_LastGroupIndex = 0; } CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) const { CStr tag = tag_.BeforeLast("."); // Strip extension for (CTerrainTextureEntry* const& te : m_TextureEntries) if (te->GetTag() == tag) return te; LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag.c_str()); return 0; } CTerrainTextureEntry* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr& props, const VfsPath& path) { CTerrainTextureEntry* entry = new CTerrainTextureEntry(props, path); m_TextureEntries.push_back(entry); return entry; } void CTerrainTextureManager::DeleteTexture(CTerrainTextureEntry* entry) { std::vector::iterator it = std::find(m_TextureEntries.begin(), m_TextureEntries.end(), entry); if (it != m_TextureEntries.end()) m_TextureEntries.erase(it); delete entry; } struct AddTextureCallbackData { CTerrainTextureManager* self; CTerrainPropertiesPtr props; }; static Status AddTextureDirCallback(const VfsPath& pathname, const uintptr_t cbData) { AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData; VfsPath path = pathname / L"terrains.xml"; if (!VfsFileExists(path)) LOGMESSAGE("'%s' does not exist. Using previous properties.", path.string8()); else data.props = CTerrainProperties::FromXML(data.props, path); return INFO::OK; } static Status AddTextureCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) { AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData; if (pathname.Basename() != L"terrains") data.self->AddTexture(data.props, pathname); return INFO::OK; } int CTerrainTextureManager::LoadTerrainTextures() { AddTextureCallbackData data = {this, CTerrainPropertiesPtr(new CTerrainProperties(CTerrainPropertiesPtr()))}; vfs::ForEachFile(g_VFS, L"art/terrains/", AddTextureCallback, (uintptr_t)&data, L"*.xml", vfs::DIR_RECURSIVE, AddTextureDirCallback, (uintptr_t)&data); return 0; } CTerrainGroup* CTerrainTextureManager::FindGroup(const CStr& name) { TerrainGroupMap::const_iterator it = m_TerrainGroups.find(name); if (it != m_TerrainGroups.end()) return it->second; else return m_TerrainGroups[name] = new CTerrainGroup(name, ++m_LastGroupIndex); } // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture. CTerrainTextureManager::TerrainAlphaMap::iterator CTerrainTextureManager::LoadAlphaMap(const VfsPath& alphaMapType) { const std::wstring key = L"(alpha map composite" + alphaMapType.string() + L")"; CTerrainTextureManager::TerrainAlphaMap::iterator it = m_TerrainAlphas.find(alphaMapType); if (it != g_TexMan.m_TerrainAlphas.end()) return it; m_TerrainAlphas[alphaMapType] = TerrainAlpha(); it = m_TerrainAlphas.find(alphaMapType); TerrainAlpha& result = it->second; // // load all textures and store Handle in array // Tex textures[NUM_ALPHA_MAPS] = {}; const VfsPath path = VfsPath("art/textures/terrain/alphamaps") / alphaMapType; const wchar_t* fnames[NUM_ALPHA_MAPS] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // For convenience, we require all alpha maps to be of the same BPP. size_t bpp = 0; for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i) { // note: these individual textures can be discarded afterwards; // we cache the composite. std::shared_ptr fileData; size_t fileSize; if (g_VFS->LoadFile(path / fnames[i], fileData, fileSize) != INFO::OK || textures[i].decode(fileData, fileSize) != INFO::OK) { m_TerrainAlphas.erase(it); LOGERROR("Failed to load alphamap: %s", alphaMapType.string8()); const VfsPath standard("standard"); if (path != standard) return LoadAlphaMap(standard); return m_TerrainAlphas.end(); } // Get its size and make sure they are all equal. // (the packing algo assumes this). if (textures[i].m_Width != textures[i].m_Height) DEBUG_DISPLAY_ERROR(L"Alpha maps are not square"); // .. first iteration: establish size if (i == 0) { base = textures[i].m_Width; bpp = textures[i].m_Bpp; } // .. not first: make sure texture size matches else if (base != textures[i].m_Width || bpp != textures[i].m_Bpp) DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)"); } // // copy each alpha map (tile) into one buffer, arrayed horizontally. // const size_t tileWidth = 2 + base + 2; // 2 pixel border (avoids bilinear filtering artifacts) const size_t totalWidth = round_up_to_pow2(tileWidth * NUM_ALPHA_MAPS); const size_t totalHeight = base; ENSURE(is_pow2(totalHeight)); std::shared_ptr data; AllocateAligned(data, totalWidth * totalHeight, maxSectorSize); // for each tile on row for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i) { // get src of copy u8* src = textures[i].get_data(); ENSURE(src); const size_t srcStep = bpp / 8; // get destination of copy u8* dst = data.get() + (i * tileWidth); // for each row of image for (size_t j = 0; j < base; ++j) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; ++k) { *dst++ = *src; src += srcStep; } // duplicate last pixel *dst++ = *(src - srcStep); *dst++ = *(src - srcStep); // advance write pointer for next row dst += totalWidth - tileWidth; } result.m_AlphaMapCoords[i].u0 = static_cast(i * tileWidth + 2) / totalWidth; result.m_AlphaMapCoords[i].u1 = static_cast((i + 1) * tileWidth - 2) / totalWidth; result.m_AlphaMapCoords[i].v0 = 0.0f; result.m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i) textures[i].free(); // Enable the following to save a png of the generated texture // in the public/ directory, for debugging. #if 0 Tex t; ignore_result(t.wrap(totalWidth, totalHeight, 8, TEX_GREY, data, 0)); const VfsPath filename("blendtex.png"); DynArray da; RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da)); // write to disk //Status ret = INFO::OK; { std::shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if (bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); //else // ret = (Status)bytes_written; } ignore_result(da_free(&da)); #endif result.m_CompositeAlphaMap = g_VideoMode.GetBackendDevice()->CreateTexture2D("CompositeAlphaMap", - Renderer::Backend::Format::A8, totalWidth, totalHeight, + Renderer::Backend::Format::A8_UNORM, totalWidth, totalHeight, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); result.m_CompositeDataToUpload = std::move(data); m_AlphaMapsToUpload.emplace_back(it); return it; } void CTerrainTextureManager::UploadResourcesIfNeeded( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { for (const CTerrainTextureManager::TerrainAlphaMap::iterator& it : m_AlphaMapsToUpload) { TerrainAlpha& alphaMap = it->second; if (!alphaMap.m_CompositeDataToUpload) continue; // Upload the composite texture. Renderer::Backend::GL::CTexture* texture = alphaMap.m_CompositeAlphaMap.get(); deviceCommandContext->UploadTexture( - texture, Renderer::Backend::Format::A8, alphaMap.m_CompositeDataToUpload.get(), + texture, Renderer::Backend::Format::A8_UNORM, alphaMap.m_CompositeDataToUpload.get(), texture->GetWidth() * texture->GetHeight()); alphaMap.m_CompositeDataToUpload.reset(); } m_AlphaMapsToUpload.clear(); } void CTerrainGroup::AddTerrain(CTerrainTextureEntry* pTerrain) { m_Terrains.push_back(pTerrain); } void CTerrainGroup::RemoveTerrain(CTerrainTextureEntry* pTerrain) { std::vector::iterator it = find(m_Terrains.begin(), m_Terrains.end(), pTerrain); if (it != m_Terrains.end()) m_Terrains.erase(it); } Index: ps/trunk/source/graphics/TerritoryTexture.cpp =================================================================== --- ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26587) +++ ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26588) @@ -1,253 +1,253 @@ /* 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 "TerritoryTexture.h" #include "graphics/Color.h" #include "graphics/Terrain.h" #include "lib/bits.h" #include "ps/Profile.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.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) { } CTerritoryTexture::~CTerritoryTexture() { DeleteTexture(); } void CTerritoryTexture::DeleteTexture() { m_Texture.reset(); } bool CTerritoryTexture::UpdateDirty() { CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); return cmpTerritoryManager && cmpTerritoryManager->NeedUpdateTexture(&m_DirtyID); } Renderer::Backend::GL::CTexture* CTerritoryTexture::GetTexture() { ENSURE(!UpdateDirty()); 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(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { 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; const uint32_t textureSize = round_up_to_pow2(static_cast(m_MapSize)); m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerritoryTexture", - Renderer::Backend::Format::R8G8B8A8, textureSize, textureSize, + Renderer::Backend::Format::R8G8B8A8_UNORM, 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 uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize * 4); memset(texData.get(), 0x00, textureSize * textureSize * 4); deviceCommandContext->UploadTexture( - m_Texture.get(), Renderer::Backend::Format::R8G8B8A8, + m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, texData.get(), textureSize * textureSize * 4); texData.reset(); { // 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 / 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 / static_cast(textureSize); m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CTerritoryTexture::RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { // 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(deviceCommandContext); PROFILE("recompute territory texture"); CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); if (!cmpTerritoryManager) return; std::unique_ptr bitmap = std::make_unique(m_MapSize * m_MapSize * 4); GenerateBitmap(cmpTerritoryManager->GetTerritoryGrid(), bitmap.get(), m_MapSize, m_MapSize); deviceCommandContext->UploadTextureRegion( - m_Texture.get(), Renderer::Backend::Format::R8G8B8A8, bitmap.get(), m_MapSize * m_MapSize * 4, + m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, bitmap.get(), m_MapSize * m_MapSize * 4, 0, 0, m_MapSize, m_MapSize); } 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; } void CTerritoryTexture::UpdateIfNeeded(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (UpdateDirty()) RecomputeTexture(deviceCommandContext); } Index: ps/trunk/source/graphics/TextureManager.cpp =================================================================== --- ps/trunk/source/graphics/TextureManager.cpp (revision 26587) +++ ps/trunk/source/graphics/TextureManager.cpp (revision 26588) @@ -1,1027 +1,1027 @@ /* 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 "TextureManager.h" #include "graphics/Color.h" #include "graphics/TextureConverter.h" #include "lib/allocators/shared_ptr.h" #include "lib/file/vfs/vfs_tree.h" #include "lib/hash.h" #include "lib/timer.h" #include "maths/MathUtil.h" #include "maths/MD5.h" #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/VideoMode.h" #include "renderer/backend/gl/Device.h" #include "renderer/Renderer.h" #include #include #include #include #include #include #include namespace { Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(Tex& textureData, const bool hasS3TC) { const bool alpha = (textureData.m_Flags & TEX_ALPHA) != 0; const bool grey = (textureData.m_Flags & TEX_GREY) != 0; const size_t dxt = textureData.m_Flags & TEX_DXT; // Some backends don't support BGR as an internal format (like GLES). // TODO: add a check that the format is internally supported. if ((textureData.m_Flags & TEX_BGR) != 0) { LOGWARNING("Using slow path to convert BGR texture."); textureData.transform_to(textureData.m_Flags & ~TEX_BGR); } if (dxt) { if (hasS3TC) { switch (dxt) { case DXT1A: - return Renderer::Backend::Format::BC1_RGBA; + return Renderer::Backend::Format::BC1_RGBA_UNORM; case 1: - return Renderer::Backend::Format::BC1_RGB; + return Renderer::Backend::Format::BC1_RGB_UNORM; case 3: - return Renderer::Backend::Format::BC2; + return Renderer::Backend::Format::BC2_UNORM; case 5: - return Renderer::Backend::Format::BC3; + return Renderer::Backend::Format::BC3_UNORM; default: LOGERROR("Unknown DXT compression."); return Renderer::Backend::Format::UNDEFINED; } } else textureData.transform_to(textureData.m_Flags & ~TEX_DXT); } switch (textureData.m_Bpp) { case 8: ENSURE(grey); - return Renderer::Backend::Format::L8; + return Renderer::Backend::Format::L8_UNORM; case 24: ENSURE(!alpha); - return Renderer::Backend::Format::R8G8B8; + return Renderer::Backend::Format::R8G8B8_UNORM; case 32: ENSURE(alpha); - return Renderer::Backend::Format::R8G8B8A8; + return Renderer::Backend::Format::R8G8B8A8_UNORM; default: LOGERROR("Unsupported BPP: %zu", textureData.m_Bpp); } return Renderer::Backend::Format::UNDEFINED; } } // anonymous namespace class CPredefinedTexture { public: const CTexturePtr& GetTexture() { return m_Texture; } void CreateTexture( std::unique_ptr backendTexture, CTextureManagerImpl* textureManager) { Renderer::Backend::GL::CTexture* fallback = backendTexture.get(); CTextureProperties props(VfsPath{}); m_Texture = CTexturePtr(new CTexture( std::move(backendTexture), fallback, props, textureManager)); m_Texture->m_State = CTexture::UPLOADED; m_Texture->m_Self = m_Texture; } private: CTexturePtr m_Texture; }; class CSingleColorTexture final : public CPredefinedTexture { public: CSingleColorTexture(const CColor& color, const bool disableGL, CTextureManagerImpl* textureManager) : m_Color(color) { if (disableGL) return; std::stringstream textureName; textureName << "SingleColorTexture ("; textureName << "R:" << m_Color.r << ", "; textureName << "G:" << m_Color.g << ", "; textureName << "B:" << m_Color.b << ", "; textureName << "A:" << m_Color.a << ")"; std::unique_ptr backendTexture = g_VideoMode.GetBackendDevice()->CreateTexture2D( textureName.str().c_str(), - Renderer::Backend::Format::R8G8B8A8, + Renderer::Backend::Format::R8G8B8A8_UNORM, 1, 1, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT)); CreateTexture(std::move(backendTexture), textureManager); } void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (!GetTexture() || !GetTexture()->GetBackendTexture()) return; const SColor4ub color32 = m_Color.AsSColor4ub(); // Construct 1x1 32-bit texture const u8 data[4] = { color32.R, color32.G, color32.B, color32.A }; deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(), - Renderer::Backend::Format::R8G8B8A8, data, std::size(data)); + Renderer::Backend::Format::R8G8B8A8_UNORM, data, std::size(data)); } private: CColor m_Color; }; class CGradientTexture final : public CPredefinedTexture { public: static const uint32_t WIDTH = 256; static const uint32_t NUMBER_OF_LEVELS = 9; CGradientTexture(const CColor& colorFrom, const CColor& colorTo, const bool disableGL, CTextureManagerImpl* textureManager) : m_ColorFrom(colorFrom), m_ColorTo(colorTo) { if (disableGL) return; std::stringstream textureName; textureName << "GradientTexture"; textureName << " From ("; textureName << "R:" << m_ColorFrom.r << ", "; textureName << "G:" << m_ColorFrom.g << ", "; textureName << "B:" << m_ColorFrom.b << ", "; textureName << "A:" << m_ColorFrom.a << ")"; textureName << " To ("; textureName << "R:" << m_ColorTo.r << ","; textureName << "G:" << m_ColorTo.g << ","; textureName << "B:" << m_ColorTo.b << ","; textureName << "A:" << m_ColorTo.a << ")"; std::unique_ptr backendTexture = g_VideoMode.GetBackendDevice()->CreateTexture2D( textureName.str().c_str(), - Renderer::Backend::Format::R8G8B8A8, + Renderer::Backend::Format::R8G8B8A8_UNORM, WIDTH, 1, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), NUMBER_OF_LEVELS); CreateTexture(std::move(backendTexture), textureManager); } void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (!GetTexture() || !GetTexture()->GetBackendTexture()) return; std::array, WIDTH> data; for (uint32_t x = 0; x < WIDTH; ++x) { const float t = static_cast(x) / (WIDTH - 1); const CColor color( Interpolate(m_ColorFrom.r, m_ColorTo.r, t), Interpolate(m_ColorFrom.g, m_ColorTo.g, t), Interpolate(m_ColorFrom.b, m_ColorTo.b, t), Interpolate(m_ColorFrom.a, m_ColorTo.a, t)); const SColor4ub color32 = color.AsSColor4ub(); data[x][0] = color32.R; data[x][1] = color32.G; data[x][2] = color32.B; data[x][3] = color32.A; } for (uint32_t level = 0; level < NUMBER_OF_LEVELS; ++level) { deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(), - Renderer::Backend::Format::R8G8B8A8, data.data(), (WIDTH >> level) * data[0].size(), level); + Renderer::Backend::Format::R8G8B8A8_UNORM, data.data(), (WIDTH >> level) * data[0].size(), level); // Prepare data for the next level. const uint32_t nextLevelWidth = (WIDTH >> (level + 1)); if (nextLevelWidth > 0) { for (uint32_t x = 0; x < nextLevelWidth; ++x) data[x] = data[(x << 1)]; // Border values should be the same. data[nextLevelWidth - 1] = data[(WIDTH >> level) - 1]; } } } private: CColor m_ColorFrom, m_ColorTo; }; struct TPhash { std::size_t operator()(const CTextureProperties& textureProperties) const { std::size_t seed = 0; hash_combine(seed, m_PathHash(textureProperties.m_Path)); hash_combine(seed, textureProperties.m_AddressModeU); hash_combine(seed, textureProperties.m_AddressModeV); hash_combine(seed, textureProperties.m_AnisotropicFilterEnabled); hash_combine(seed, textureProperties.m_FormatOverride); hash_combine(seed, textureProperties.m_IgnoreQuality); return seed; } std::size_t operator()(const CTexturePtr& texture) const { return this->operator()(texture->m_Properties); } private: std::hash m_PathHash; }; struct TPequal_to { bool operator()(const CTextureProperties& lhs, const CTextureProperties& rhs) const { return lhs.m_Path == rhs.m_Path && lhs.m_AddressModeU == rhs.m_AddressModeU && lhs.m_AddressModeV == rhs.m_AddressModeV && lhs.m_AnisotropicFilterEnabled == rhs.m_AnisotropicFilterEnabled && lhs.m_FormatOverride == rhs.m_FormatOverride && lhs.m_IgnoreQuality == rhs.m_IgnoreQuality; } bool operator()(const CTexturePtr& lhs, const CTexturePtr& rhs) const { return this->operator()(lhs->m_Properties, rhs->m_Properties); } }; class CTextureManagerImpl { friend class CTexture; public: CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) : m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL), m_TextureConverter(vfs, highQuality), m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), disableGL, this), m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), disableGL, this), m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this), m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), disableGL, this), m_AlphaGradientTexture( CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this) { // Allow hotloading of textures RegisterFileReloadFunc(ReloadChangedFileCB, this); if (disableGL) return; Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice(); m_HasS3TC = - backendDevice->IsFormatSupported(Renderer::Backend::Format::BC1_RGB) && - backendDevice->IsFormatSupported(Renderer::Backend::Format::BC1_RGBA) && - backendDevice->IsFormatSupported(Renderer::Backend::Format::BC2) && - backendDevice->IsFormatSupported(Renderer::Backend::Format::BC3); + backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGB_UNORM) && + backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGBA_UNORM) && + backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC2_UNORM) && + backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC3_UNORM); } ~CTextureManagerImpl() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } const CTexturePtr& GetErrorTexture() { return m_ErrorTexture.GetTexture(); } const CTexturePtr& GetWhiteTexture() { return m_WhiteTexture.GetTexture(); } const CTexturePtr& GetTransparentTexture() { return m_TransparentTexture.GetTexture(); } const CTexturePtr& GetAlphaGradientTexture() { return m_AlphaGradientTexture.GetTexture(); } /** * See CTextureManager::CreateTexture */ CTexturePtr CreateTexture(const CTextureProperties& props) { // Construct a new default texture with the given properties to use as the search key CTexturePtr texture(new CTexture( nullptr, m_DisableGL ? nullptr : m_DefaultTexture.GetTexture()->GetBackendTexture(), props, this)); // Try to find an existing texture with the given properties TextureCache::iterator it = m_TextureCache.find(texture); if (it != m_TextureCache.end()) return *it; // Can't find an existing texture - finish setting up this new texture texture->m_Self = texture; m_TextureCache.insert(texture); m_HotloadFiles[props.m_Path].insert(texture); return texture; } CTexturePtr WrapBackendTexture( std::unique_ptr backendTexture) { ENSURE(backendTexture); Renderer::Backend::GL::CTexture* fallback = backendTexture.get(); CTextureProperties props(VfsPath{}); CTexturePtr texture(new CTexture( std::move(backendTexture), fallback, props, this)); texture->m_State = CTexture::UPLOADED; texture->m_Self = texture; return texture; } /** * Load the given file into the texture object and upload it to OpenGL. * Assumes the file already exists. */ void LoadTexture(const CTexturePtr& texture, const VfsPath& path) { if (m_DisableGL) return; PROFILE2("load texture"); PROFILE2_ATTR("name: %ls", path.string().c_str()); std::shared_ptr fileData; size_t fileSize; texture->m_TextureData = std::make_unique(); Tex& textureData = *texture->m_TextureData; if (g_VFS->LoadFile(path, fileData, fileSize) != INFO::OK || textureData.decode(fileData, fileSize) != INFO::OK) { LOGERROR("Texture failed to load; \"%s\"", texture->m_Properties.m_Path.string8()); texture->ResetBackendTexture( nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture()); return; } // Initialise base color from the texture texture->m_BaseColor = textureData.get_average_color(); Renderer::Backend::Format format = Renderer::Backend::Format::UNDEFINED; if (texture->m_Properties.m_FormatOverride != Renderer::Backend::Format::UNDEFINED) { format = texture->m_Properties.m_FormatOverride; // TODO: it'd be good to remove the override hack and provide information // via XML. ENSURE((textureData.m_Flags & TEX_DXT) == 0); - if (format == Renderer::Backend::Format::A8) + if (format == Renderer::Backend::Format::A8_UNORM) { ENSURE(textureData.m_Bpp == 8 && (textureData.m_Flags & TEX_GREY)); } - else if (format == Renderer::Backend::Format::R8G8B8A8) + else if (format == Renderer::Backend::Format::R8G8B8A8_UNORM) { ENSURE(textureData.m_Bpp == 32 && (textureData.m_Flags & TEX_ALPHA)); } else debug_warn("Unsupported format override."); } else { format = ChooseFormatAndTransformTextureDataIfNeeded(textureData, m_HasS3TC); } if (format == Renderer::Backend::Format::UNDEFINED) { LOGERROR("Texture failed to choose format; \"%s\"", texture->m_Properties.m_Path.string8()); texture->ResetBackendTexture( nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture()); return; } const uint32_t width = texture->m_TextureData->m_Width; const uint32_t height = texture->m_TextureData->m_Height ; const uint32_t MIPLevelCount = texture->m_TextureData->GetMIPLevels().size(); texture->m_BaseLevelOffset = 0; Renderer::Backend::Sampler::Desc defaultSamplerDesc = Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT); defaultSamplerDesc.addressModeU = texture->m_Properties.m_AddressModeU; defaultSamplerDesc.addressModeV = texture->m_Properties.m_AddressModeV; if (texture->m_Properties.m_AnisotropicFilterEnabled) { int maxAnisotropy = 1; CFG_GET_VAL("textures.maxanisotropy", maxAnisotropy); const int allowedValues[] = {2, 4, 8, 16}; if (std::find(std::begin(allowedValues), std::end(allowedValues), maxAnisotropy) != std::end(allowedValues)) { defaultSamplerDesc.anisotropyEnabled = true; defaultSamplerDesc.maxAnisotropy = maxAnisotropy; } } if (!texture->m_Properties.m_IgnoreQuality) { int quality = 2; CFG_GET_VAL("textures.quality", quality); if (quality == 1) { if (MIPLevelCount > 1 && std::min(width, height) > 8) texture->m_BaseLevelOffset += 1; } else if (quality == 0) { if (MIPLevelCount > 2 && std::min(width, height) > 16) texture->m_BaseLevelOffset += 2; while (std::min(width >> texture->m_BaseLevelOffset, height >> texture->m_BaseLevelOffset) > 256 && MIPLevelCount > texture->m_BaseLevelOffset + 1) { texture->m_BaseLevelOffset += 1; } defaultSamplerDesc.mipFilter = Renderer::Backend::Sampler::Filter::NEAREST; defaultSamplerDesc.anisotropyEnabled = false; } } texture->m_BackendTexture = g_VideoMode.GetBackendDevice()->CreateTexture2D( texture->m_Properties.m_Path.string8().c_str(), format, (width >> texture->m_BaseLevelOffset), (height >> texture->m_BaseLevelOffset), defaultSamplerDesc, MIPLevelCount - texture->m_BaseLevelOffset); } /** * Set up some parameters for the loose cache filename code. */ void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version) { // Hash the settings, so we won't use an old loose cache file if the // settings have changed CTextureConverter::Settings settings = GetConverterSettings(texture); settings.Hash(hash); // Arbitrary version number - change this if we update the code and // need to invalidate old users' caches version = 1; } /** * Attempts to load a cached version of a texture. * If the texture is loaded (or there was an error), returns true. * Otherwise, returns false to indicate the caller should generate the cached version. */ bool TryLoadingCached(const CTexturePtr& texture) { MD5 hash; u32 version; PrepareCacheKey(texture, hash, version); VfsPath loadPath; Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath); if (ret == INFO::OK) { // Found a cached texture - load it LoadTexture(texture, loadPath); return true; } else if (ret == INFO::SKIPPED) { // No cached version was found - we'll need to create it return false; } else { ENSURE(ret < 0); // No source file or archive cache was found, so we can't load the // real texture at all - return the error texture instead LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8()); texture->ResetBackendTexture( nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture()); return true; } } /** * Initiates an asynchronous conversion process, from the texture's * source file to the corresponding loose cache file. */ void ConvertTexture(const CTexturePtr& texture) { VfsPath sourcePath = texture->m_Properties.m_Path; PROFILE2("convert texture"); PROFILE2_ATTR("name: %ls", sourcePath.string().c_str()); MD5 hash; u32 version; PrepareCacheKey(texture, hash, version); VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version); // LOGWARNING("Converting texture \"%s\"", srcPath.c_str()); CTextureConverter::Settings settings = GetConverterSettings(texture); m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings); } bool TextureExists(const VfsPath& path) const { return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK || m_VFS->GetFileInfo(path, 0) == INFO::OK; } bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath) { archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath); CTextureProperties textureProps(sourcePath); CTexturePtr texture = CreateTexture(textureProps); CTextureConverter::Settings settings = GetConverterSettings(texture); if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings)) return false; while (true) { CTexturePtr textureOut; VfsPath dest; bool ok; if (m_TextureConverter.Poll(textureOut, dest, ok)) return ok; std::this_thread::sleep_for(std::chrono::microseconds(1)); } } bool MakeProgress() { // Process any completed conversion tasks { CTexturePtr texture; VfsPath dest; bool ok; if (m_TextureConverter.Poll(texture, dest, ok)) { if (ok) { LoadTexture(texture, dest); } else { LOGERROR("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8()); texture->ResetBackendTexture( nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture()); } texture->m_State = CTexture::LOADED; return true; } } // We'll only push new conversion requests if it's not already busy bool converterBusy = m_TextureConverter.IsBusy(); if (!converterBusy) { // Look for all high-priority textures needing conversion. // (Iterating over all textures isn't optimally efficient, but it // doesn't seem to be a problem yet and it's simpler than maintaining // multiple queues.) for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) { if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING) { // Start converting this texture (*it)->m_State = CTexture::HIGH_IS_CONVERTING; ConvertTexture(*it); return true; } } } // Try loading prefetched textures from their cache for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) { if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING) { if (TryLoadingCached(*it)) { (*it)->m_State = CTexture::LOADED; } else { (*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING; } return true; } } // If we've got nothing better to do, then start converting prefetched textures. if (!converterBusy) { for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) { if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING) { (*it)->m_State = CTexture::PREFETCH_IS_CONVERTING; ConvertTexture(*it); return true; } } } return false; } bool MakeUploadProgress( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (!m_PredefinedTexturesUploaded) { m_DefaultTexture.Upload(deviceCommandContext); m_ErrorTexture.Upload(deviceCommandContext); m_WhiteTexture.Upload(deviceCommandContext); m_TransparentTexture.Upload(deviceCommandContext); m_AlphaGradientTexture.Upload(deviceCommandContext); m_PredefinedTexturesUploaded = true; return true; } return false; } /** * Compute the conversion settings that apply to a given texture, by combining * the textures.xml files from its directory and all parent directories * (up to the VFS root). */ CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture) { fs::wpath srcPath = texture->m_Properties.m_Path.string(); std::vector files; VfsPath p; for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it) { VfsPath settingsPath = p / "textures.xml"; m_HotloadFiles[settingsPath].insert(texture); CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath); if (f) files.push_back(f); p = p / GetWstringFromWpath(*it); } return m_TextureConverter.ComputeSettings(GetWstringFromWpath(srcPath.leaf()), files); } /** * Return the (cached) settings file with the given filename, * or NULL if it doesn't exist. */ CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path) { SettingsFilesMap::iterator it = m_SettingsFiles.find(path); if (it != m_SettingsFiles.end()) return it->second.get(); if (m_VFS->GetFileInfo(path, NULL) >= 0) { std::shared_ptr settings(m_TextureConverter.LoadSettings(path)); m_SettingsFiles.insert(std::make_pair(path, settings)); return settings.get(); } else { m_SettingsFiles.insert(std::make_pair(path, std::shared_ptr())); return NULL; } } static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } Status ReloadChangedFile(const VfsPath& path) { // Uncache settings file, if this is one m_SettingsFiles.erase(path); // Find all textures using this file HotloadFilesMap::iterator files = m_HotloadFiles.find(path); if (files != m_HotloadFiles.end()) { // Flag all textures using this file as needing reloading for (std::set>::iterator it = files->second.begin(); it != files->second.end(); ++it) { if (std::shared_ptr texture = it->lock()) { texture->m_State = CTexture::UNLOADED; texture->ResetBackendTexture( nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture()); texture->m_TextureData.reset(); } } } return INFO::OK; } void ReloadAllTextures() { for (const CTexturePtr& texture : m_TextureCache) { texture->m_State = CTexture::UNLOADED; texture->ResetBackendTexture( nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture()); texture->m_TextureData.reset(); } } size_t GetBytesUploaded() const { size_t size = 0; for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it) size += (*it)->GetUploadedSize(); return size; } void OnQualityChanged() { ReloadAllTextures(); } private: PIVFS m_VFS; CCacheLoader m_CacheLoader; bool m_DisableGL; CTextureConverter m_TextureConverter; CSingleColorTexture m_DefaultTexture; CSingleColorTexture m_ErrorTexture; CSingleColorTexture m_WhiteTexture; CSingleColorTexture m_TransparentTexture; CGradientTexture m_AlphaGradientTexture; bool m_PredefinedTexturesUploaded = false; // Cache of all loaded textures using TextureCache = std::unordered_set; TextureCache m_TextureCache; // TODO: we ought to expire unused textures from the cache eventually // Store the set of textures that need to be reloaded when the given file // (a source file or settings.xml) is modified using HotloadFilesMap = std::unordered_map, std::owner_less>>>; HotloadFilesMap m_HotloadFiles; // Cache for the conversion settings files using SettingsFilesMap = std::unordered_map>; SettingsFilesMap m_SettingsFiles; bool m_HasS3TC = false; }; CTexture::CTexture( std::unique_ptr texture, Renderer::Backend::GL::CTexture* fallback, const CTextureProperties& props, CTextureManagerImpl* textureManager) : m_BackendTexture(std::move(texture)), m_FallbackBackendTexture(fallback), m_BaseColor(0), m_State(UNLOADED), m_Properties(props), m_TextureManager(textureManager) { } CTexture::~CTexture() = default; void CTexture::UploadBackendTextureIfNeeded( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (IsUploaded()) return; if (!IsLoaded()) TryLoad(); if (!IsLoaded()) return; else if (!m_TextureData) { ResetBackendTexture(nullptr, m_TextureManager->GetErrorTexture()->GetBackendTexture()); m_State = UPLOADED; return; } m_UploadedSize = 0; for (uint32_t textureDataLevel = m_BaseLevelOffset, level = 0; textureDataLevel < m_TextureData->GetMIPLevels().size(); ++textureDataLevel) { const Tex::MIPLevel& levelData = m_TextureData->GetMIPLevels()[textureDataLevel]; deviceCommandContext->UploadTexture(m_BackendTexture.get(), m_BackendTexture->GetFormat(), levelData.data, levelData.dataSize, level++); m_UploadedSize += levelData.dataSize; } m_TextureData.reset(); m_State = UPLOADED; } Renderer::Backend::GL::CTexture* CTexture::GetBackendTexture() { return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture; } const Renderer::Backend::GL::CTexture* CTexture::GetBackendTexture() const { return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture; } bool CTexture::TryLoad() { // If we haven't started loading, then try loading, and if that fails then request conversion. // If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority. if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING) { if (std::shared_ptr self = m_Self.lock()) { if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self)) m_State = LOADED; else m_State = HIGH_NEEDS_CONVERTING; } } return IsLoaded() || IsUploaded(); } void CTexture::Prefetch() { if (m_State == UNLOADED) { if (std::shared_ptr self = m_Self.lock()) { m_State = PREFETCH_NEEDS_LOADING; } } } void CTexture::ResetBackendTexture( std::unique_ptr backendTexture, Renderer::Backend::GL::CTexture* fallbackBackendTexture) { m_BackendTexture = std::move(backendTexture); m_FallbackBackendTexture = fallbackBackendTexture; } size_t CTexture::GetWidth() const { return GetBackendTexture()->GetWidth(); } size_t CTexture::GetHeight() const { return GetBackendTexture()->GetHeight(); } bool CTexture::HasAlpha() const { const Renderer::Backend::Format format = GetBackendTexture()->GetFormat(); return - format == Renderer::Backend::Format::A8 || - format == Renderer::Backend::Format::R8G8B8A8 || - format == Renderer::Backend::Format::BC1_RGBA || - format == Renderer::Backend::Format::BC2 || - format == Renderer::Backend::Format::BC3; + format == Renderer::Backend::Format::A8_UNORM || + format == Renderer::Backend::Format::R8G8B8A8_UNORM || + format == Renderer::Backend::Format::BC1_RGBA_UNORM || + format == Renderer::Backend::Format::BC2_UNORM || + format == Renderer::Backend::Format::BC3_UNORM; } u32 CTexture::GetBaseColor() const { return m_BaseColor; } size_t CTexture::GetUploadedSize() const { return m_UploadedSize; } // CTextureManager: forward all calls to impl: CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, bool disableGL) : m(new CTextureManagerImpl(vfs, highQuality, disableGL)) { } CTextureManager::~CTextureManager() { delete m; } CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props) { return m->CreateTexture(props); } CTexturePtr CTextureManager::WrapBackendTexture( std::unique_ptr backendTexture) { return m->WrapBackendTexture(std::move(backendTexture)); } bool CTextureManager::TextureExists(const VfsPath& path) const { return m->TextureExists(path); } const CTexturePtr& CTextureManager::GetErrorTexture() { return m->GetErrorTexture(); } const CTexturePtr& CTextureManager::GetWhiteTexture() { return m->GetWhiteTexture(); } const CTexturePtr& CTextureManager::GetTransparentTexture() { return m->GetTransparentTexture(); } const CTexturePtr& CTextureManager::GetAlphaGradientTexture() { return m->GetAlphaGradientTexture(); } bool CTextureManager::MakeProgress() { return m->MakeProgress(); } bool CTextureManager::MakeUploadProgress( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { return m->MakeUploadProgress(deviceCommandContext); } bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath) { return m->GenerateCachedTexture(path, outputPath); } size_t CTextureManager::GetBytesUploaded() const { return m->GetBytesUploaded(); } void CTextureManager::OnQualityChanged() { m->OnQualityChanged(); } Index: ps/trunk/source/renderer/PostprocManager.cpp =================================================================== --- ps/trunk/source/renderer/PostprocManager.cpp (revision 26587) +++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26588) @@ -1,768 +1,768 @@ /* 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/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/backend/gl/Device.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "tools/atlas/GameInterface/GameLoop.h" #if !CONFIG2_GLES CPostprocManager::CPostprocManager() : m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0) { } CPostprocManager::~CPostprocManager() { Cleanup(); } bool CPostprocManager::IsEnabled() const { return g_RenderingOptions.GetPostProc() && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB; } void CPostprocManager::Cleanup() { if (!m_IsInitialized) // Only cleanup if previously used return; m_CaptureFramebuffer.reset(); m_PingFramebuffer.reset(); m_PongFramebuffer.reset(); m_ColorTex1.reset(); m_ColorTex2.reset(); m_DepthTex.reset(); for (BlurScale& scale : m_BlurScales) { for (BlurScale::Step& step : scale.steps) { step.framebuffer.reset(); step.texture.reset(); } } } void CPostprocManager::Initialize() { if (m_IsInitialized) return; const uint32_t maxSamples = g_VideoMode.GetBackendDevice()->GetCapabilities().maxSampleCount; const uint32_t possibleSampleCounts[] = {2, 4, 8, 16}; std::copy_if( std::begin(possibleSampleCounts), std::end(possibleSampleCounts), std::back_inserter(m_AllowedSampleCounts), [maxSamples](const uint32_t 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(); Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice(); #define GEN_BUFFER_RGBA(name, w, h) \ name = backendDevice->CreateTexture2D( \ - "PostProc" #name, Renderer::Backend::Format::R8G8B8A8, w, h, \ + "PostProc" #name, Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \ Renderer::Backend::Sampler::MakeDefaultSampler( \ Renderer::Backend::Sampler::Filter::LINEAR, \ Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); // 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. uint32_t width = m_Width / 2, height = m_Height / 2; for (BlurScale& scale : m_BlurScales) { for (BlurScale::Step& step : scale.steps) { GEN_BUFFER_RGBA(step.texture, width, height); step.framebuffer = backendDevice->CreateFramebuffer("BlurScaleSteoFramebuffer", step.texture.get(), nullptr); } width /= 2; height /= 2; } #undef GEN_BUFFER_RGBA // Allocate the Depth/Stencil texture. m_DepthTex = backendDevice->CreateTexture2D("PostPRocDepthTexture", 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)); // Set up the framebuffers with some initial textures. m_CaptureFramebuffer = backendDevice->CreateFramebuffer("PostprocCaptureFramebuffer", m_ColorTex1.get(), m_DepthTex.get(), g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor()); m_PingFramebuffer = backendDevice->CreateFramebuffer("PostprocPingFramebuffer", m_ColorTex1.get(), nullptr); m_PongFramebuffer = backendDevice->CreateFramebuffer("PostprocPongFramebuffer", m_ColorTex2.get(), nullptr); if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer) { LOGWARNING("Failed to create postproc framebuffers"); g_RenderingOptions.SetPostProc(false); } if (m_UsingMultisampleBuffer) { DestroyMultisampleBuffer(); CreateMultisampleBuffer(); } } void CPostprocManager::ApplyBlurDownscale2x( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, Renderer::Backend::GL::CFramebuffer* framebuffer, Renderer::Backend::GL::CTexture* inTex, int inWidth, int inHeight) { deviceCommandContext->SetFramebuffer(framebuffer); // 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(); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); const CShaderProgramPtr& shader = tech->GetShader(); 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[] = { 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(); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); } void CPostprocManager::ApplyBlurGauss( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, Renderer::Backend::GL::CTexture* inTex, Renderer::Backend::GL::CTexture* tempTex, Renderer::Backend::GL::CFramebuffer* tempFramebuffer, Renderer::Backend::GL::CFramebuffer* outFramebuffer, int inWidth, int inHeight) { deviceCommandContext->SetFramebuffer(tempFramebuffer); // 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(); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); CShaderProgramPtr shader = tech->GetShader(); shader->BindTexture(str_renderedTex, inTex); 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[] = { 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(); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); deviceCommandContext->SetFramebuffer(outFramebuffer); // 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(); deviceCommandContext->Draw(0, 6); g_Renderer.SetViewport(oldVp); tech->EndPass(); } void CPostprocManager::ApplyBlur( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { uint32_t width = m_Width, height = m_Height; Renderer::Backend::GL::CTexture* previousTexture = (m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get(); for (BlurScale& scale : m_BlurScales) { ApplyBlurDownscale2x(deviceCommandContext, scale.steps[0].framebuffer.get(), previousTexture, width, height); width /= 2; height /= 2; ApplyBlurGauss(deviceCommandContext, scale.steps[0].texture.get(), scale.steps[1].texture.get(), scale.steps[1].framebuffer.get(), scale.steps[0].framebuffer.get(), width, height); } } void CPostprocManager::CaptureRenderOutput( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { ENSURE(m_IsInitialized); // Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point. if (m_UsingMultisampleBuffer) deviceCommandContext->SetFramebuffer(m_MultisampleFramebuffer.get()); else deviceCommandContext->SetFramebuffer(m_CaptureFramebuffer.get()); m_WhichBuffer = true; } void CPostprocManager::ReleaseRenderOutput( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { ENSURE(m_IsInitialized); GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer"); // We blit to the backbuffer from the previous active buffer. deviceCommandContext->BlitFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer(), (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get()); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); } void CPostprocManager::ApplyEffect( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CShaderTechniquePtr& shaderTech, int pass) { // select the other FBO for rendering deviceCommandContext->SetFramebuffer( (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get()); shaderTech->BeginPass(pass); deviceCommandContext->SetGraphicsPipelineState( shaderTech->GetGraphicsPipelineStateDesc(pass)); const CShaderProgramPtr& shader = shaderTech->GetShader(pass); // 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.get()); else shader->BindTexture(str_renderedTex, m_ColorTex2.get()); shader->BindTexture(str_depthTex, m_DepthTex.get()); shader->BindTexture(str_blurTex2, m_BlurScales[0].steps[0].texture.get()); shader->BindTexture(str_blurTex4, m_BlurScales[1].steps[0].texture.get()); shader->BindTexture(str_blurTex8, m_BlurScales[2].steps[0].texture.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(); deviceCommandContext->Draw(0, 6); shaderTech->EndPass(pass); m_WhichBuffer = !m_WhichBuffer; } void CPostprocManager::ApplyPostproc( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { 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; GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc"); 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(deviceCommandContext); for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass) ApplyEffect(deviceCommandContext, m_PostProcTech, pass); } if (hasAA) { for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass) ApplyEffect(deviceCommandContext, m_AATech, pass); } if (hasSharp) { for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass) ApplyEffect(deviceCommandContext, m_SharpTech, pass); } } // 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) { // We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas. if (g_AtlasGameLoop && g_AtlasGameLoop->running) return; if (!g_VideoMode.GetBackendDevice()->GetCapabilities().multisampling && !m_AllowedSampleCounts.empty()) { 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(); } } 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() { Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice(); m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS", Renderer::Backend::GL::CTexture::Type::TEXTURE_2D_MULTISAMPLE, - Renderer::Backend::Format::R8G8B8A8, m_Width, m_Height, + Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount); // Allocate the Depth/Stencil texture. m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS", 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, m_MultisampleCount); // Set up the framebuffers with some initial textures. m_MultisampleFramebuffer = backendDevice->CreateFramebuffer("PostprocMultisampleFramebuffer", m_MultisampleColorTex.get(), m_MultisampleDepthTex.get(), g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor()); if (!m_MultisampleFramebuffer) { LOGERROR("Failed to create postproc multisample framebuffer"); m_UsingMultisampleBuffer = false; DestroyMultisampleBuffer(); } } void CPostprocManager::DestroyMultisampleBuffer() { if (m_UsingMultisampleBuffer) return; m_MultisampleFramebuffer.reset(); m_MultisampleColorTex.reset(); m_MultisampleDepthTex.reset(); } bool CPostprocManager::IsMultisampleEnabled() const { return m_UsingMultisampleBuffer; } void CPostprocManager::ResolveMultisampleFramebuffer( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (!m_UsingMultisampleBuffer) return; GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample"); deviceCommandContext->BlitFramebuffer( m_PingFramebuffer.get(), m_MultisampleFramebuffer.get()); deviceCommandContext->SetFramebuffer(m_PingFramebuffer.get()); } #else #warning TODO: implement PostprocManager for GLES void ApplyBlurDownscale2x( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext), Renderer::Backend::GL::CFramebuffer* UNUSED(framebuffer), Renderer::Backend::GL::CTexture* UNUSED(inTex), int UNUSED(inWidth), int UNUSED(inHeight)) { } void CPostprocManager::ApplyBlurGauss( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext), Renderer::Backend::GL::CTexture* UNUSED(inTex), Renderer::Backend::GL::CTexture* UNUSED(tempTex), Renderer::Backend::GL::CFramebuffer* UNUSED(tempFramebuffer), Renderer::Backend::GL::CFramebuffer* UNUSED(outFramebuffer), int UNUSED(inWidth), int UNUSED(inHeight)) { } void CPostprocManager::ApplyEffect( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext), const CShaderTechniquePtr& UNUSED(shaderTech), int UNUSED(pass)) { } CPostprocManager::CPostprocManager() { } CPostprocManager::~CPostprocManager() { } bool CPostprocManager::IsEnabled() const { return false; } 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( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext)) { } void CPostprocManager::ApplyPostproc( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext)) { } void CPostprocManager::ReleaseRenderOutput( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext)) { } void CPostprocManager::CreateMultisampleBuffer() { } void CPostprocManager::DestroyMultisampleBuffer() { } bool CPostprocManager::IsMultisampleEnabled() const { return false; } void CPostprocManager::ResolveMultisampleFramebuffer( Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext)) { } #endif Index: ps/trunk/source/renderer/ShadowMap.cpp =================================================================== --- ps/trunk/source/renderer/ShadowMap.cpp (revision 26587) +++ ps/trunk/source/renderer/ShadowMap.cpp (revision 26588) @@ -1,772 +1,772 @@ /* 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 "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/Device.h" #include "renderer/backend/gl/Texture.h" #include "renderer/DebugRenderer.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.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 { std::unique_ptr Framebuffer; 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; 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->Framebuffer.reset(); m->Texture.reset(); m->DummyTexture.reset(); delete m; } // Force the texture/buffer/etc to be recreated, particularly when the renderer's // size has changed void ShadowMap::RecreateTexture() { m->Framebuffer.reset(); m->Texture.reset(); m->DummyTexture.reset(); 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 Framebuffer.reset(); Texture.reset(); DummyTexture.reset(); Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice(); 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(backendDevice->GetCapabilities().maxTextureSize)); Width = Height = shadowMapSize; // Since we're using a framebuffer object, the whole texture is available EffectiveWidth = Width; EffectiveHeight = Height; const char* formatName; Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED; #if CONFIG2_GLES formatName = "DEPTH_COMPONENT"; backendFormat = Renderer::Backend::Format::D24; #else switch (DepthTextureBits) { case 16: formatName = "Format::D16"; backendFormat = Renderer::Backend::Format::D16; break; case 24: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break; case 32: formatName = "Format::D32"; backendFormat = Renderer::Backend::Format::D32; break; default: formatName = "Format::D24"; 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 = backendDevice->CreateTexture2D("ShadowMapDummy", - Renderer::Backend::Format::R8G8B8A8, Width, Height, + Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); } Renderer::Backend::Sampler::Desc samplerDesc = 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 LINEAR to trigger automatic PCF on some devices. Renderer::Backend::Sampler::Filter::LINEAR, #endif Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); // Enable automatic depth comparisons samplerDesc.compareEnabled = true; samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL; Texture = backendDevice->CreateTexture2D("ShadowMapDepth", backendFormat, Width, Height, samplerDesc); Framebuffer = backendDevice->CreateFramebuffer("ShadowMapFramebuffer", g_RenderingOptions.GetShadowAlphaFix() ? DummyTexture.get() : nullptr, Texture.get()); if (!Framebuffer) { LOGERROR("Failed to create shadows framebuffer"); // 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() { Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext = g_Renderer.GetDeviceCommandContext(); { PROFILE("bind framebuffer"); ENSURE(m->Framebuffer); deviceCommandContext->SetFramebuffer(m->Framebuffer.get()); } // 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. deviceCommandContext->ClearFramebuffer(); } m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera(); } 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.GetSceneRenderer().SetViewCamera(camera); const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort; Renderer::Backend::GL::CDeviceCommandContext::Rect scissorRect; scissorRect.x = cascadeViewPort.m_X; scissorRect.y = cascadeViewPort.m_Y; scissorRect.width = cascadeViewPort.m_Width; scissorRect.height = cascadeViewPort.m_Height; g_Renderer.GetDeviceCommandContext()->SetScissors(1, &scissorRect); } // Finish rendering into shadow map texture void ShadowMap::EndRender() { g_Renderer.GetDeviceCommandContext()->SetScissors(0, nullptr); g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera); { PROFILE("unbind framebuffer"); g_Renderer.GetDeviceCommandContext()->SetFramebuffer( g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()); } const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() }; g_Renderer.SetViewport(vp); } void ShadowMap::BindTo(const CShaderProgramPtr& shader) const { if (!shader->GetTextureBinding(str_shadowTex).Active() || !m->Texture) return; 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.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn(); shader->Uniform(str_cameraForward, cameraForward.X, cameraForward.Y, cameraForward.Z, cameraForward.Dot(g_Renderer.GetSceneRenderer().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() { // 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.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform; g_Renderer.GetDebugRenderer().DrawBoundingBox( m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true); for (int cascade = 0; cascade < GetCascadeCount(); ++cascade) { g_Renderer.GetDebugRenderer().DrawBoundingBox( m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform); g_Renderer.GetDebugRenderer().DrawBoundingBox( m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true); 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); g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f)); g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true); } ogl_WarnIfError(); } void ShadowMap::RenderDebugTexture( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (!m->Texture) return; #if !CONFIG2_GLES deviceCommandContext->BindTexture(0, GL_TEXTURE_2D, m->Texture->GetHandle()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); #endif CShaderTechniquePtr texTech = g_Renderer.GetShaderManager().LoadEffect(str_canvas2d); texTech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState( texTech->GetGraphicsPipelineStateDesc()); const CShaderProgramPtr& texShader = texTech->GetShader(); texShader->Uniform(str_transform, GetDefaultGuiMatrix()); 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(); deviceCommandContext->Draw(0, 6); texTech->EndPass(); #if !CONFIG2_GLES deviceCommandContext->BindTexture(0, GL_TEXTURE_2D, m->Texture->GetHandle()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); #endif 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 26587) +++ ps/trunk/source/renderer/SkyManager.cpp (revision 26588) @@ -1,336 +1,336 @@ /* 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/SkyManager.h" #include "graphics/LightEnv.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/bits.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/VideoMode.h" #include "ps/World.h" #include "renderer/backend/gl/Device.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include "renderer/RenderingOptions.h" #include SkyManager::SkyManager() : m_VertexArray(Renderer::Backend::GL::CBuffer::Type::VERTEX, false) { CFG_GET_VAL("showsky", m_RenderSky); } void SkyManager::LoadAndUploadSkyTexturesIfNeeded( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (m_SkyCubeMap) return; GPU_SCOPED_LABEL(deviceCommandContext, "Load Sky Textures"); static const CStrW images[NUMBER_OF_TEXTURES + 1] = { L"front", L"back", L"top", L"top", L"right", L"left" }; /*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 = g_VideoMode.GetBackendDevice()->CreateTexture("SkyCubeMap", Renderer::Backend::GL::CTexture::Type::TEXTURE_CUBE, - Renderer::Backend::Format::R8G8B8A8, textures[0].m_Width, textures[0].m_Height, + Renderer::Backend::Format::R8G8B8A8_UNORM, 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, 1); std::vector rotated; for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i) { u8* data = textures[i].get_data(); // We need to rotate the side if it's looking up or down. // TODO: maybe it should be done during texture conversion. if (i == 2 || i == 3) { 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]; } } deviceCommandContext->UploadTexture( - m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8, + m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, &rotated[0], textures[i].m_DataSize, 0, i); } else { deviceCommandContext->UploadTexture( - m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8, + m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, data, textures[i].m_DataSize, 0, i); } } /////////////////////////////////////////////////////////////////////////// } void SkyManager::SetSkySet(const CStrW& newSet) { if (newSet == m_SkySet) return; m_SkyCubeMap.reset(); m_SkySet = newSet; } 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; } void SkyManager::RenderSky( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { GPU_SCOPED_LABEL(deviceCommandContext, "Render sky"); #if CONFIG2_GLES UNUSED2(deviceCommandContext); #warning TODO: implement SkyManager::RenderSky for GLES #else if (!m_RenderSky) return; // Do nothing unless SetSkySet was called if (m_SkySet.empty() || !m_SkyCubeMap) return; if (m_VertexArray.GetNumberOfVertices() == 0) CreateSkyCube(); const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera(); CShaderTechniquePtr skytech = g_Renderer.GetShaderManager().LoadEffect(str_sky_simple); skytech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState( skytech->GetGraphicsPipelineStateDesc()); const CShaderProgramPtr& shader = skytech->GetShader(); 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.GetSceneRenderer().GetLightEnv().GetRotation()); shader->Uniform( str_transform, camera.GetViewProjection() * translate * rotate * scale); m_VertexArray.PrepareForRendering(); u8* base = m_VertexArray.Bind(deviceCommandContext); const GLsizei stride = static_cast(m_VertexArray.GetStride()); shader->VertexPointer( 3, GL_FLOAT, stride, base + m_AttributePosition.offset); shader->TexCoordPointer( GL_TEXTURE0, 3, GL_FLOAT, stride, base + m_AttributeUV.offset); shader->AssertPointersBound(); deviceCommandContext->Draw(0, m_VertexArray.GetNumberOfVertices()); skytech->EndPass(); #endif } void SkyManager::CreateSkyCube() { m_AttributePosition.type = GL_FLOAT; m_AttributePosition.elems = 3; m_VertexArray.AddAttribute(&m_AttributePosition); m_AttributeUV.type = GL_FLOAT; m_AttributeUV.elems = 3; m_VertexArray.AddAttribute(&m_AttributeUV); // 6 sides of cube with 6 vertices. m_VertexArray.SetNumberOfVertices(6 * 6); m_VertexArray.Layout(); VertexArrayIterator attrPosition = m_AttributePosition.GetIterator(); VertexArrayIterator attrUV = m_AttributeUV.GetIterator(); #define ADD_VERTEX(U, V, W, VX, VY, VZ) \ STMT( \ attrPosition->X = VX; \ attrPosition->Y = VY; \ attrPosition->Z = VZ; \ ++attrPosition; \ attrUV->X = U; \ attrUV->Y = V; \ attrUV->Z = W; \ ++attrUV;) // Axis -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); ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); // Axis +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); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); // Axis -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); ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f); ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f); // Axis +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); ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); // Axis -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); ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f); ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f); // Axis +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); 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 m_VertexArray.Upload(); m_VertexArray.FreeBackingStore(); } Index: ps/trunk/source/renderer/TerrainOverlay.cpp =================================================================== --- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26587) +++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26588) @@ -1,394 +1,394 @@ /* 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 "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/backend/gl/Device.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.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( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (g_TerrainOverlayList.empty()) return; PROFILE3_GPU("terrain overlays (before)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays before water"); for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i) g_TerrainOverlayList[i].first->RenderBeforeWater(deviceCommandContext); } void ITerrainOverlay::RenderOverlaysAfterWater( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, int cullGroup) { if (g_TerrainOverlayList.empty()) return; PROFILE3_GPU("terrain overlays (after)"); GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays after water"); for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i) g_TerrainOverlayList[i].first->RenderAfterWater(deviceCommandContext, 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( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (!m_Terrain) return; // should never happen, but let's play it safe #if CONFIG2_GLES UNUSED2(deviceCommandContext); #warning TODO: implement TerrainOverlay::RenderOverlays for GLES #else 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(deviceCommandContext, m_i, m_j); EndRender(); #endif } void TerrainOverlay::RenderTile( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden) { RenderTile(deviceCommandContext, color, drawHidden, m_i, m_j); } void TerrainOverlay::RenderTile( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden, 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 CONFIG2_GLES UNUSED2(deviceCommandContext); UNUSED2(color); UNUSED2(drawHidden); UNUSED2(i); UNUSED2(j); #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); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = overlayTech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.rasterizationState.cullMode = drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK; // To ensure that outlines are drawn on top of the terrain correctly (and // don't Z-fight and flicker nastily), use detph bias to pull them towards // the camera. pipelineStateDesc.rasterizationState.depthBiasEnabled = true; pipelineStateDesc.rasterizationState.depthBiasConstantFactor = -1.0f; pipelineStateDesc.rasterizationState.depthBiasSlopeFactor = -1.0f; overlayTech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); CShaderProgramPtr overlayShader = overlayTech->GetShader(); overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection()); overlayShader->Uniform(str_color, color); overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data()); overlayShader->AssertPointersBound(); deviceCommandContext->Draw(0, vertices.size() / 3); overlayTech->EndPass(); #endif } void TerrainOverlay::RenderTileOutline( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden) { RenderTileOutline(deviceCommandContext, color, drawHidden, m_i, m_j); } void TerrainOverlay::RenderTileOutline( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CColor& color, bool drawHidden, ssize_t i, ssize_t j) { #if CONFIG2_GLES UNUSED2(deviceCommandContext); UNUSED2(color); UNUSED2(drawHidden); UNUSED2(i); UNUSED2(j); #warning TODO: implement TerrainOverlay::RenderTileOutline for GLES #else 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); ADD(i + 1, j + 1); ADD(i, j + 1); #undef ADD CShaderTechniquePtr overlayTech = g_Renderer.GetShaderManager().LoadEffect(str_debug_line); Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = overlayTech->GetGraphicsPipelineStateDesc(); pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden; pipelineStateDesc.blendState.enabled = true; pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor = Renderer::Backend::BlendFactor::SRC_ALPHA; pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor = Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA; pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp = Renderer::Backend::BlendOp::ADD; pipelineStateDesc.rasterizationState.cullMode = drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK; pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE; overlayTech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc); const CShaderProgramPtr& overlayShader = overlayTech->GetShader(); overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection()); overlayShader->Uniform(str_color, color); overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data()); overlayShader->AssertPointersBound(); deviceCommandContext->Draw(0, vertices.size() / 3); overlayTech->EndPass(); #endif } ////////////////////////////////////////////////////////////////////////// TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) : ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile) { } TerrainTextureOverlay::~TerrainTextureOverlay() = default; void TerrainTextureOverlay::RenderAfterWater( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, 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); // Recreate the texture with new size if necessary if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight) { m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerrainOverlayTexture", - Renderer::Backend::Format::R8G8B8A8, requiredWidth, requiredHeight, + Renderer::Backend::Format::R8G8B8A8_UNORM, requiredWidth, requiredHeight, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); } u8* data = (u8*)calloc(w * h, 4); BuildTextureRGBA(data, w, h); deviceCommandContext->UploadTextureRegion( - m_Texture.get(), Renderer::Backend::Format::R8G8B8A8, data, w * h * 4, 0, 0, w, h); + m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, data, w * h * 4, 0, 0, w, h); 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.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture( deviceCommandContext, cullGroup, matrix, m_Texture.get()); } SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const { 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/WaterManager.cpp =================================================================== --- ps/trunk/source/renderer/WaterManager.cpp (revision 26587) +++ ps/trunk/source/renderer/WaterManager.cpp (revision 26588) @@ -1,1024 +1,1024 @@ /* 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 "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderProgram.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/ogl.h" #include "lib/tex/tex.h" #include "maths/MathUtil.h" #include "maths/Vector2D.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Game.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "renderer/backend/gl/Device.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/SceneRenderer.h" #include "renderer/WaterManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/components/ICmpRangeManager.h" #include struct CoastalPoint { CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {}; int index; CVector2D position; }; struct SWavesVertex { // vertex position CVector3D m_BasePosition; CVector3D m_ApexPosition; CVector3D m_SplashPosition; CVector3D m_RetreatPosition; CVector2D m_PerpVect; u8 m_UV[3]; // pad to a power of two u8 m_Padding[5]; }; cassert(sizeof(SWavesVertex) == 64); struct WaveObject { CVertexBufferManager::Handle m_VBVertices; CBoundingBoxAligned m_AABB; size_t m_Width; float m_TimeDiff; }; WaterManager::WaterManager() { // water m_RenderWater = false; // disabled until textures are successfully loaded m_WaterHeight = 5.0f; m_RefTextureSize = 0; m_WaterTexTimer = 0.0; m_WindAngle = 0.0f; m_Waviness = 8.0f; m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f); m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f); m_Murkiness = 0.45f; m_RepeatPeriod = 16.0f; m_WaterEffects = true; m_WaterFancyEffects = false; m_WaterRealDepth = false; m_WaterRefraction = false; m_WaterReflection = false; m_WaterType = L"ocean"; m_NeedsReloading = false; m_NeedInfoUpdate = true; m_MapSize = 0; m_updatei0 = 0; m_updatej0 = 0; m_updatei1 = 0; m_updatej1 = 0; } WaterManager::~WaterManager() { // Cleanup if the caller messed up UnloadWaterTextures(); m_ShoreWaves.clear(); m_ShoreWavesVBIndices.Reset(); m_DistanceHeightmap.reset(); m_WindStrength.reset(); m_FancyEffectsFramebuffer.reset(); m_RefractionFramebuffer.reset(); m_ReflectionFramebuffer.reset(); m_FancyTexture.reset(); m_FancyTextureDepth.reset(); m_ReflFboDepthTexture.reset(); m_RefrFboDepthTexture.reset(); } /////////////////////////////////////////////////////////////////// // Progressive load of water textures int WaterManager::LoadWaterTextures() { // TODO: this doesn't need to be progressive-loading any more // (since texture loading is async now) wchar_t pathname[PATH_MAX]; // Load diffuse grayscale images (for non-fancy water) for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1); CTextureProperties textureProps(pathname); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_WaterTexture[i] = texture; } m_RenderWater = true; if (!WillRenderFancyWater()) return 0; #if CONFIG2_GLES #warning Fix WaterManager::LoadWaterTextures on GLES #else // Load normalmaps (for fancy water) ReloadWaterNormalTextures(); // Load CoastalWaves { CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png"); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_WaveTex = texture; } // Load Foam { CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png"); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_FoamTex = texture; } // Use screen-sized textures for minimum artifacts. m_RefTextureSize = round_up_to_pow2(g_Renderer.GetHeight()); Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice(); // Create reflection texture m_ReflectionTexture = backendDevice->CreateTexture2D("WaterReflectionTexture", - Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize, + Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT)); // Create refraction texture m_RefractionTexture = backendDevice->CreateTexture2D("WaterRefractionTexture", - Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize, + Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT)); // Create depth textures m_ReflFboDepthTexture = backendDevice->CreateTexture2D("WaterReflectionDepthTexture", Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::REPEAT)); m_RefrFboDepthTexture = backendDevice->CreateTexture2D("WaterRefractionDepthTexture", Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::REPEAT)); Resize(); // Create the water framebuffers m_ReflectionFramebuffer = backendDevice->CreateFramebuffer("ReflectionFramebuffer", m_ReflectionTexture.get(), m_ReflFboDepthTexture.get(), CColor(0.5f, 0.5f, 1.0f, 0.0f)); if (!m_ReflectionFramebuffer) { g_RenderingOptions.SetWaterReflection(false); UpdateQuality(); } m_RefractionFramebuffer = backendDevice->CreateFramebuffer("RefractionFramebuffer", m_RefractionTexture.get(), m_RefrFboDepthTexture.get(), CColor(1.0f, 0.0f, 0.0f, 0.0f)); if (!m_RefractionFramebuffer) { g_RenderingOptions.SetWaterRefraction(false); UpdateQuality(); } m_FancyEffectsFramebuffer = backendDevice->CreateFramebuffer("FancyEffectsFramebuffer", m_FancyTexture.get(), m_FancyTextureDepth.get()); if (!m_FancyEffectsFramebuffer) { g_RenderingOptions.SetWaterRefraction(false); UpdateQuality(); } #endif return 0; } /////////////////////////////////////////////////////////////////// // Resize: Updates the fancy water textures. void WaterManager::Resize() { Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice(); // Create the Fancy Effects texture m_FancyTexture = backendDevice->CreateTexture2D("WaterFancyTexture", - Renderer::Backend::Format::R8G8B8A8, g_Renderer.GetWidth(), g_Renderer.GetHeight(), + Renderer::Backend::Format::R8G8B8A8_UNORM, g_Renderer.GetWidth(), g_Renderer.GetHeight(), Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT)); m_FancyTextureDepth = backendDevice->CreateTexture2D("WaterFancyDepthTexture", Renderer::Backend::Format::D32, g_Renderer.GetWidth(), g_Renderer.GetHeight(), Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::REPEAT)); } void WaterManager::ReloadWaterNormalTextures() { wchar_t pathname[PATH_MAX]; for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i) { swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), static_cast(i) + 1); CTextureProperties textureProps(pathname); textureProps.SetAddressMode( Renderer::Backend::Sampler::AddressMode::REPEAT); textureProps.SetAnisotropicFilter(true); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); texture->Prefetch(); m_NormalMap[i] = texture; } } /////////////////////////////////////////////////////////////////// // Unload water textures void WaterManager::UnloadWaterTextures() { for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++) m_WaterTexture[i].reset(); for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++) m_NormalMap[i].reset(); m_RefractionFramebuffer.reset(); m_ReflectionFramebuffer.reset(); m_ReflectionTexture.reset(); m_RefractionTexture.reset(); } template static inline void ComputeDirection(float* distanceMap, const u16* heightmap, float waterHeight, size_t SideSize, size_t maxLevel) { #define ABOVEWATER(x, z) (HEIGHT_SCALE * heightmap[z*SideSize + x] >= waterHeight) #define UPDATELOOKAHEAD \ for (; lookahead <= id2+maxLevel && lookahead < SideSize && \ ((!Transpose && !ABOVEWATER(lookahead, id1)) || (Transpose && !ABOVEWATER(id1, lookahead))); ++lookahead) // Algorithm: // We want to know the distance to the closest shore point. Go through each line/column, // keep track of when we encountered the last shore point and how far ahead the next one is. for (size_t id1 = 0; id1 < SideSize; ++id1) { size_t id2 = 0; const size_t& x = Transpose ? id1 : id2; const size_t& z = Transpose ? id2 : id1; size_t level = ABOVEWATER(x, z) ? 0 : maxLevel; size_t lookahead = (size_t)(level > 0); UPDATELOOKAHEAD; // start moving for (; id2 < SideSize; ++id2) { // update current level if (ABOVEWATER(x, z)) level = 0; else level = std::min(level+1, maxLevel); // move lookahead if (lookahead == id2) ++lookahead; UPDATELOOKAHEAD; // This is the important bit: set the distance to either: // - the distance to the previous shore point (level) // - the distance to the next shore point (lookahead-id2) distanceMap[z*SideSize + x] = std::min(distanceMap[z*SideSize + x], (float)std::min(lookahead-id2, level)); } } #undef ABOVEWATER #undef UPDATELOOKAHEAD } /////////////////////////////////////////////////////////////////// // Calculate our binary heightmap from the terrain heightmap. void WaterManager::RecomputeDistanceHeightmap() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain || !terrain->GetHeightMap()) return; size_t SideSize = m_MapSize; // we want to look ahead some distance, but not too much (less efficient and not interesting). This is our lookahead. const size_t maxLevel = 5; if (!m_DistanceHeightmap) { m_DistanceHeightmap = std::make_unique(SideSize * SideSize); std::fill(m_DistanceHeightmap.get(), m_DistanceHeightmap.get() + SideSize * SideSize, static_cast(maxLevel)); } // Create a manhattan-distance heightmap. // This could be refined to only be done near the coast itself, but it's probably not necessary. u16* heightmap = terrain->GetHeightMap(); ComputeDirection(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel); ComputeDirection(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel); } // This requires m_DistanceHeightmap to be defined properly. void WaterManager::CreateWaveMeshes() { if (m_MapSize == 0) return; CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain || !terrain->GetHeightMap()) return; m_ShoreWaves.clear(); m_ShoreWavesVBIndices.Reset(); if (m_Waviness < 5.0f && m_WaterType != L"ocean") return; size_t SideSize = m_MapSize; // First step: get the points near the coast. std::set CoastalPointsSet; for (size_t z = 1; z < SideSize-1; ++z) for (size_t x = 1; x < SideSize-1; ++x) // get the points not on the shore but near it, ocean-side if (m_DistanceHeightmap[z*m_MapSize + x] > 0.5f && m_DistanceHeightmap[z*m_MapSize + x] < 1.5f) CoastalPointsSet.insert((z)*SideSize + x); // Second step: create chains out of those coastal points. static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } }; std::vector > CoastalPointsChains; while (!CoastalPointsSet.empty()) { int index = *(CoastalPointsSet.begin()); int x = index % SideSize; int y = (index - x ) / SideSize; std::deque Chain; Chain.push_front(CoastalPoint(index,CVector2D(x*4,y*4))); // Erase us. CoastalPointsSet.erase(CoastalPointsSet.begin()); // We're our starter points. At most we can have 2 points close to us. // We'll pick the first one and look for its neighbors (he can only have one new) // Up until we either reach the end of the chain, or ourselves. // Then go down the other direction if there is any. int neighbours[2] = { -1, -1 }; int nbNeighb = 0; for (int i = 0; i < 8; ++i) { if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize)) { if (nbNeighb < 2) neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize; ++nbNeighb; } } if (nbNeighb > 2) continue; for (int i = 0; i < 2; ++i) { if (neighbours[i] == -1) continue; // Move to our neighboring point int xx = neighbours[i] % SideSize; int yy = (neighbours[i] - xx ) / SideSize; int indexx = xx + yy*SideSize; int endedChain = false; if (i == 0) Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); else Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); // If there's a loop we'll be the "other" neighboring point already so check for that. // We'll readd at the end/front the other one to have full squares. if (CoastalPointsSet.count(indexx) == 0) break; CoastalPointsSet.erase(indexx); // Start checking from there. while(!endedChain) { bool found = false; nbNeighb = 0; for (int p = 0; p < 8; ++p) { if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize)) { if (nbNeighb >= 2) { CoastalPointsSet.erase(xx + yy*SideSize); continue; } ++nbNeighb; // We've found a new point around us. // Move there xx = xx + around[p][0]; yy = yy + around[p][1]; indexx = xx + yy*SideSize; if (i == 0) Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); else Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4))); CoastalPointsSet.erase(xx + yy*SideSize); found = true; break; } } if (!found) endedChain = true; } } if (Chain.size() > 10) CoastalPointsChains.push_back(Chain); } // (optional) third step: Smooth chains out. // This is also really dumb. for (size_t i = 0; i < CoastalPointsChains.size(); ++i) { // Bump 1 for smoother. for (int p = 0; p < 3; ++p) { for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j) { CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position; CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f; } } } // Fourth step: create waves themselves, using those chains. We basically create subchains. GLushort waveSizes = 14; // maximal size in width. // Construct indices buffer (we can afford one for all of them) std::vector water_indices; for (GLushort a = 0; a < waveSizes - 1; ++a) { for (GLushort rect = 0; rect < 7; ++rect) { water_indices.push_back(a * 9 + rect); water_indices.push_back(a * 9 + 9 + rect); water_indices.push_back(a * 9 + 1 + rect); water_indices.push_back(a * 9 + 9 + rect); water_indices.push_back(a * 9 + 10 + rect); water_indices.push_back(a * 9 + 1 + rect); } } // Generic indexes, max-length m_ShoreWavesVBIndices = g_VBMan.AllocateChunk( sizeof(GLushort), water_indices.size(), Renderer::Backend::GL::CBuffer::Type::INDEX, false, nullptr, CVertexBufferManager::Group::WATER); m_ShoreWavesVBIndices->m_Owner->UpdateChunkVertices(m_ShoreWavesVBIndices.Get(), &water_indices[0]); float diff = (rand() % 50) / 5.0f; std::vector vertices, reversed; for (size_t i = 0; i < CoastalPointsChains.size(); ++i) { for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j) { if (CoastalPointsChains[i].size()- 1 - j < waveSizes) break; GLushort width = waveSizes; // First pass to get some parameters out. float outmost = 0.0f; // how far to move on the shore. float avgDepth = 0.0f; int sign = 1; CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0); for (GLushort a = 0; a < waveSizes;++a) { lastPerp = perp; perp = CVector2D(0,0); int nb = 0; CVector2D pos = CoastalPointsChains[i][j+a].position; CVector2D posPlus; CVector2D posMinus; if (a > 0) { ++nb; posMinus = CoastalPointsChains[i][j+a-1].position; perp += pos-posMinus; } if (a < waveSizes-1) { ++nb; posPlus = CoastalPointsChains[i][j+a+1].position; perp += posPlus-pos; } perp /= nb; perp = CVector2D(-perp.Y,perp.X).Normalized(); if (a == 0) firstPerp = perp; if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f) { width = a+1; break; } if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight) sign = -1; avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight; float localOutmost = -2.0f; while (localOutmost < 0.0f) { float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight; if (depth < 0.0f || depth > 0.6f) localOutmost += 0.2f; else break; } outmost += localOutmost; } if (width < 5) { j += 6; continue; } outmost /= width; if (outmost > -0.5f) { j += 3; continue; } outmost = -2.5f + outmost * m_Waviness/10.0f; avgDepth /= width; if (avgDepth > -1.3f) { j += 3; continue; } // we passed the checks, we can create a wave of size "width". std::unique_ptr shoreWave = std::make_unique(); vertices.clear(); vertices.reserve(9 * width); shoreWave->m_Width = width; shoreWave->m_TimeDiff = diff; diff += (rand() % 100) / 25.0f + 4.0f; for (GLushort a = 0; a < width;++a) { perp = CVector2D(0,0); int nb = 0; CVector2D pos = CoastalPointsChains[i][j+a].position; CVector2D posPlus; CVector2D posMinus; if (a > 0) { ++nb; posMinus = CoastalPointsChains[i][j+a-1].position; perp += pos-posMinus; } if (a < waveSizes-1) { ++nb; posPlus = CoastalPointsChains[i][j+a+1].position; perp += posPlus-pos; } perp /= nb; perp = CVector2D(-perp.Y,perp.X).Normalized(); SWavesVertex point[9]; float baseHeight = 0.04f; float halfWidth = (width-1.0f)/2.0f; float sideNess = sqrtf(Clamp( (halfWidth - fabsf(a - halfWidth)) / 3.0f, 0.0f, 1.0f)); point[0].m_UV[0] = a; point[0].m_UV[1] = 8; point[1].m_UV[0] = a; point[1].m_UV[1] = 7; point[2].m_UV[0] = a; point[2].m_UV[1] = 6; point[3].m_UV[0] = a; point[3].m_UV[1] = 5; point[4].m_UV[0] = a; point[4].m_UV[1] = 4; point[5].m_UV[0] = a; point[5].m_UV[1] = 3; point[6].m_UV[0] = a; point[6].m_UV[1] = 2; point[7].m_UV[0] = a; point[7].m_UV[1] = 1; point[8].m_UV[0] = a; point[8].m_UV[1] = 0; point[0].m_PerpVect = perp; point[1].m_PerpVect = perp; point[2].m_PerpVect = perp; point[3].m_PerpVect = perp; point[4].m_PerpVect = perp; point[5].m_PerpVect = perp; point[6].m_PerpVect = perp; point[7].m_PerpVect = perp; point[8].m_PerpVect = perp; static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f }; static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f }; static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f }; static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f }; static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 }; static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 }; static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 }; for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost), pos.Y+sign*perp.Y*(perpT1[t]+outmost)); point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT1[t]+outmost)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost), pos.Y+sign*perp.Y*(perpT2[t]+outmost)); point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT2[t]+outmost)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess)); point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess)); } for (size_t t = 0; t < 9; ++t) { float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost), pos.Y+sign*perp.Y*(perpT4[t]+outmost)); point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT4[t]+outmost)); } vertices.push_back(point[8]); vertices.push_back(point[7]); vertices.push_back(point[6]); vertices.push_back(point[5]); vertices.push_back(point[4]); vertices.push_back(point[3]); vertices.push_back(point[2]); vertices.push_back(point[1]); vertices.push_back(point[0]); shoreWave->m_AABB += point[8].m_SplashPosition; shoreWave->m_AABB += point[8].m_BasePosition; shoreWave->m_AABB += point[0].m_SplashPosition; shoreWave->m_AABB += point[0].m_BasePosition; shoreWave->m_AABB += point[4].m_ApexPosition; } if (sign == 1) { // Let's do some fancy reversing. reversed.clear(); reversed.reserve(vertices.size()); for (int a = width - 1; a >= 0; --a) { for (size_t t = 0; t < 9; ++t) reversed.push_back(vertices[a * 9 + t]); } std::swap(vertices, reversed); } j += width/2-1; shoreWave->m_VBVertices = g_VBMan.AllocateChunk( sizeof(SWavesVertex), vertices.size(), Renderer::Backend::GL::CBuffer::Type::VERTEX, false, nullptr, CVertexBufferManager::Group::WATER); shoreWave->m_VBVertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBVertices.Get(), &vertices[0]); m_ShoreWaves.emplace_back(std::move(shoreWave)); } } } void WaterManager::RenderWaves( Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CFrustum& frustrum) { GPU_SCOPED_LABEL(deviceCommandContext, "Render Waves"); #if CONFIG2_GLES UNUSED2(frustrum); #warning Fix WaterManager::RenderWaves on GLES #else if (!m_WaterFancyEffects) return; deviceCommandContext->SetFramebuffer(m_FancyEffectsFramebuffer.get()); deviceCommandContext->ClearFramebuffer(); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_water_waves); tech->BeginPass(); deviceCommandContext->SetGraphicsPipelineState( tech->GetGraphicsPipelineStateDesc()); const CShaderProgramPtr& shader = tech->GetShader(); m_WaveTex->UploadBackendTextureIfNeeded(deviceCommandContext); m_FoamTex->UploadBackendTextureIfNeeded(deviceCommandContext); shader->BindTexture(str_waveTex, m_WaveTex->GetBackendTexture()); shader->BindTexture(str_foamTex, m_FoamTex->GetBackendTexture()); shader->Uniform(str_time, (float)m_WaterTexTimer); shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection()); for (size_t a = 0; a < m_ShoreWaves.size(); ++a) { if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB)) continue; CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBVertices.Get(); SWavesVertex* base = (SWavesVertex*)VBchunk->m_Owner->Bind(deviceCommandContext); // setup data pointers GLsizei stride = sizeof(SWavesVertex); shader->VertexPointer(3, GL_FLOAT, stride, &base[VBchunk->m_Index].m_BasePosition); shader->TexCoordPointer(GL_TEXTURE0, 2, GL_UNSIGNED_BYTE, stride, &base[VBchunk->m_Index].m_UV); // NormalPointer(gl_FLOAT, stride, &base[m_VBWater->m_Index].m_UV) glVertexAttribPointerARB(2, 2, GL_FLOAT, GL_FALSE, stride, &base[VBchunk->m_Index].m_PerpVect); // replaces commented above because my normal is vec2 shader->VertexAttribPointer(str_a_apexPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_ApexPosition); shader->VertexAttribPointer(str_a_splashPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_SplashPosition); shader->VertexAttribPointer(str_a_retreatPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_RetreatPosition); shader->AssertPointersBound(); shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff); shader->Uniform(str_width, (int)m_ShoreWaves[a]->m_Width); m_ShoreWavesVBIndices->m_Owner->UploadIfNeeded(deviceCommandContext); deviceCommandContext->SetIndexBuffer(m_ShoreWavesVBIndices->m_Owner->GetBuffer()); deviceCommandContext->DrawIndexed(m_ShoreWavesVBIndices->m_Index, (m_ShoreWaves[a]->m_Width - 1) * (7 * 6), 0); shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff + 6.0f); // TODO: figure out why this doesn't work. //g_Renderer.m_Stats.m_DrawCalls++; //g_Renderer.m_Stats.m_WaterTris += m_ShoreWaves_VBIndices->m_Count / 3; CVertexBuffer::Unbind(deviceCommandContext); } tech->EndPass(); deviceCommandContext->SetFramebuffer( deviceCommandContext->GetDevice()->GetCurrentBackbuffer()); #endif } void WaterManager::RecomputeWaterData() { if (!m_MapSize) return; RecomputeDistanceHeightmap(); RecomputeWindStrength(); CreateWaveMeshes(); } /////////////////////////////////////////////////////////////////// // Calculate the strength of the wind at a given point on the map. void WaterManager::RecomputeWindStrength() { if (m_MapSize <= 0) return; if (!m_WindStrength) m_WindStrength = std::make_unique(m_MapSize * m_MapSize); CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); if (!terrain || !terrain->GetHeightMap()) return; CVector2D windDir = CVector2D(cos(m_WindAngle), sin(m_WindAngle)); int stepSize = 10; ssize_t windX = -round(stepSize * windDir.X); ssize_t windY = -round(stepSize * windDir.Y); struct SWindPoint { SWindPoint(size_t x, size_t y, float strength) : X(x), Y(y), windStrength(strength) {} ssize_t X; ssize_t Y; float windStrength; }; std::vector startingPoints; std::vector> movement; // Every increment, move each starting point by all of these. // Compute starting points (one or two edges of the map) and how much to move each computation increment. if (fabs(windDir.X) < 0.01f) { movement.emplace_back(0, windY > 0.f ? 1 : -1); startingPoints.reserve(m_MapSize); size_t start = windY > 0 ? 0 : m_MapSize - 1; for (size_t x = 0; x < m_MapSize; ++x) startingPoints.emplace_back(x, start, 0.f); } else if (fabs(windDir.Y) < 0.01f) { movement.emplace_back(windX > 0.f ? 1 : - 1, 0); startingPoints.reserve(m_MapSize); size_t start = windX > 0 ? 0 : m_MapSize - 1; for (size_t z = 0; z < m_MapSize; ++z) startingPoints.emplace_back(start, z, 0.f); } else { startingPoints.reserve(m_MapSize * 2); // Points along X. size_t start = windY > 0 ? 0 : m_MapSize - 1; for (size_t x = 0; x < m_MapSize; ++x) startingPoints.emplace_back(x, start, 0.f); // Points along Z, avoid repeating the corner point. start = windX > 0 ? 0 : m_MapSize - 1; if (windY > 0) for (size_t z = 1; z < m_MapSize; ++z) startingPoints.emplace_back(start, z, 0.f); else for (size_t z = 0; z < m_MapSize-1; ++z) startingPoints.emplace_back(start, z, 0.f); // Compute movement array. movement.reserve(std::max(std::abs(windX),std::abs(windY))); while (windX != 0 || windY != 0) { std::pair move = { windX == 0 ? 0 : windX > 0 ? +1 : -1, windY == 0 ? 0 : windY > 0 ? +1 : -1 }; windX -= move.first; windY -= move.second; movement.push_back(move); } } // We have all starting points ready, move them all until the map is covered. for (SWindPoint& point : startingPoints) { // Starting velocity is 1.0 unless in shallow water. m_WindStrength[point.Y * m_MapSize + point.X] = 1.f; float depth = m_WaterHeight - terrain->GetVertexGroundLevel(point.X, point.Y); if (depth > 0.f && depth < 2.f) m_WindStrength[point.Y * m_MapSize + point.X] = depth / 2.f; point.windStrength = m_WindStrength[point.Y * m_MapSize + point.X]; bool onMap = true; while (onMap) for (size_t step = 0; step < movement.size(); ++step) { // Move wind speed towards the mean. point.windStrength = 0.15f + point.windStrength * 0.85f; // Adjust speed based on height difference, a positive height difference slowly increases speed (simulate venturi effect) // and a lower height reduces speed (wind protection from hills/...) float heightDiff = std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X + movement[step].first, point.Y + movement[step].second)) - std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X, point.Y)); if (heightDiff > 0.f) point.windStrength = std::min(2.f, point.windStrength + std::min(4.f, heightDiff) / 40.f); else point.windStrength = std::max(0.f, point.windStrength + std::max(-4.f, heightDiff) / 5.f); point.X += movement[step].first; point.Y += movement[step].second; if (point.X < 0 || point.X >= static_cast(m_MapSize) || point.Y < 0 || point.Y >= static_cast(m_MapSize)) { onMap = false; break; } m_WindStrength[point.Y * m_MapSize + point.X] = point.windStrength; } } // TODO: should perhaps blur a little, or change the above code to incorporate neighboring tiles a bit. } //////////////////////////////////////////////////////////////////////// // TODO: This will always recalculate for now void WaterManager::SetMapSize(size_t size) { // TODO: Im' blindly trusting the user here. m_MapSize = size; m_NeedInfoUpdate = true; m_updatei0 = 0; m_updatei1 = size; m_updatej0 = 0; m_updatej1 = size; m_DistanceHeightmap.reset(); m_WindStrength.reset(); } //////////////////////////////////////////////////////////////////////// // This will set the bools properly void WaterManager::UpdateQuality() { if (g_RenderingOptions.GetWaterEffects() != m_WaterEffects) { m_WaterEffects = g_RenderingOptions.GetWaterEffects(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterFancyEffects() != m_WaterFancyEffects) { m_WaterFancyEffects = g_RenderingOptions.GetWaterFancyEffects(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterRealDepth() != m_WaterRealDepth) { m_WaterRealDepth = g_RenderingOptions.GetWaterRealDepth(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterRefraction() != m_WaterRefraction) { m_WaterRefraction = g_RenderingOptions.GetWaterRefraction(); m_NeedsReloading = true; } if (g_RenderingOptions.GetWaterReflection() != m_WaterReflection) { m_WaterReflection = g_RenderingOptions.GetWaterReflection(); m_NeedsReloading = true; } } bool WaterManager::WillRenderFancyWater() const { return m_RenderWater && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB && g_RenderingOptions.GetWaterEffects(); } size_t WaterManager::GetCurrentTextureIndex(const double& period) const { ENSURE(period > 0.0); return static_cast(m_WaterTexTimer * ARRAY_SIZE(m_WaterTexture) / period) % ARRAY_SIZE(m_WaterTexture); } size_t WaterManager::GetNextTextureIndex(const double& period) const { ENSURE(period > 0.0); return (GetCurrentTextureIndex(period) + 1) % ARRAY_SIZE(m_WaterTexture); } Index: ps/trunk/source/renderer/backend/Format.h =================================================================== --- ps/trunk/source/renderer/backend/Format.h (revision 26587) +++ ps/trunk/source/renderer/backend/Format.h (revision 26588) @@ -1,51 +1,56 @@ /* 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_RENDERER_BACKEND_FORMAT #define INCLUDED_RENDERER_BACKEND_FORMAT namespace Renderer { namespace Backend { enum class Format { UNDEFINED, - R8G8B8, - R8G8B8A8, + R8G8B8_UNORM, + R8G8B8A8_UNORM, - A8, - L8, + A8_UNORM, + L8_UNORM, + + R32_SFLOAT, + R32G32_SFLOAT, + R32G32B32_SFLOAT, + R32G32B32A32_SFLOAT, D16, D24, D24_S8, D32, - BC1_RGB, - BC1_RGBA, - BC2, - BC3 + BC1_RGB_UNORM, + BC1_RGBA_UNORM, + BC2_UNORM, + BC3_UNORM }; } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_FORMAT Index: ps/trunk/source/renderer/backend/gl/Device.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26587) +++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26588) @@ -1,877 +1,883 @@ /* 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 "Device.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Profile.h" #include "renderer/backend/gl/DeviceCommandContext.h" #include "renderer/backend/gl/Texture.h" #include "scriptinterface/JSON.h" #include "scriptinterface/Object.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRequest.h" #if OS_WIN #include "lib/sysdep/os/win/wgfx.h" // We can't include wutil directly because GL headers conflict with Windows // until we use a proper GL loader. extern void* wutil_GetAppHDC(); #endif #include #include #include // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES #include #include #endif namespace Renderer { namespace Backend { namespace GL { namespace { std::string GetNameImpl() { // GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards. // On top of that WMI can cause crashes with Nvidia Optimus and some netbooks // see http://trac.wildfiregames.com/ticket/1952 // http://trac.wildfiregames.com/ticket/1575 char cardName[128]; const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); // Happens if called before GL initialization. if (!vendor || !renderer) return {}; sprintf_s(cardName, std::size(cardName), "%s %s", vendor, renderer); // Remove crap from vendor names. (don't dare touch the model name - // it's too risky, there are too many different strings). #define SHORTEN(what, charsToKeep) \ if (!strncmp(cardName, what, std::size(what) - 1)) \ memmove(cardName + charsToKeep, cardName + std::size(what) - 1, (strlen(cardName) - (std::size(what) - 1) + 1) * sizeof(char)); SHORTEN("ATI Technologies Inc.", 3); SHORTEN("NVIDIA Corporation", 6); SHORTEN("S3 Graphics", 2); // returned by EnumDisplayDevices SHORTEN("S3 Graphics, Incorporated", 2); // returned by GL_VENDOR #undef SHORTEN return cardName; } std::string GetVersionImpl() { return reinterpret_cast(glGetString(GL_VERSION)); } std::string GetDriverInformationImpl() { const std::string version = GetVersionImpl(); std::string driverInfo; #if OS_WIN driverInfo = CStrW(wgfx_DriverInfo()).ToUTF8(); if (driverInfo.empty()) #endif { if (!version.empty()) { // Add "OpenGL" to differentiate this from the real driver version // (returned by platform-specific detect routines). driverInfo = std::string("OpenGL ") + version; } } if (driverInfo.empty()) return version; return version + " " + driverInfo; } std::vector GetExtensionsImpl() { std::vector extensions; const std::string exts = ogl_ExtensionString(); boost::split(extensions, exts, boost::algorithm::is_space(), boost::token_compress_on); std::sort(extensions.begin(), extensions.end()); return extensions; } void GLAD_API_PTR OnDebugMessage( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei UNUSED(length), const GLchar* message, const void* UNUSED(user_param)) { std::string debugSource = "unknown"; std::string debugType = "unknown"; std::string debugSeverity = "unknown"; switch (source) { case GL_DEBUG_SOURCE_API: debugSource = "the API"; break; case GL_DEBUG_SOURCE_WINDOW_SYSTEM: debugSource = "the window system"; break; case GL_DEBUG_SOURCE_SHADER_COMPILER: debugSource = "the shader compiler"; break; case GL_DEBUG_SOURCE_THIRD_PARTY: debugSource = "a third party"; break; case GL_DEBUG_SOURCE_APPLICATION: debugSource = "the application"; break; case GL_DEBUG_SOURCE_OTHER: debugSource = "somewhere"; break; } switch (type) { case GL_DEBUG_TYPE_ERROR: debugType = "error"; break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: debugType = "deprecated behaviour"; break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: debugType = "undefined behaviour"; break; case GL_DEBUG_TYPE_PORTABILITY: debugType = "portability"; break; case GL_DEBUG_TYPE_PERFORMANCE: debugType = "performance"; break; case GL_DEBUG_TYPE_OTHER: debugType = "other"; break; case GL_DEBUG_TYPE_MARKER: debugType = "marker"; break; case GL_DEBUG_TYPE_PUSH_GROUP: debugType = "push group"; break; case GL_DEBUG_TYPE_POP_GROUP: debugType = "pop group"; break; } switch (severity) { case GL_DEBUG_SEVERITY_HIGH: debugSeverity = "high"; break; case GL_DEBUG_SEVERITY_MEDIUM: debugSeverity = "medium"; break; case GL_DEBUG_SEVERITY_LOW: debugSeverity = "low"; break; case GL_DEBUG_SEVERITY_NOTIFICATION: debugSeverity = "notification"; break; } if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { debug_printf( "OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message); } else { LOGWARNING( "OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message); } } } // anonymous namespace // static std::unique_ptr CDevice::Create(SDL_Window* window, const bool arb) { std::unique_ptr device(new CDevice()); if (window) { // According to https://wiki.libsdl.org/SDL_CreateWindow we don't need to // call SDL_GL_LoadLibrary if we have a window with SDL_WINDOW_OPENGL, // because it'll be called internally for the first created window. device->m_Window = window; device->m_Context = SDL_GL_CreateContext(device->m_Window); if (!device->m_Context) { LOGERROR("SDL_GL_CreateContext failed: '%s'", SDL_GetError()); return nullptr; } #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES ogl_Init(SDL_GL_GetProcAddress, GetX11Display(device->m_Window)); #else ogl_Init(SDL_GL_GetProcAddress); #endif } else { #if OS_WIN ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC()); #elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES ogl_Init(SDL_GL_GetProcAddress, XOpenDisplay(NULL)); #else ogl_Init(SDL_GL_GetProcAddress); #endif #if OS_WIN || defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES // Hack to stop things looking very ugly when scrolling in Atlas. ogl_SetVsyncEnabled(true); #endif } // If we don't have GL2.0 then we don't have GLSL in core. if (!arb && !ogl_HaveVersion(2, 0)) return nullptr; if ((ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr) // ARB && !ogl_HaveVersion(2, 0)) // GLSL || !ogl_HaveExtension("GL_ARB_vertex_buffer_object") // VBO || ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", nullptr) || (!ogl_HaveExtension("GL_EXT_framebuffer_object") && !ogl_HaveExtension("GL_ARB_framebuffer_object"))) { // It doesn't make sense to continue working here, because we're not // able to display anything. DEBUG_DISPLAY_FATAL_ERROR( L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders." L" The game does not support pre-shader graphics cards." L" You are advised to try installing newer drivers and/or upgrade your graphics card." L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734" ); } device->m_Name = GetNameImpl(); device->m_Version = GetVersionImpl(); device->m_DriverInformation = GetDriverInformationImpl(); device->m_Extensions = GetExtensionsImpl(); // Set packing parameters for uploading and downloading data. glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glEnable(GL_TEXTURE_2D); device->m_Backbuffer = CFramebuffer::CreateBackbuffer(device.get()); Capabilities& capabilities = device->m_Capabilities; capabilities.ARBShaders = !ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr); if (capabilities.ARBShaders) capabilities.ARBShadersShadow = ogl_HaveExtension("GL_ARB_fragment_program_shadow"); #if CONFIG2_GLES // Some GLES implementations have GL_EXT_texture_compression_dxt1 // but that only supports DXT1 so we can't use it. capabilities.S3TC = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", nullptr) == 0; #else // Note: we don't bother checking for GL_S3_s3tc - it is incompatible // and irrelevant (was never widespread). capabilities.S3TC = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", nullptr) == 0; #endif #if CONFIG2_GLES capabilities.multisampling = false; capabilities.maxSampleCount = 1; #else capabilities.multisampling = ogl_HaveVersion(3, 3) && ogl_HaveExtension("GL_ARB_multisample") && ogl_HaveExtension("GL_ARB_texture_multisample"); if (capabilities.multisampling) { // By default GL_MULTISAMPLE should be enabled, but enable it for buggy drivers. glEnable(GL_MULTISAMPLE); GLint maxSamples = 1; glGetIntegerv(GL_MAX_SAMPLES, &maxSamples); capabilities.maxSampleCount = maxSamples; } #endif capabilities.anisotropicFiltering = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"); if (capabilities.anisotropicFiltering) { GLfloat maxAnisotropy = 1.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy); capabilities.maxAnisotropy = maxAnisotropy; } GLint maxTextureSize = 1024; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); capabilities.maxTextureSize = maxTextureSize; #if CONFIG2_GLES const bool isDebugInCore = ogl_HaveVersion(3, 2); #else const bool isDebugInCore = ogl_HaveVersion(4, 3); #endif const bool hasDebug = isDebugInCore || ogl_HaveExtension("GL_KHR_debug"); if (hasDebug) { #ifdef NDEBUG bool enableDebugMessages = false; CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages); capabilities.debugLabels = false; CFG_GET_VAL("renderer.backend.debuglabels", capabilities.debugLabels); capabilities.debugScopedLabels = false; CFG_GET_VAL("renderer.backend.debugscopedlabels", capabilities.debugScopedLabels); #else const bool enableDebugMessages = true; capabilities.debugLabels = true; capabilities.debugScopedLabels = true; #endif if (enableDebugMessages) { glEnable(GL_DEBUG_OUTPUT); #if !CONFIG2_GLES glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); #else #warning GLES without GL_DEBUG_OUTPUT_SYNCHRONOUS might call the callback from different threads which might be unsafe. #endif glDebugMessageCallback(OnDebugMessage, nullptr); // Filter out our own debug group messages const GLuint id = 0x0AD; glDebugMessageControl( GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE); glDebugMessageControl( GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE); } } return device; } CDevice::CDevice() = default; CDevice::~CDevice() { if (m_Context) SDL_GL_DeleteContext(m_Context); } void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings) { const char* errstr = "(error)"; #define INTEGER(id) do { \ GLint i = -1; \ glGetIntegerv(GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #id, i); \ } while (false) #define INTEGER2(id) do { \ GLint i[2] = { -1, -1 }; \ glGetIntegerv(GL_##id, i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \ } else { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", i[0]); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", i[1]); \ } \ } while (false) #define FLOAT(id) do { \ GLfloat f = std::numeric_limits::quiet_NaN(); \ glGetFloatv(GL_##id, &f); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #id, f); \ } while (false) #define FLOAT2(id) do { \ GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \ glGetFloatv(GL_##id, f); \ if (ogl_SquelchError(GL_INVALID_ENUM)) { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \ } else { \ Script::SetProperty(rq, settings, "GL_" #id "[0]", f[0]); \ Script::SetProperty(rq, settings, "GL_" #id "[1]", f[1]); \ } \ } while (false) #define STRING(id) do { \ const char* c = (const char*)glGetString(GL_##id); \ if (!c) c = ""; \ if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \ Script::SetProperty(rq, settings, "GL_" #id, std::string(c)); \ } while (false) #define QUERY(target, pname) do { \ GLint i = -1; \ glGetQueryivARB(GL_##target, GL_##pname, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, errstr); \ else \ Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, i); \ } while (false) #define VERTEXPROGRAM(id) do { \ GLint i = -1; \ glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \ } while (false) #define FRAGMENTPROGRAM(id) do { \ GLint i = -1; \ glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \ if (ogl_SquelchError(GL_INVALID_ENUM)) \ Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \ else \ Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \ } while (false) #define BOOL(id) INTEGER(id) ogl_WarnIfError(); // Core OpenGL 1.3: // (We don't bother checking extension strings for anything older than 1.3; // it'll just produce harmless warnings) STRING(VERSION); STRING(VENDOR); STRING(RENDERER); STRING(EXTENSIONS); #if !CONFIG2_GLES INTEGER(MAX_CLIP_PLANES); #endif INTEGER(SUBPIXEL_BITS); #if !CONFIG2_GLES INTEGER(MAX_3D_TEXTURE_SIZE); #endif INTEGER(MAX_TEXTURE_SIZE); INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE); INTEGER2(MAX_VIEWPORT_DIMS); #if !CONFIG2_GLES BOOL(RGBA_MODE); BOOL(INDEX_MODE); BOOL(DOUBLEBUFFER); BOOL(STEREO); #endif FLOAT2(ALIASED_POINT_SIZE_RANGE); FLOAT2(ALIASED_LINE_WIDTH_RANGE); #if !CONFIG2_GLES INTEGER(MAX_ELEMENTS_INDICES); INTEGER(MAX_ELEMENTS_VERTICES); INTEGER(MAX_TEXTURE_UNITS); #endif INTEGER(SAMPLE_BUFFERS); INTEGER(SAMPLES); // TODO: compressed texture formats INTEGER(RED_BITS); INTEGER(GREEN_BITS); INTEGER(BLUE_BITS); INTEGER(ALPHA_BITS); #if !CONFIG2_GLES INTEGER(INDEX_BITS); #endif INTEGER(DEPTH_BITS); INTEGER(STENCIL_BITS); #if !CONFIG2_GLES // Core OpenGL 2.0 (treated as extensions): if (ogl_HaveExtension("GL_EXT_texture_lod_bias")) { FLOAT(MAX_TEXTURE_LOD_BIAS_EXT); } if (ogl_HaveExtension("GL_ARB_occlusion_query")) { QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_ARB_shading_language_100")) { STRING(SHADING_LANGUAGE_VERSION_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_shader")) { INTEGER(MAX_VERTEX_ATTRIBS_ARB); INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB); INTEGER(MAX_VARYING_FLOATS_ARB); INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB); } if (ogl_HaveExtension("GL_ARB_fragment_shader")) { INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") || ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program")) { INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_TEXTURE_COORDS_ARB); } if (ogl_HaveExtension("GL_ARB_draw_buffers")) { INTEGER(MAX_DRAW_BUFFERS_ARB); } // Core OpenGL 3.0: if (ogl_HaveExtension("GL_EXT_gpu_shader4")) { INTEGER(MIN_PROGRAM_TEXEL_OFFSET_EXT); // no _EXT version of these in glext.h INTEGER(MAX_PROGRAM_TEXEL_OFFSET_EXT); } if (ogl_HaveExtension("GL_EXT_framebuffer_object")) { INTEGER(MAX_COLOR_ATTACHMENTS_EXT); INTEGER(MAX_RENDERBUFFER_SIZE_EXT); } if (ogl_HaveExtension("GL_EXT_framebuffer_multisample")) { INTEGER(MAX_SAMPLES_EXT); } if (ogl_HaveExtension("GL_EXT_texture_array")) { INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT); } if (ogl_HaveExtension("GL_EXT_transform_feedback")) { INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT); INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT); INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT); } // Other interesting extensions: if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query")) { QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_ARB_timer_query")) { QUERY(TIMESTAMP, QUERY_COUNTER_BITS); } if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic")) { FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT); } if (ogl_HaveExtension("GL_ARB_texture_rectangle")) { INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program")) { INTEGER(MAX_PROGRAM_MATRICES_ARB); INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB); } if (ogl_HaveExtension("GL_ARB_vertex_program")) { VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB); VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB); VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB); if (ogl_HaveExtension("GL_ARB_fragment_program")) { // The spec seems to say these should be supported, but // Mesa complains about them so let's not bother /* VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB); VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB); */ } } if (ogl_HaveExtension("GL_ARB_fragment_program")) { FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB); if (ogl_HaveExtension("GL_ARB_vertex_program")) { // The spec seems to say these should be supported, but // Intel drivers on Windows complain about them so let's not bother /* FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB); FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB); */ } } if (ogl_HaveExtension("GL_ARB_geometry_shader4")) { INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB); INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB); INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB); INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB); INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB); INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB); } #else // CONFIG2_GLES // Core OpenGL ES 2.0: STRING(SHADING_LANGUAGE_VERSION); INTEGER(MAX_VERTEX_ATTRIBS); INTEGER(MAX_VERTEX_UNIFORM_VECTORS); INTEGER(MAX_VARYING_VECTORS); INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS); INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS); INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS); INTEGER(MAX_TEXTURE_IMAGE_UNITS); INTEGER(MAX_RENDERBUFFER_SIZE); #endif // CONFIG2_GLES // TODO: Support OpenGL platforms which don't use GLX as well. #if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES #define GLXQCR_INTEGER(id) do { \ unsigned int i = UINT_MAX; \ if (glXQueryCurrentRendererIntegerMESA(id, &i)) \ Script::SetProperty(rq, settings, #id, i); \ } while (false) #define GLXQCR_INTEGER2(id) do { \ unsigned int i[2] = { UINT_MAX, UINT_MAX }; \ if (glXQueryCurrentRendererIntegerMESA(id, i)) { \ Script::SetProperty(rq, settings, #id "[0]", i[0]); \ Script::SetProperty(rq, settings, #id "[1]", i[1]); \ } \ } while (false) #define GLXQCR_INTEGER3(id) do { \ unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \ if (glXQueryCurrentRendererIntegerMESA(id, i)) { \ Script::SetProperty(rq, settings, #id "[0]", i[0]); \ Script::SetProperty(rq, settings, #id "[1]", i[1]); \ Script::SetProperty(rq, settings, #id "[2]", i[2]); \ } \ } while (false) #define GLXQCR_STRING(id) do { \ const char* str = glXQueryCurrentRendererStringMESA(id); \ if (str) \ Script::SetProperty(rq, settings, #id ".string", str); \ } while (false) SDL_SysWMinfo wminfo; SDL_VERSION(&wminfo.version); const int ret = SDL_GetWindowWMInfo(m_Window, &wminfo); if (ret && wminfo.subsystem == SDL_SYSWM_X11) { Display* dpy = wminfo.info.x11.display; int scrnum = DefaultScreen(dpy); const char* glxexts = glXQueryExtensionsString(dpy, scrnum); Script::SetProperty(rq, settings, "glx_extensions", glxexts); if (strstr(glxexts, "GLX_MESA_query_renderer") && glXQueryCurrentRendererIntegerMESA && glXQueryCurrentRendererStringMESA) { GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA); GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA); GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA); GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA); GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA); GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA); GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA); GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA); GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA); GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA); } } #endif // SDL_VIDEO_DRIVER_X11 } std::unique_ptr CDevice::CreateCommandContext() { std::unique_ptr commandContet = CDeviceCommandContext::Create(this); m_ActiveCommandContext = commandContet.get(); return commandContet; } std::unique_ptr CDevice::CreateTexture(const char* name, const CTexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CTexture::Create(this, name, type, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { return CreateTexture(name, CTexture::Type::TEXTURE_2D, format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, CTexture* colorAttachment, CTexture* depthStencilAttachment) { return CreateFramebuffer(name, colorAttachment, depthStencilAttachment, CColor(0.0f, 0.0f, 0.0f, 0.0f)); } std::unique_ptr CDevice::CreateFramebuffer( const char* name, CTexture* colorAttachment, CTexture* depthStencilAttachment, const CColor& clearColor) { return CFramebuffer::Create(this, name, colorAttachment, depthStencilAttachment, clearColor); } std::unique_ptr CDevice::CreateBuffer( const char* name, const CBuffer::Type type, const uint32_t size, const bool dynamic) { return CBuffer::Create(this, name, type, size, dynamic); } void CDevice::Present() { if (m_Window) { PROFILE3("swap buffers"); SDL_GL_SwapWindow(m_Window); ogl_WarnIfError(); } bool checkGLErrorAfterSwap = false; CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap); #if defined(NDEBUG) if (!checkGLErrorAfterSwap) return; #endif PROFILE3("error check"); // We have to check GL errors after SwapBuffer to avoid possible // synchronizations during rendering. if (GLenum err = glGetError()) ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err)); } -bool CDevice::IsFormatSupported(const Format format) const +bool CDevice::IsTextureFormatSupported(const Format format) const { bool supported = false; switch (format) { case Format::UNDEFINED: break; - case Format::R8G8B8: FALLTHROUGH; - case Format::R8G8B8A8: FALLTHROUGH; - case Format::A8: FALLTHROUGH; - case Format::L8: + case Format::R8G8B8_UNORM: FALLTHROUGH; + case Format::R8G8B8A8_UNORM: FALLTHROUGH; + case Format::A8_UNORM: FALLTHROUGH; + case Format::L8_UNORM: supported = true; break; + case Format::R32_SFLOAT: FALLTHROUGH; + case Format::R32G32_SFLOAT: FALLTHROUGH; + case Format::R32G32B32_SFLOAT: FALLTHROUGH; + case Format::R32G32B32A32_SFLOAT: + break; + case Format::D16: FALLTHROUGH; case Format::D24: FALLTHROUGH; case Format::D32: supported = true; break; case Format::D24_S8: #if !CONFIG2_GLES supported = true; #endif break; - case Format::BC1_RGB: FALLTHROUGH; - case Format::BC1_RGBA: FALLTHROUGH; - case Format::BC2: FALLTHROUGH; - case Format::BC3: + case Format::BC1_RGB_UNORM: FALLTHROUGH; + case Format::BC1_RGBA_UNORM: FALLTHROUGH; + case Format::BC2_UNORM: FALLTHROUGH; + case Format::BC3_UNORM: supported = m_Capabilities.S3TC; break; } return supported; } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Device.h =================================================================== --- ps/trunk/source/renderer/backend/gl/Device.h (revision 26587) +++ ps/trunk/source/renderer/backend/gl/Device.h (revision 26588) @@ -1,134 +1,134 @@ /* 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_RENDERER_BACKEND_GL_DEVICE #define INCLUDED_RENDERER_BACKEND_GL_DEVICE #include "renderer/backend/Format.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/gl/Framebuffer.h" #include "renderer/backend/gl/Texture.h" #include "scriptinterface/ScriptForward.h" #include #include #include typedef struct SDL_Window SDL_Window; typedef void* SDL_GLContext; namespace Renderer { namespace Backend { namespace GL { class CDeviceCommandContext; class CDevice { public: struct Capabilities { bool S3TC; bool ARBShaders; bool ARBShadersShadow; bool debugLabels; bool debugScopedLabels; bool multisampling; bool anisotropicFiltering; uint32_t maxSampleCount; float maxAnisotropy; uint32_t maxTextureSize; }; ~CDevice(); /** * Creates the GL device and the GL context for the window if it presents. */ static std::unique_ptr Create(SDL_Window* window, const bool arb); const std::string& GetName() const { return m_Name; } const std::string& GetVersion() const { return m_Version; } const std::string& GetDriverInformation() const { return m_DriverInformation; } const std::vector& GetExtensions() const { return m_Extensions; } void Report(const ScriptRequest& rq, JS::HandleValue settings); CFramebuffer* GetCurrentBackbuffer() { return m_Backbuffer.get(); } std::unique_ptr CreateCommandContext(); CDeviceCommandContext* GetActiveCommandContext() { return m_ActiveCommandContext; } std::unique_ptr CreateTexture(const char* name, const CTexture::Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount); std::unique_ptr CreateTexture2D(const char* name, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1); std::unique_ptr CreateFramebuffer( const char* name, CTexture* colorAttachment, CTexture* depthStencilAttachment); std::unique_ptr CreateFramebuffer( const char* name, CTexture* colorAttachment, CTexture* depthStencilAttachment, const CColor& clearColor); std::unique_ptr CreateBuffer( const char* name, const CBuffer::Type type, const uint32_t size, const bool dynamic); void Present(); - bool IsFormatSupported(const Format format) const; + bool IsTextureFormatSupported(const Format format) const; const Capabilities& GetCapabilities() const { return m_Capabilities; } private: CDevice(); SDL_Window* m_Window = nullptr; SDL_GLContext m_Context = nullptr; std::string m_Name; std::string m_Version; std::string m_DriverInformation; std::vector m_Extensions; // GL can have the only one command context at once. // TODO: remove as soon as we have no GL code outside backend, currently // it's used only as a helper for transition. CDeviceCommandContext* m_ActiveCommandContext = nullptr; std::unique_ptr m_Backbuffer; Capabilities m_Capabilities{}; }; } // namespace GL } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_GL_DEVICE Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26587) +++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26588) @@ -1,809 +1,809 @@ /* 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 "DeviceCommandContext.h" #include "ps/CLogger.h" #include "renderer/backend/gl/Buffer.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/Framebuffer.h" #include "renderer/backend/gl/Mapping.h" #include "renderer/backend/gl/Texture.h" #include #include #include namespace Renderer { namespace Backend { namespace GL { namespace { bool operator==(const StencilOpState& lhs, const StencilOpState& rhs) { return lhs.failOp == rhs.failOp && lhs.passOp == rhs.passOp && lhs.depthFailOp == rhs.depthFailOp && lhs.compareOp == rhs.compareOp; } bool operator!=(const StencilOpState& lhs, const StencilOpState& rhs) { return !operator==(lhs, rhs); } bool operator==( const CDeviceCommandContext::Rect& lhs, const CDeviceCommandContext::Rect& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.width == rhs.width && lhs.height == rhs.height; } bool operator!=( const CDeviceCommandContext::Rect& lhs, const CDeviceCommandContext::Rect& rhs) { return !operator==(lhs, rhs); } void ApplyDepthMask(const bool depthWriteEnabled) { glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE); } void ApplyColorMask(const uint8_t colorWriteMask) { glColorMask( (colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE, (colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE); } void ApplyStencilMask(const uint32_t stencilWriteMask) { glStencilMask(stencilWriteMask); } GLenum BufferTypeToGLTarget(const CBuffer::Type type) { GLenum target = GL_ARRAY_BUFFER; switch (type) { case CBuffer::Type::VERTEX: target = GL_ARRAY_BUFFER; break; case CBuffer::Type::INDEX: target = GL_ELEMENT_ARRAY_BUFFER; break; }; return target; } void UploadBufferRegionImpl( const GLenum target, const uint32_t dataOffset, const uint32_t dataSize, const CDeviceCommandContext::UploadBufferFunction& uploadFunction) { ENSURE(dataOffset < dataSize); while (true) { void* mappedData = glMapBufferARB(target, GL_WRITE_ONLY); if (mappedData == nullptr) { // This shouldn't happen unless we run out of virtual address space LOGERROR("glMapBuffer failed"); break; } uploadFunction(static_cast(mappedData) + dataOffset); if (glUnmapBufferARB(target) == GL_TRUE) break; // Unmap might fail on e.g. resolution switches, so just try again // and hope it will eventually succeed LOGMESSAGE("glUnmapBuffer failed, trying again...\n"); } } } // anonymous namespace // static std::unique_ptr CDeviceCommandContext::Create(CDevice* device) { std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device)); deviceCommandContext->m_Framebuffer = device->GetCurrentBackbuffer(); deviceCommandContext->ResetStates(); return deviceCommandContext; } CDeviceCommandContext::CDeviceCommandContext(CDevice* device) : m_Device(device) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); for (std::pair& unit : m_BoundTextures) unit.first = unit.second = 0; } CDeviceCommandContext::~CDeviceCommandContext() = default; void CDeviceCommandContext::SetGraphicsPipelineState( const GraphicsPipelineStateDesc& pipelineStateDesc) { SetGraphicsPipelineStateImpl(pipelineStateDesc, false); } void CDeviceCommandContext::UploadTexture( CTexture* texture, const Format format, const void* data, const size_t dataSize, const uint32_t level, const uint32_t layer) { UploadTextureRegion(texture, format, data, dataSize, 0, 0, std::max(1u, texture->GetWidth() >> level), std::max(1u, texture->GetHeight() >> level), level, layer); } void CDeviceCommandContext::UploadTextureRegion( CTexture* texture, const Format dataFormat, const void* data, const size_t dataSize, const uint32_t xOffset, const uint32_t yOffset, const uint32_t width, const uint32_t height, const uint32_t level, const uint32_t layer) { ENSURE(texture); ENSURE(width > 0 && height > 0); if (texture->GetType() == CTexture::Type::TEXTURE_2D) { ENSURE(layer == 0); - if (texture->GetFormat() == Format::R8G8B8A8 || - texture->GetFormat() == Format::R8G8B8 || - texture->GetFormat() == Format::A8) + if (texture->GetFormat() == Format::R8G8B8A8_UNORM || + texture->GetFormat() == Format::R8G8B8_UNORM || + texture->GetFormat() == Format::A8_UNORM) { ENSURE(texture->GetFormat() == dataFormat); size_t bytesPerPixel = 4; GLenum pixelFormat = GL_RGBA; switch (dataFormat) { - case Format::R8G8B8A8: + case Format::R8G8B8A8_UNORM: break; - case Format::R8G8B8: + case Format::R8G8B8_UNORM: pixelFormat = GL_RGB; bytesPerPixel = 3; break; - case Format::A8: + case Format::A8_UNORM: pixelFormat = GL_ALPHA; bytesPerPixel = 1; break; - case Format::L8: + case Format::L8_UNORM: pixelFormat = GL_LUMINANCE; bytesPerPixel = 1; break; default: debug_warn("Unexpected format."); break; } ENSURE(dataSize == width * height * bytesPerPixel); ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle()); glTexSubImage2D(GL_TEXTURE_2D, level, xOffset, yOffset, width, height, pixelFormat, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } else if ( - texture->GetFormat() == Format::BC1_RGB || - texture->GetFormat() == Format::BC1_RGBA || - texture->GetFormat() == Format::BC2 || - texture->GetFormat() == Format::BC3) + texture->GetFormat() == Format::BC1_RGB_UNORM || + texture->GetFormat() == Format::BC1_RGBA_UNORM || + texture->GetFormat() == Format::BC2_UNORM || + texture->GetFormat() == Format::BC3_UNORM) { ENSURE(xOffset == 0 && yOffset == 0); ENSURE(texture->GetFormat() == dataFormat); // TODO: add data size check. GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; switch (texture->GetFormat()) { - case Format::BC1_RGBA: + case Format::BC1_RGBA_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case Format::BC2: + case Format::BC2_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case Format::BC3: + case Format::BC3_UNORM: internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: break; } ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle()); glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data); ogl_WarnIfError(); } else debug_warn("Unsupported format"); } else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE) { - if (texture->GetFormat() == Format::R8G8B8A8) + if (texture->GetFormat() == Format::R8G8B8A8_UNORM) { ENSURE(texture->GetFormat() == dataFormat); ENSURE(level == 0 && layer < 6); ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height); const size_t bpp = 4; ENSURE(dataSize == width * height * bpp); // The order of layers should be the following: // front, back, top, bottom, right, left static const GLenum targets[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle()); glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); ogl_WarnIfError(); } else debug_warn("Unsupported format"); } else debug_warn("Unsupported type"); } void CDeviceCommandContext::UploadBuffer(CBuffer* buffer, const void* data, const uint32_t dataSize) { UploadBufferRegion(buffer, data, dataSize, 0); } void CDeviceCommandContext::UploadBuffer( CBuffer* buffer, const UploadBufferFunction& uploadFunction) { UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction); } void CDeviceCommandContext::UploadBufferRegion( CBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize) { ENSURE(data); ENSURE(dataOffset + dataSize <= buffer->GetSize()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); glBindBufferARB(target, buffer->GetHandle()); if (buffer->IsDynamic()) { // Tell the driver that it can reallocate the whole VBO glBufferDataARB(target, buffer->GetSize(), nullptr, buffer->IsDynamic() ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); // (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used // here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with // current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do // the explicit glBufferData.) UploadBufferRegion(buffer, dataOffset, dataSize, [data, dataOffset, dataSize](u8* mappedData) { std::memcpy(mappedData, data, dataSize); }); } else { glBufferSubDataARB(target, dataOffset, dataSize, data); } glBindBufferARB(target, 0); } void CDeviceCommandContext::UploadBufferRegion( CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize, const UploadBufferFunction& uploadFunction) { ENSURE(dataOffset + dataSize <= buffer->GetSize()); const GLenum target = BufferTypeToGLTarget(buffer->GetType()); glBindBufferARB(target, buffer->GetHandle()); ENSURE(buffer->IsDynamic()); UploadBufferRegionImpl(target, dataOffset, dataSize, uploadFunction); glBindBufferARB(target, 0); } void CDeviceCommandContext::BeginScopedLabel(const char* name) { if (!m_Device->GetCapabilities().debugScopedLabels) return; ++m_ScopedLabelDepth; glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name); } void CDeviceCommandContext::EndScopedLabel() { if (!m_Device->GetCapabilities().debugScopedLabels) return; ENSURE(m_ScopedLabelDepth > 0); --m_ScopedLabelDepth; glPopDebugGroup(); } void CDeviceCommandContext::BindTexture(const uint32_t unit, const GLenum target, const GLuint handle) { ENSURE(unit < m_BoundTextures.size()); #if CONFIG2_GLES ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP); #else ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE); #endif if (m_BoundTextures[unit].first == target && m_BoundTextures[unit].second == handle) return; if (m_ActiveTextureUnit != unit) { glActiveTexture(GL_TEXTURE0 + unit); m_ActiveTextureUnit = unit; } if (m_BoundTextures[unit].first != target && m_BoundTextures[unit].first && m_BoundTextures[unit].second) glBindTexture(m_BoundTextures[unit].first, 0); if (m_BoundTextures[unit].second != handle) glBindTexture(target, handle); m_BoundTextures[unit] = {target, handle}; } void CDeviceCommandContext::BindBuffer(const CBuffer::Type type, CBuffer* buffer) { ENSURE(!buffer || buffer->GetType() == type); if (type == CBuffer::Type::INDEX) { if (!buffer) m_IndexBuffer = nullptr; m_IndexBufferData = nullptr; } glBindBufferARB(BufferTypeToGLTarget(type), buffer ? buffer->GetHandle() : 0); } void CDeviceCommandContext::Flush() { ResetStates(); m_IndexBuffer = nullptr; m_IndexBufferData = nullptr; BindTexture(0, GL_TEXTURE_2D, 0); BindBuffer(CBuffer::Type::INDEX, nullptr); BindBuffer(CBuffer::Type::VERTEX, nullptr); ENSURE(m_ScopedLabelDepth == 0); } void CDeviceCommandContext::ResetStates() { SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true); SetScissors(0, nullptr); SetFramebuffer(m_Device->GetCurrentBackbuffer()); } void CDeviceCommandContext::SetGraphicsPipelineStateImpl( const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force) { const DepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState; const DepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState; if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled) { if (nextDepthStencilStateDesc.depthTestEnabled) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); } if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp) { glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp)); } if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled) { ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled); } if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled) { if (nextDepthStencilStateDesc.stencilTestEnabled) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); } if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace) { if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace) { glStencilOp( Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp)); } else { if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace) { glStencilOpSeparate( GL_FRONT, Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp)); } if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace) { glStencilOpSeparate( GL_BACK, Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp), Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp)); } } } if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask) { ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask); } if (force || currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference || currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask || currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp || currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp) { if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp) { glStencilFunc( Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); } else { glStencilFuncSeparate(GL_FRONT, Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); glStencilFuncSeparate(GL_BACK, Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp), nextDepthStencilStateDesc.stencilReference, nextDepthStencilStateDesc.stencilReadMask); } } const BlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState; const BlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState; if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled) { if (nextBlendStateDesc.enabled) glEnable(GL_BLEND); else glDisable(GL_BLEND); } if (force || currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor || currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor || currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor || currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor) { if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor && nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor) { glBlendFunc( Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor)); } else { glBlendFuncSeparate( Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor), Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor)); } } if (force || currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp || currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp) { if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp) { glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp)); } else { glBlendEquationSeparate( Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp), Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp)); } } if (force || currentBlendStateDesc.constant != nextBlendStateDesc.constant) { glBlendColor( nextBlendStateDesc.constant.r, nextBlendStateDesc.constant.g, nextBlendStateDesc.constant.b, nextBlendStateDesc.constant.a); } if (force || currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask) { ApplyColorMask(nextBlendStateDesc.colorWriteMask); } const RasterizationStateDesc& currentRasterizationStateDesc = m_GraphicsPipelineStateDesc.rasterizationState; const RasterizationStateDesc& nextRasterizationStateDesc = pipelineStateDesc.rasterizationState; if (force || currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode) { #if !CONFIG2_GLES glPolygonMode( GL_FRONT_AND_BACK, nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL); #endif } if (force || currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode) { if (nextRasterizationStateDesc.cullMode == CullMode::NONE) { glDisable(GL_CULL_FACE); } else { if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE) glEnable(GL_CULL_FACE); glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK); } } if (force || currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace) { if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE) glFrontFace(GL_CW); else glFrontFace(GL_CCW); } #if !CONFIG2_GLES if (force || currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled) { if (nextRasterizationStateDesc.depthBiasEnabled) glEnable(GL_POLYGON_OFFSET_FILL); else glDisable(GL_POLYGON_OFFSET_FILL); } if (force || currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor || currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor) { glPolygonOffset( nextRasterizationStateDesc.depthBiasSlopeFactor, nextRasterizationStateDesc.depthBiasConstantFactor); } #endif m_GraphicsPipelineStateDesc = pipelineStateDesc; } void CDeviceCommandContext::BlitFramebuffer( CFramebuffer* destinationFramebuffer, CFramebuffer* sourceFramebuffer) { #if CONFIG2_GLES UNUSED2(destinationFramebuffer); UNUSED2(sourceFramebuffer); debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES"); #else // Source framebuffer should not be backbuffer. ENSURE( sourceFramebuffer->GetHandle() != 0); ENSURE( destinationFramebuffer != sourceFramebuffer ); glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle()); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle()); // TODO: add more check for internal formats. And currently we don't support // scaling inside blit. glBlitFramebufferEXT( 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(), 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(), (sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()), GL_NEAREST); #endif } void CDeviceCommandContext::ClearFramebuffer() { ClearFramebuffer(true, true, true); } void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil) { const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0; const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0; const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0; GLbitfield mask = 0; if (needsColor) { ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA); glClearColor( m_Framebuffer->GetClearColor().r, m_Framebuffer->GetClearColor().g, m_Framebuffer->GetClearColor().b, m_Framebuffer->GetClearColor().a); mask |= GL_COLOR_BUFFER_BIT; } if (needsDepth) { ApplyDepthMask(true); mask |= GL_DEPTH_BUFFER_BIT; } if (needsStencil) { ApplyStencilMask(std::numeric_limits::max()); mask |= GL_STENCIL_BUFFER_BIT; } glClear(mask); if (needsColor) ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask); if (needsDepth) ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled); if (needsStencil) ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask); } void CDeviceCommandContext::SetFramebuffer(CFramebuffer* framebuffer) { ENSURE(framebuffer); ENSURE(framebuffer->GetHandle() == 0 || (framebuffer->GetWidth() > 0 && framebuffer->GetHeight() > 0)); m_Framebuffer = framebuffer; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->GetHandle()); } void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors) { ENSURE(scissorCount <= 1); if (scissorCount == 0) { if (m_ScissorCount != scissorCount) glDisable(GL_SCISSOR_TEST); } else { if (m_ScissorCount != scissorCount) glEnable(GL_SCISSOR_TEST); ENSURE(scissors); if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0]) { m_Scissors[0] = scissors[0]; glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height); } } m_ScissorCount = scissorCount; } void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports) { ENSURE(viewportCount == 1); glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height); } void CDeviceCommandContext::SetIndexBuffer(CBuffer* buffer) { ENSURE(buffer->GetType() == CBuffer::Type::INDEX); m_IndexBuffer = buffer; m_IndexBufferData = nullptr; BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer); } void CDeviceCommandContext::SetIndexBufferData(const void* data) { if (m_IndexBuffer) { BindBuffer(CBuffer::Type::INDEX, nullptr); m_IndexBuffer = nullptr; } m_IndexBufferData = data; } void CDeviceCommandContext::Draw( const uint32_t firstVertex, const uint32_t vertexCount) { // Some drivers apparently don't like count = 0 in glDrawArrays here, so skip // all drawing in that case. if (vertexCount == 0) return; glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); } void CDeviceCommandContext::DrawIndexed( const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset) { if (indexCount == 0) return; ENSURE(m_IndexBuffer || m_IndexBufferData); ENSURE(vertexOffset == 0); if (m_IndexBuffer) { ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize()); } // 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). glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex))); } void CDeviceCommandContext::DrawIndexedInRange( const uint32_t firstIndex, const uint32_t indexCount, const uint32_t start, const uint32_t end) { if (indexCount == 0) return; ENSURE(m_IndexBuffer || m_IndexBufferData); const void* indices = static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)); // Draw with DrawRangeElements where available, since it might be more // efficient for slow hardware. #if CONFIG2_GLES glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices); #else glDrawRangeElementsEXT(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices); #endif } CDeviceCommandContext::ScopedBind::ScopedBind( CDeviceCommandContext* deviceCommandContext, const GLenum target, const GLuint handle) : m_DeviceCommandContext(deviceCommandContext), m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit]) { m_DeviceCommandContext->BindTexture( m_DeviceCommandContext->m_ActiveTextureUnit, target, handle); } CDeviceCommandContext::ScopedBind::~ScopedBind() { m_DeviceCommandContext->BindTexture( m_DeviceCommandContext->m_ActiveTextureUnit, m_OldBindUnit.first, m_OldBindUnit.second); } } // namespace GL } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/gl/Texture.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26587) +++ ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26588) @@ -1,302 +1,304 @@ /* 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 "Texture.h" #include "lib/code_annotation.h" #include "lib/config2.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.h" #include "renderer/backend/gl/Mapping.h" #include namespace Renderer { namespace Backend { namespace GL { namespace { GLint CalculateMinFilter(const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount) { if (MIPLevelCount == 1) return defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST; if (defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR) return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST; return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST; } GLint AddressModeToGLEnum(Sampler::AddressMode addressMode) { switch (addressMode) { case Sampler::AddressMode::REPEAT: return GL_REPEAT; case Sampler::AddressMode::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; case Sampler::AddressMode::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; case Sampler::AddressMode::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; } return GL_REPEAT; } GLenum TypeToGLEnum(CTexture::Type type) { GLenum target = GL_TEXTURE_2D; switch (type) { case CTexture::Type::TEXTURE_2D: target = GL_TEXTURE_2D; break; case CTexture::Type::TEXTURE_2D_MULTISAMPLE: #if CONFIG2_GLES ENSURE(false && "Multisample textures are unsupported on GLES"); #else target = GL_TEXTURE_2D_MULTISAMPLE; #endif break; case CTexture::Type::TEXTURE_CUBE: target = GL_TEXTURE_CUBE_MAP; break; } return target; } } // anonymous namespace // static std::unique_ptr CTexture::Create(CDevice* device, const char* name, const Type type, const Format format, const uint32_t width, const uint32_t height, const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount) { std::unique_ptr texture(new CTexture()); ENSURE(format != Format::UNDEFINED); ENSURE(width > 0 && height > 0 && MIPLevelCount > 0); ENSURE((type == Type::TEXTURE_2D_MULTISAMPLE && sampleCount > 1) || sampleCount == 1); texture->m_Device = device; texture->m_Format = format; texture->m_Type = type; texture->m_Width = width; texture->m_Height = height; texture->m_MIPLevelCount = MIPLevelCount; glGenTextures(1, &texture->m_Handle); ogl_WarnIfError(); const GLenum target = TypeToGLEnum(type); texture->m_Device->GetActiveCommandContext()->BindTexture(0, target, texture->m_Handle); // It's forbidden to set sampler state for multisample textures. if (type != Type::TEXTURE_2D_MULTISAMPLE) { glTexParameteri(target, GL_TEXTURE_MIN_FILTER, CalculateMinFilter(defaultSamplerDesc, MIPLevelCount)); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, defaultSamplerDesc.magFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST); ogl_WarnIfError(); glTexParameteri(target, GL_TEXTURE_WRAP_S, AddressModeToGLEnum(defaultSamplerDesc.addressModeU)); glTexParameteri(target, GL_TEXTURE_WRAP_T, AddressModeToGLEnum(defaultSamplerDesc.addressModeV)); } #if !CONFIG2_GLES if (type == Type::TEXTURE_CUBE) glTexParameteri(target, GL_TEXTURE_WRAP_R, AddressModeToGLEnum(defaultSamplerDesc.addressModeW)); #endif ogl_WarnIfError(); #if !CONFIG2_GLES glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, MIPLevelCount - 1); if (defaultSamplerDesc.mipLODBias != 0.0f) glTexParameteri(target, GL_TEXTURE_LOD_BIAS, defaultSamplerDesc.mipLODBias); #endif // !CONFIG2_GLES if (type == Type::TEXTURE_2D && defaultSamplerDesc.anisotropyEnabled && texture->m_Device->GetCapabilities().anisotropicFiltering) { const float maxAnisotropy = std::min( defaultSamplerDesc.maxAnisotropy, texture->m_Device->GetCapabilities().maxAnisotropy); glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy); } if (defaultSamplerDesc.addressModeU == Sampler::AddressMode::CLAMP_TO_BORDER || defaultSamplerDesc.addressModeV == Sampler::AddressMode::CLAMP_TO_BORDER || defaultSamplerDesc.addressModeW == Sampler::AddressMode::CLAMP_TO_BORDER) { glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, defaultSamplerDesc.borderColor.AsFloatArray()); } ogl_WarnIfError(); if (type == CTexture::Type::TEXTURE_2D) { bool compressedFormat = false; GLint internalFormat = GL_RGBA; // Actually pixel data is nullptr so it doesn't make sense to account // it, but in theory some buggy drivers might complain about invalid // pixel format. GLenum pixelFormat = GL_RGBA; GLenum pixelType = GL_UNSIGNED_BYTE; switch (format) { case Format::UNDEFINED: debug_warn("Texture should defined format"); break; - case Format::R8G8B8A8: + case Format::R8G8B8A8_UNORM: break; - case Format::R8G8B8: + case Format::R8G8B8_UNORM: internalFormat = GL_RGB; pixelFormat = GL_RGB; pixelType = GL_UNSIGNED_BYTE; break; - case Format::A8: + case Format::A8_UNORM: internalFormat = GL_ALPHA; pixelFormat = GL_ALPHA; pixelType = GL_UNSIGNED_BYTE; break; - case Format::L8: + case Format::L8_UNORM: internalFormat = GL_LUMINANCE; pixelFormat = GL_LUMINANCE; pixelType = GL_UNSIGNED_BYTE; break; #if CONFIG2_GLES // GLES requires pixel type == UNSIGNED_SHORT or UNSIGNED_INT for depth. case Format::D16: FALLTHROUGH; case Format::D24: FALLTHROUGH; case Format::D32: internalFormat = GL_DEPTH_COMPONENT; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D24_S8: debug_warn("Unsupported format"); break; #else case Format::D16: internalFormat = GL_DEPTH_COMPONENT16; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D24: internalFormat = GL_DEPTH_COMPONENT24; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D32: internalFormat = GL_DEPTH_COMPONENT32; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; break; case Format::D24_S8: internalFormat = GL_DEPTH24_STENCIL8_EXT; pixelFormat = GL_DEPTH_STENCIL_EXT; pixelType = GL_UNSIGNED_INT_24_8_EXT; break; #endif - case Format::BC1_RGB: FALLTHROUGH; - case Format::BC1_RGBA: FALLTHROUGH; - case Format::BC2: FALLTHROUGH; - case Format::BC3: + case Format::BC1_RGB_UNORM: FALLTHROUGH; + case Format::BC1_RGBA_UNORM: FALLTHROUGH; + case Format::BC2_UNORM: FALLTHROUGH; + case Format::BC3_UNORM: compressedFormat = true; break; + default: + debug_warn("Unsupported format."); } // glCompressedTexImage2D can't accept a null data, so we will initialize it during uploading. if (!compressedFormat) { for (uint32_t level = 0; level < MIPLevelCount; ++level) { glTexImage2D(target, level, internalFormat, std::max(1u, width >> level), std::max(1u, height >> level), 0, pixelFormat, pixelType, nullptr); } } } else if (type == CTexture::Type::TEXTURE_2D_MULTISAMPLE) { ENSURE(MIPLevelCount == 1); #if !CONFIG2_GLES - if (format == Format::R8G8B8A8) + if (format == Format::R8G8B8A8_UNORM) { glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_RGBA8, width, height, GL_TRUE); } else if (format == Format::D24_S8) { glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_DEPTH24_STENCIL8_EXT, width, height, GL_TRUE); } else #endif // !CONFIG2_GLES { debug_warn("Unsupported format"); } } #if !CONFIG2_GLES if (format == Format::D16 || format == Format::D24 || format == Format::D32 || format == Format::D24_S8) { if (defaultSamplerDesc.compareEnabled) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, Mapping::FromCompareOp(defaultSamplerDesc.compareOp)); } else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); } #endif ogl_WarnIfError(); if (texture->m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_TEXTURE, texture->m_Handle, -1, name); } texture->m_Device->GetActiveCommandContext()->BindTexture(0, target, 0); return texture; } CTexture::CTexture() = default; CTexture::~CTexture() { if (m_Handle) glDeleteTextures(1, &m_Handle); } } // namespace GL } // namespace Backend } // namespace Renderer