Index: ps/trunk/source/renderer/PostprocManager.cpp =================================================================== --- ps/trunk/source/renderer/PostprocManager.cpp (revision 26484) +++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26485) @@ -1,775 +1,771 @@ /* 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, \ 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)); - g_Renderer.GetDeviceCommandContext()->BindTexture(0, GL_TEXTURE_2D, m_DepthTex->GetHandle()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - g_Renderer.GetDeviceCommandContext()->BindTexture(0, GL_TEXTURE_2D, 0); - // 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(); glDrawArrays(GL_TRIANGLES, 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(); glDrawArrays(GL_TRIANGLES, 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(); glDrawArrays(GL_TRIANGLES, 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(); glDrawArrays(GL_TRIANGLES, 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() { glEnable(GL_MULTISAMPLE); 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::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(); glDisable(GL_MULTISAMPLE); } 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 26484) +++ ps/trunk/source/renderer/ShadowMap.cpp (revision 26485) @@ -1,775 +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 "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::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::NEAREST, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); } - Texture = backendDevice->CreateTexture2D("ShadowMapDepth", - backendFormat, Width, Height, + 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 GL_LINEAR to trigger automatic PCF on some devices Renderer::Backend::Sampler::Filter::LINEAR, #endif - Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); - - -#if !CONFIG2_GLES - g_Renderer.GetDeviceCommandContext()->BindTexture(0, GL_TEXTURE_2D, Texture->GetHandle()); + Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE); // Enable automatic depth comparisons - glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - g_Renderer.GetDeviceCommandContext()->BindTexture(0, GL_TEXTURE_2D, 0); -#endif + samplerDesc.compareEnabled = true; + samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL; - ogl_WarnIfError(); + 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"); 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::ScissorRect 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().DrawBoundingBoxOutline(m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform); 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().DrawBoundingBoxOutline(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform); 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().DrawBrushOutline(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.5f)); } 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(); glDrawArrays(GL_TRIANGLES, 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/backend/CompareOp.cpp =================================================================== --- ps/trunk/source/renderer/backend/CompareOp.cpp (nonexistent) +++ ps/trunk/source/renderer/backend/CompareOp.cpp (revision 26485) @@ -0,0 +1,47 @@ +/* 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 "CompareOp.h" + +namespace Renderer +{ + +namespace Backend +{ + +CompareOp ParseCompareOp(const CStr& str) +{ + // TODO: it might make sense to use upper case in XML for consistency. +#define CASE(NAME, VALUE) if (str == NAME) return CompareOp::VALUE + CASE("never", NEVER); + CASE("less", LESS); + CASE("equal", EQUAL); + CASE("lequal", LESS_OR_EQUAL); + CASE("greater", GREATER); + CASE("notequal", NOT_EQUAL); + CASE("gequal", GREATER_OR_EQUAL); + CASE("always", ALWAYS); +#undef CASE + debug_warn("Invalid compare op"); + return CompareOp::NEVER; +} + +} // namespace Backend + +} // namespace Renderer Property changes on: ps/trunk/source/renderer/backend/CompareOp.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/backend/CompareOp.h =================================================================== --- ps/trunk/source/renderer/backend/CompareOp.h (nonexistent) +++ ps/trunk/source/renderer/backend/CompareOp.h (revision 26485) @@ -0,0 +1,57 @@ +/* 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_COMPAREOP +#define INCLUDED_RENDERER_BACKEND_COMPAREOP + +#include "graphics/Color.h" + +class CStr; + +namespace Renderer +{ + +namespace Backend +{ + +enum class CompareOp +{ + // Never passes the comparison. + NEVER, + // Passes if the source value is less than the destination value. + LESS, + // Passes if the source depth value is equal to the destination value. + EQUAL, + // Passes if the source depth value is less than or equal to the destination value. + LESS_OR_EQUAL, + // Passes if the source depth value is greater than the destination value. + GREATER, + // Passes if the source depth value is not equal to the destination value. + NOT_EQUAL, + // Passes if the source depth value is greater than or equal to the destination value. + GREATER_OR_EQUAL, + // Always passes the comparison. + ALWAYS +}; + +CompareOp ParseCompareOp(const CStr& str); + +} // namespace Backend + +} // namespace Renderer + +#endif // INCLUDED_RENDERER_BACKEND_COMPAREOP Property changes on: ps/trunk/source/renderer/backend/CompareOp.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/backend/PipelineState.cpp =================================================================== --- ps/trunk/source/renderer/backend/PipelineState.cpp (revision 26484) +++ ps/trunk/source/renderer/backend/PipelineState.cpp (revision 26485) @@ -1,160 +1,143 @@ /* 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 "PipelineState.h" #include namespace Renderer { namespace Backend { GraphicsPipelineStateDesc MakeDefaultGraphicsPipelineStateDesc() { GraphicsPipelineStateDesc desc{}; desc.depthStencilState.depthTestEnabled = true; desc.depthStencilState.depthCompareOp = CompareOp::LESS_OR_EQUAL; desc.depthStencilState.depthWriteEnabled = true; desc.depthStencilState.stencilTestEnabled = false; desc.depthStencilState.stencilFrontFace.failOp = StencilOp::KEEP; desc.depthStencilState.stencilFrontFace.passOp = StencilOp::KEEP; desc.depthStencilState.stencilFrontFace.depthFailOp = StencilOp::KEEP; desc.depthStencilState.stencilFrontFace.compareOp = CompareOp::ALWAYS; desc.depthStencilState.stencilBackFace = desc.depthStencilState.stencilFrontFace; desc.depthStencilState.stencilReadMask = desc.depthStencilState.stencilWriteMask = std::numeric_limits::max(); desc.depthStencilState.stencilReference = 0; desc.blendState.enabled = false; desc.blendState.srcColorBlendFactor = desc.blendState.srcAlphaBlendFactor = BlendFactor::ONE; desc.blendState.dstColorBlendFactor = desc.blendState.dstAlphaBlendFactor = BlendFactor::ZERO; desc.blendState.colorBlendOp = desc.blendState.alphaBlendOp = BlendOp::ADD; desc.blendState.constant = CColor(0.0f, 0.0f, 0.0f, 0.0f); desc.blendState.colorWriteMask = ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA; desc.rasterizationState.cullMode = CullMode::BACK; desc.rasterizationState.frontFace = FrontFace::COUNTER_CLOCKWISE; return desc; } -CompareOp ParseCompareOp(const CStr& str) -{ - // TODO: it might make sense to use upper case in XML for consistency. -#define CASE(NAME, VALUE) if (str == NAME) return CompareOp::VALUE - CASE("never", NEVER); - CASE("less", LESS); - CASE("equal", EQUAL); - CASE("lequal", LESS_OR_EQUAL); - CASE("greater", GREATER); - CASE("notequal", NOT_EQUAL); - CASE("gequal", GREATER_OR_EQUAL); - CASE("always", ALWAYS); -#undef CASE - debug_warn("Invalid compare op"); - return CompareOp::NEVER; -} - StencilOp ParseStencilOp(const CStr& str) { #define CASE(NAME) if (str == #NAME) return StencilOp::NAME CASE(KEEP); CASE(ZERO); CASE(REPLACE); CASE(INCREMENT_AND_CLAMP); CASE(DECREMENT_AND_CLAMP); CASE(INVERT); CASE(INCREMENT_AND_WRAP); CASE(DECREMENT_AND_WRAP); #undef CASE debug_warn("Invalid stencil op"); return StencilOp::KEEP; } BlendFactor ParseBlendFactor(const CStr& str) { // TODO: it might make sense to use upper case in XML for consistency. #define CASE(NAME, VALUE) if (str == NAME) return BlendFactor::VALUE CASE("zero", ZERO); CASE("one", ONE); CASE("src_color", SRC_COLOR); CASE("one_minus_src_color", ONE_MINUS_SRC_COLOR); CASE("dst_color", DST_COLOR); CASE("one_minus_dst_color", ONE_MINUS_DST_COLOR); CASE("src_alpha", SRC_ALPHA); CASE("one_minus_src_alpha", ONE_MINUS_SRC_ALPHA); CASE("dst_alpha", DST_ALPHA); CASE("one_minus_dst_alpha", ONE_MINUS_DST_ALPHA); CASE("constant_color", CONSTANT_COLOR); CASE("one_minus_constant_color", ONE_MINUS_CONSTANT_COLOR); CASE("constant_alpha", CONSTANT_ALPHA); CASE("one_minus_constant_alpha", ONE_MINUS_CONSTANT_ALPHA); CASE("src_alpha_saturate", SRC_ALPHA_SATURATE); CASE("src1_color", SRC1_COLOR); CASE("one_minus_src1_color", ONE_MINUS_SRC1_COLOR); CASE("src1_alpha", SRC1_ALPHA); CASE("one_minus_src1_alpha", ONE_MINUS_SRC1_ALPHA); #undef CASE debug_warn("Invalid blend factor"); return BlendFactor::ZERO; } BlendOp ParseBlendOp(const CStr& str) { #define CASE(NAME) if (str == #NAME) return BlendOp::NAME CASE(ADD); CASE(SUBTRACT); CASE(REVERSE_SUBTRACT); CASE(MIN); CASE(MAX); #undef CASE debug_warn("Invalid blend op"); return BlendOp::ADD; } CullMode ParseCullMode(const CStr& str) { if (str == "NONE") return CullMode::NONE; else if (str == "FRONT") return CullMode::FRONT; else if (str == "BACK") return CullMode::BACK; debug_warn("Invalid cull mode"); return CullMode::BACK; } FrontFace ParseFrontFace(const CStr& str) { if (str == "CLOCKWISE") return FrontFace::CLOCKWISE; else if (str == "COUNTER_CLOCKWISE") return FrontFace::COUNTER_CLOCKWISE; debug_warn("Invalid front face"); return FrontFace::COUNTER_CLOCKWISE; } } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/PipelineState.h =================================================================== --- ps/trunk/source/renderer/backend/PipelineState.h (revision 26484) +++ ps/trunk/source/renderer/backend/PipelineState.h (revision 26485) @@ -1,197 +1,176 @@ /* 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_PIPELINESTATE #define INCLUDED_RENDERER_BACKEND_PIPELINESTATE #include "graphics/Color.h" +#include "renderer/backend/CompareOp.h" class CStr; namespace Renderer { namespace Backend { -enum class CompareOp -{ - // Never passes the comparison. - NEVER, - // Passes if the source value is less than the destination value. - LESS, - // Passes if the source depth value is equal to the destination value. - EQUAL, - // Passes if the source depth value is less than or equal to the destination value. - LESS_OR_EQUAL, - // Passes if the source depth value is greater than the destination value. - GREATER, - // Passes if the source depth value is not equal to the destination value. - NOT_EQUAL, - // Passes if the source depth value is greater than or equal to the destination value. - GREATER_OR_EQUAL, - // Always passes the comparison. - ALWAYS -}; - enum class StencilOp { // Keeps the current value. KEEP, // Sets the value to zero. ZERO, // Sets the value to reference. REPLACE, // Increments the value and clamps to the maximum representable unsigned // value. INCREMENT_AND_CLAMP, // Decrements the value and clamps to zero. DECREMENT_AND_CLAMP, // Bitwise inverts the value. INVERT, // Increments the value and wraps it to zero when incrementing the maximum // representable unsigned value. INCREMENT_AND_WRAP, // Decrements the value and wraps it to the maximum representable unsigned // value when decrementing zero. DECREMENT_AND_WRAP }; struct StencilOpState { StencilOp failOp; StencilOp passOp; StencilOp depthFailOp; CompareOp compareOp; }; struct DepthStencilStateDesc { bool depthTestEnabled; CompareOp depthCompareOp; bool depthWriteEnabled; bool stencilTestEnabled; uint32_t stencilReadMask; uint32_t stencilWriteMask; uint32_t stencilReference; StencilOpState stencilFrontFace; StencilOpState stencilBackFace; }; // TODO: add per constant description. enum class BlendFactor { ZERO, ONE, SRC_COLOR, ONE_MINUS_SRC_COLOR, DST_COLOR, ONE_MINUS_DST_COLOR, SRC_ALPHA, ONE_MINUS_SRC_ALPHA, DST_ALPHA, ONE_MINUS_DST_ALPHA, CONSTANT_COLOR, ONE_MINUS_CONSTANT_COLOR, CONSTANT_ALPHA, ONE_MINUS_CONSTANT_ALPHA, SRC_ALPHA_SATURATE, SRC1_COLOR, ONE_MINUS_SRC1_COLOR, SRC1_ALPHA, ONE_MINUS_SRC1_ALPHA, }; enum class BlendOp { ADD, SUBTRACT, REVERSE_SUBTRACT, MIN, MAX }; // Using a namespace instead of a enum allows using the same syntax while // avoiding adding operator overrides and additional checks on casts. namespace ColorWriteMask { constexpr uint8_t RED = 0x01; constexpr uint8_t GREEN = 0x02; constexpr uint8_t BLUE = 0x04; constexpr uint8_t ALPHA = 0x08; } // namespace ColorWriteMask struct BlendStateDesc { bool enabled; BlendFactor srcColorBlendFactor; BlendFactor dstColorBlendFactor; BlendOp colorBlendOp; BlendFactor srcAlphaBlendFactor; BlendFactor dstAlphaBlendFactor; BlendOp alphaBlendOp; CColor constant; uint8_t colorWriteMask; }; enum class CullMode { NONE, FRONT, BACK }; enum class FrontFace { COUNTER_CLOCKWISE, CLOCKWISE }; struct RasterizationStateDesc { CullMode cullMode; FrontFace frontFace; }; // TODO: Add a shader program to the graphics pipeline state. struct GraphicsPipelineStateDesc { DepthStencilStateDesc depthStencilState; BlendStateDesc blendState; RasterizationStateDesc rasterizationState; }; // We don't provide additional helpers intentionally because all custom states // should be described with a related shader and should be switched together. GraphicsPipelineStateDesc MakeDefaultGraphicsPipelineStateDesc(); -CompareOp ParseCompareOp(const CStr& str); - StencilOp ParseStencilOp(const CStr& str); BlendFactor ParseBlendFactor(const CStr& str); BlendOp ParseBlendOp(const CStr& str); CullMode ParseCullMode(const CStr& str); FrontFace ParseFrontFace(const CStr& str); } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_PIPELINESTATE Index: ps/trunk/source/renderer/backend/Sampler.cpp =================================================================== --- ps/trunk/source/renderer/backend/Sampler.cpp (revision 26484) +++ ps/trunk/source/renderer/backend/Sampler.cpp (revision 26485) @@ -1,50 +1,51 @@ /* 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 "Sampler.h" namespace Renderer { namespace Backend { namespace Sampler { Desc MakeDefaultSampler(Filter filter, AddressMode addressMode) { Desc desc{}; desc.minFilter = filter; desc.magFilter = filter; desc.mipFilter = filter; desc.addressModeU = addressMode; desc.addressModeV = addressMode; desc.addressModeW = addressMode; desc.anisotropyEnabled = false; desc.mipLODBias = 0.0f; desc.borderColor = CColor(0.0f, 0.0f, 0.0f, 0.0f); + desc.compareEnabled = false; return desc; } } // namespace Sampler } // namespace Backend } // namespace Renderer Index: ps/trunk/source/renderer/backend/Sampler.h =================================================================== --- ps/trunk/source/renderer/backend/Sampler.h (revision 26484) +++ ps/trunk/source/renderer/backend/Sampler.h (revision 26485) @@ -1,71 +1,74 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_RENDERER_BACKEND_SAMPLER #define INCLUDED_RENDERER_BACKEND_SAMPLER #include "graphics/Color.h" +#include "renderer/backend/CompareOp.h" #include namespace Renderer { namespace Backend { namespace Sampler { enum class Filter { NEAREST, LINEAR }; enum class AddressMode { REPEAT, MIRRORED_REPEAT, CLAMP_TO_EDGE, CLAMP_TO_BORDER, }; struct Desc { Filter magFilter; Filter minFilter; Filter mipFilter; AddressMode addressModeU; AddressMode addressModeV; AddressMode addressModeW; float mipLODBias; bool anisotropyEnabled; float maxAnisotropy; // When some filter is CLAMP_TO_BORDER. CColor borderColor; + bool compareEnabled; + CompareOp compareOp; }; Desc MakeDefaultSampler(Filter filter, AddressMode addressMode); } // namespace Sampler } // namespace Backend } // namespace Renderer #endif // INCLUDED_RENDERER_BACKEND_SAMPLER Index: ps/trunk/source/renderer/backend/gl/Texture.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26484) +++ ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26485) @@ -1,284 +1,302 @@ /* 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: break; case Format::R8G8B8: internalFormat = GL_RGB; pixelFormat = GL_RGB; pixelType = GL_UNSIGNED_BYTE; break; case Format::A8: internalFormat = GL_ALPHA; pixelFormat = GL_ALPHA; pixelType = GL_UNSIGNED_BYTE; break; case Format::L8: 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: compressedFormat = true; break; } // 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) { 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