Adds PBR output texture to PostProc
Some checks failed
checkrefs / lfscheck (push) Has been cancelled
checkrefs / checkrefs (push) Has been cancelled
lint / cppcheck (push) Has been cancelled
lint / copyright (push) Has been cancelled
lint / jenkinsfiles (push) Has been cancelled
pre-commit / build (push) Has been cancelled

This commit is contained in:
Vladislav Belov 2026-04-29 19:25:14 +02:00
parent 25df2d8a02
commit a7967d4ad9
No known key found for this signature in database
GPG key ID: 353545E45DB9CCB3
8 changed files with 177 additions and 47 deletions

View file

@ -97,6 +97,7 @@ X(color)
X(colorAdd)
X(colorMul)
X(compute_rcas)
X(compute_resolve_pbr)
X(compute_skinning)
X(compute_upscale_fsr)
X(debug_line)
@ -104,6 +105,7 @@ X(debug_overlay)
X(delta)
X(depthTex)
X(dummy)
X(exposure)
X(foamTex)
X(fogColor)
X(fogParams)

View file

@ -128,6 +128,7 @@ void CPostprocManager::Cleanup()
m_PingFramebuffer.reset();
m_PongFramebuffer.reset();
m_PBROutputTexture.reset();
m_ColorTex1.reset();
m_ColorTex2.reset();
m_DepthTex.reset();
@ -147,6 +148,9 @@ void CPostprocManager::Initialize()
if (m_IsInitialized)
return;
if (g_ConfigDB.Get("pbr", false))
InitializePBR();
const std::array<Renderer::Backend::SVertexAttributeFormat, 2> attributes{{
{Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
@ -183,6 +187,40 @@ void CPostprocManager::Initialize()
m_DownscaleComputeTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("compute_downscale"));
}
void CPostprocManager::InitializePBR()
{
const bool PBRSupported{
m_Device->GetCapabilities().computeShaders &&
m_Device->IsFramebufferFormatSupported(Renderer::Backend::Format::R16G16B16A16_SFLOAT)};
if (!PBRSupported)
{
m_PBREnabled = false;
LOGWARNING("PBR is unsupported for the current renderer backend");
return;
}
m_FramebufferColorFormat = Renderer::Backend::Format::R16G16B16A16_SFLOAT;
const std::string framebufferFormatName{g_ConfigDB.Get("pbr.framebufferformat", std::string{})};
if (framebufferFormatName == "rgba32")
{
if (m_Device->IsFramebufferFormatSupported(Renderer::Backend::Format::R32G32B32A32_SFLOAT))
m_FramebufferColorFormat = Renderer::Backend::Format::R32G32B32A32_SFLOAT;
else
LOGWARNING("%s is unsupported", framebufferFormatName);
}
else if (framebufferFormatName == "r11g11b10")
{
if (m_Device->IsFramebufferFormatSupported(Renderer::Backend::Format::B10G11R11_UFLOAT))
m_FramebufferColorFormat = Renderer::Backend::Format::B10G11R11_UFLOAT;
else
LOGWARNING("%s is unsupported", framebufferFormatName);
}
m_ResolvePBRComputeTech = g_Renderer.GetShaderManager().LoadEffect(str_compute_resolve_pbr);
if (m_ResolvePBRComputeTech)
m_PBREnabled = true;
}
void CPostprocManager::Resize()
{
RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
@ -196,21 +234,33 @@ void CPostprocManager::RecreateBuffers()
{
Cleanup();
#define GEN_BUFFER_RGBA(name, w, h) \
name = m_Device->CreateTexture2D( \
"PostProc" #name, \
Renderer::Backend::ITexture::Usage::SAMPLED | \
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT | \
Renderer::Backend::ITexture::Usage::TRANSFER_SRC | \
Renderer::Backend::ITexture::Usage::TRANSFER_DST, \
Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \
Renderer::Backend::Sampler::MakeDefaultSampler( \
Renderer::Backend::Sampler::Filter::LINEAR, \
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
const auto defaultSampler{
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)};
const uint32_t colorTextureUsage{
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT |
Renderer::Backend::ITexture::Usage::TRANSFER_SRC |
Renderer::Backend::ITexture::Usage::TRANSFER_DST};
if (m_PBREnabled)
{
m_PBROutputTexture = m_Device->CreateTexture2D(
"PostProcPBROutput", colorTextureUsage,
m_FramebufferColorFormat,
m_Width, m_Height, defaultSampler);
}
// Two fullscreen ping-pong textures.
GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height);
GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height);
m_ColorTex1 = m_Device->CreateTexture2D(
"PostProcColor1", colorTextureUsage | (m_PBREnabled ? Renderer::Backend::ITexture::Usage::STORAGE : 0),
Renderer::Backend::Format::R8G8B8A8_UNORM,
m_Width, m_Height, defaultSampler);
m_ColorTex2 = m_Device->CreateTexture2D(
"PostProcColor2", colorTextureUsage | (m_PBREnabled ? Renderer::Backend::ITexture::Usage::STORAGE : 0),
Renderer::Backend::Format::R8G8B8A8_UNORM,
m_Width, m_Height, defaultSampler);
if (m_UnscaledWidth != m_Width && m_Device->GetCapabilities().computeShaders)
{
@ -257,7 +307,10 @@ void CPostprocManager::RecreateBuffers()
{
for (BlurScale::Step& step : scale.steps)
{
GEN_BUFFER_RGBA(step.texture, width, height);
step.texture = m_Device->CreateTexture2D(
"PostProcBlurStep", colorTextureUsage,
Renderer::Backend::Format::R8G8B8A8_UNORM,
width, height, defaultSampler);
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = step.texture.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
@ -270,8 +323,6 @@ void CPostprocManager::RecreateBuffers()
height = std::max(1u, height / 2);
}
#undef GEN_BUFFER_RGBA
// Allocate the Depth/Stencil texture.
m_DepthTex = m_Device->CreateTexture2D("PostProcDepthTexture",
Renderer::Backend::ITexture::Usage::SAMPLED |
@ -285,24 +336,14 @@ void CPostprocManager::RecreateBuffers()
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
RecreateCaptureFramebuffer();
// Set up the framebuffers with some initial textures.
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = m_ColorTex1.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
depthStencilAttachment.texture = m_DepthTex.get();
depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
m_CaptureFramebuffer = m_Device->CreateFramebuffer("PostprocCaptureFramebuffer",
&colorAttachment, &depthStencilAttachment);
colorAttachment.texture = m_ColorTex1.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
m_PingFramebuffer = m_Device->CreateFramebuffer("PostprocPingFramebuffer",
&colorAttachment, nullptr);
@ -310,7 +351,7 @@ void CPostprocManager::RecreateBuffers()
m_PongFramebuffer = m_Device->CreateFramebuffer("PostprocPongFramebuffer",
&colorAttachment, nullptr);
if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer)
if (!m_PingFramebuffer || !m_PongFramebuffer)
{
LOGWARNING("Failed to create postproc framebuffers");
g_RenderingOptions.SetPostProc(false);
@ -323,6 +364,28 @@ void CPostprocManager::RecreateBuffers()
}
}
void CPostprocManager::RecreateCaptureFramebuffer()
{
Renderer::Backend::SColorAttachment colorAttachment{};
colorAttachment.texture = m_PBREnabled ? m_PBROutputTexture.get() : m_ColorTex1.get();
colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
depthStencilAttachment.texture = m_DepthTex.get();
depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
m_CaptureFramebuffer = m_Device->CreateFramebuffer("PostprocCaptureFramebuffer",
&colorAttachment, m_UsingMultisampleBuffer ? nullptr : &depthStencilAttachment);
if (!m_CaptureFramebuffer)
{
LOGWARNING("Failed to create postproc capture framebuffer");
g_RenderingOptions.SetPostProc(false);
}
}
void CPostprocManager::ApplyBlurDownscale2x(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
@ -698,17 +761,15 @@ void CPostprocManager::ApplyPostproc(
{
ENSURE(m_IsInitialized);
// Don't do anything if we are using the default effect and no AA.
const bool hasEffects{m_PostProcEffect != L"default"};
const bool hasAA{m_AATech};
const bool hasSharp{m_SharpTech};
if (!hasEffects && !hasAA && !hasSharp)
return;
PROFILE3_GPU(deviceCommandContext, "Render postproc");
GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc");
if (hasEffects)
if (m_PBREnabled)
{
ResolvePBR(deviceCommandContext);
}
if (!m_PBREnabled && m_PostProcEffect != L"default")
{
// 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)
@ -717,13 +778,13 @@ void CPostprocManager::ApplyPostproc(
ApplyEffect(deviceCommandContext, m_PostProcTech, pass);
}
if (hasAA)
if (m_AATech)
{
for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_AATech, pass);
}
if (hasSharp && !ShouldUpscale())
if (m_SharpTech && !ShouldUpscale())
{
for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_SharpTech, pass);
@ -815,6 +876,8 @@ void CPostprocManager::UpdateAntiAliasingTechnique()
m_UsingMultisampleBuffer = true;
CreateMultisampleBuffer();
}
RecreateCaptureFramebuffer();
}
void CPostprocManager::UpdateSharpeningTechnique()
@ -871,7 +934,7 @@ void CPostprocManager::CreateMultisampleBuffer()
Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT |
Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height,
m_FramebufferColorFormat, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
@ -935,7 +998,42 @@ void CPostprocManager::ResolveMultisampleFramebuffer(
GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample");
deviceCommandContext->ResolveFramebuffer(
m_MultisampleFramebuffer.get(), m_PingFramebuffer.get());
m_MultisampleFramebuffer.get(),
m_PBREnabled ? m_CaptureFramebuffer.get() : m_PingFramebuffer.get());
}
void CPostprocManager::ResolvePBR(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
ENSURE(m_PBREnabled);
ENSURE(m_ResolvePBRComputeTech);
GPU_SCOPED_LABEL(deviceCommandContext, "Resolve PBR");
Renderer::Backend::IShaderProgram* shaderProgram{m_ResolvePBRComputeTech->GetShader()};
Renderer::Backend::ITexture* source{m_PBROutputTexture.get()};
Renderer::Backend::ITexture* destination{m_ColorTex1.get()};
const std::array<float, 4> screenSize{{
static_cast<float>(m_Width), static_cast<float>(m_Height),
1.0f / static_cast<float>(m_Width), 1.0f / static_cast<float>(m_Height)}};
constexpr uint32_t threadGroupWorkRegionDim{8};
const uint32_t dispatchGroupCountX{DivideRoundUp(m_Width, threadGroupWorkRegionDim)};
const uint32_t dispatchGroupCountY{DivideRoundUp(m_Height, threadGroupWorkRegionDim)};
deviceCommandContext->BeginComputePass();
deviceCommandContext->SetComputePipelineState(
m_ResolvePBRComputeTech->GetComputePipelineState());
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
// Mapping [0.0, 1.0] to [-2.0, 2.0].
deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_exposure),
std::exp2f((g_RenderingOptions.GetPBRBrightness() - 0.5f) * 4.0f));
deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
deviceCommandContext->SetStorageTexture(shaderProgram->GetBindingSlot(str_outTex), destination);
deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
deviceCommandContext->EndComputePass();
}
void CPostprocManager::RecalculateSize(const uint32_t width, const uint32_t height)

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -95,9 +95,16 @@ public:
float GetScale() const { return m_Scale; }
bool IsPBREnabled() const { return m_PBREnabled; }
Renderer::Backend::Format GetFramebufferColorFormat() const { return m_FramebufferColorFormat; }
private:
void CreateMultisampleBuffer();
void DestroyMultisampleBuffer();
void RecreateCaptureFramebuffer();
void InitializePBR();
void RecalculateSize(const uint32_t width, const uint32_t height);
@ -127,8 +134,20 @@ private:
Renderer::Backend::ITexture* source,
Renderer::Backend::ITexture* destination);
/**
* Resolve PBR HDR output to SDR one.
*/
void ResolvePBR(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext);
Renderer::Backend::IDevice* m_Device = nullptr;
Renderer::Backend::Format m_FramebufferColorFormat{
Renderer::Backend::Format::R8G8B8A8_UNORM};
bool m_PBREnabled{false};
std::unique_ptr<Renderer::Backend::ITexture> m_PBROutputTexture;
std::unique_ptr<Renderer::Backend::IFramebuffer> m_CaptureFramebuffer;
// Two framebuffers, that we flip between at each shader pass.
@ -170,6 +189,8 @@ private:
CStrW m_PostProcEffect;
CShaderTechniquePtr m_PostProcTech;
CShaderTechniquePtr m_ResolvePBRComputeTech;
CStr m_SharpName;
CShaderTechniquePtr m_SharpTech;
float m_Sharpness;

View file

@ -271,6 +271,8 @@ void CRenderingOptions::ReadConfigAndSetupHooks()
if (CRenderer::IsInitialised())
g_Renderer.GetTextureManager().OnQualityChanged();
});
m_ConfigHooks->Setup("pbr.brightness", m_PBRBrightness);
}
void CRenderingOptions::ClearHooks()

View file

@ -120,6 +120,8 @@ OPTION_CUSTOM_SETTER(NAME, TYPE); OPTION_GETTER(NAME, TYPE); OPTION_DEF(NAME, TY
OPTION(DisplayFrustum, bool);
OPTION(DisplayShadowsFrustum, bool);
OPTION(PBRBrightness, float);
// Cutscene Mode: while active, (most) visual overlays aren't rendered in order to give the scene a more "pure"
// and real feel, like a movie.
// The idea is for it to be inactive during normal gameplay, but enabled while playing cinema paths and

View file

@ -260,6 +260,11 @@ void WaterManager::RecreateOrLoadTexturesIfNeeded()
Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
true, false);
Renderer::Backend::Format framebufferColorFormat{
g_Renderer.GetPostprocManager().IsEnabled()
? g_Renderer.GetPostprocManager().GetFramebufferColorFormat()
: Renderer::Backend::Format::R8G8B8A8_UNORM};
// Create reflection textures.
const bool needsReflectionTextures =
g_RenderingOptions.GetWaterEffects() &&
@ -269,7 +274,7 @@ void WaterManager::RecreateOrLoadTexturesIfNeeded()
m_ReflectionTexture = m_Device->CreateTexture2D("WaterReflectionTexture",
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize,
framebufferColorFormat, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
@ -312,7 +317,7 @@ void WaterManager::RecreateOrLoadTexturesIfNeeded()
m_RefractionTexture = m_Device->CreateTexture2D("WaterRefractionTexture",
Renderer::Backend::ITexture::Usage::SAMPLED |
Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize,
framebufferColorFormat, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));

View file

@ -968,7 +968,7 @@ bool CDevice::IsTextureFormatSupported(const Format format) const
case Format::R16G16B16A16_SFLOAT:
case Format::R32G32B32A32_SFLOAT:
supported = m_Capabilities.computeShaders && m_Capabilities.storage && ogl_HaveExtension("GL_ARB_texture_float");
supported = m_Capabilities.computeShaders && m_Capabilities.storage && GLAD_GL_ARB_texture_float;
break;
default:

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify