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