Merge 'remotes/trunk'
[0ad.git] / source / renderer / PostprocManager.cpp
blob90bcb59711bcf19c8eb6a76e8fd772c6c20ddfc9
1 /* Copyright (C) 2024 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "renderer/PostprocManager.h"
22 #include "graphics/GameView.h"
23 #include "graphics/LightEnv.h"
24 #include "graphics/ShaderManager.h"
25 #include "lib/bits.h"
26 #include "maths/MathUtil.h"
27 #include "ps/ConfigDB.h"
28 #include "ps/CLogger.h"
29 #include "ps/CStrInternStatic.h"
30 #include "ps/Filesystem.h"
31 #include "ps/Game.h"
32 #include "ps/World.h"
33 #include "renderer/backend/IDevice.h"
34 #include "renderer/Renderer.h"
35 #include "renderer/RenderingOptions.h"
36 #include "tools/atlas/GameInterface/GameLoop.h"
38 #include <string_view>
40 namespace
43 void DrawFullscreenQuad(
44 Renderer::Backend::IVertexInputLayout* vertexInputLayout,
45 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
47 float quadVerts[] =
49 1.0f, 1.0f,
50 -1.0f, 1.0f,
51 -1.0f, -1.0f,
53 -1.0f, -1.0f,
54 1.0f, -1.0f,
55 1.0f, 1.0f
57 const bool flip =
58 deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN;
59 const float bottomV = flip ? 1.0 : 0.0f;
60 const float topV = flip ? 0.0f : 1.0f;
61 float quadTex[] =
63 1.0f, topV,
64 0.0f, topV,
65 0.0f, bottomV,
67 0.0f, bottomV,
68 1.0f, bottomV,
69 1.0f, topV
72 deviceCommandContext->SetVertexInputLayout(vertexInputLayout);
74 deviceCommandContext->SetVertexBufferData(
75 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
76 deviceCommandContext->SetVertexBufferData(
77 1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
79 deviceCommandContext->Draw(0, 6);
82 } // anonymous namespace
84 CPostprocManager::CPostprocManager(Renderer::Backend::IDevice* device)
85 : m_Device(device), m_IsInitialized(false), m_PostProcEffect(L"default"),
86 m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false),
87 m_MultisampleCount(0)
91 CPostprocManager::~CPostprocManager()
93 Cleanup();
96 bool CPostprocManager::IsEnabled() const
98 const bool isDepthStencilFormatPresent =
99 m_Device->GetPreferredDepthStencilFormat(
100 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT, true, true)
101 != Renderer::Backend::Format::UNDEFINED;
102 return
103 g_RenderingOptions.GetPostProc() &&
104 m_Device->GetBackend() != Renderer::Backend::Backend::GL_ARB &&
105 isDepthStencilFormatPresent;
108 void CPostprocManager::Cleanup()
110 if (!m_IsInitialized) // Only cleanup if previously used
111 return;
113 m_CaptureFramebuffer.reset();
115 m_PingFramebuffer.reset();
116 m_PongFramebuffer.reset();
118 m_ColorTex1.reset();
119 m_ColorTex2.reset();
120 m_DepthTex.reset();
122 for (BlurScale& scale : m_BlurScales)
124 for (BlurScale::Step& step : scale.steps)
126 step.framebuffer.reset();
127 step.texture.reset();
132 void CPostprocManager::Initialize()
134 if (m_IsInitialized)
135 return;
137 const std::array<Renderer::Backend::SVertexAttributeFormat, 2> attributes{{
138 {Renderer::Backend::VertexAttributeStream::POSITION,
139 Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
140 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
141 {Renderer::Backend::VertexAttributeStream::UV0,
142 Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
143 Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1},
145 m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
147 const uint32_t maxSamples = m_Device->GetCapabilities().maxSampleCount;
148 const uint32_t possibleSampleCounts[] = {2, 4, 8, 16};
149 std::copy_if(
150 std::begin(possibleSampleCounts), std::end(possibleSampleCounts),
151 std::back_inserter(m_AllowedSampleCounts),
152 [maxSamples](const uint32_t sampleCount) { return sampleCount <= maxSamples; } );
154 // The screen size starts out correct and then must be updated with Resize()
155 RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
157 RecreateBuffers();
158 m_IsInitialized = true;
160 // Once we have initialised the buffers, we can update the techniques.
161 UpdateAntiAliasingTechnique();
162 UpdateSharpeningTechnique();
163 UpdateSharpnessFactor();
164 CStr upscaleName;
165 CFG_GET_VAL("renderer.upscale.technique", upscaleName);
166 SetUpscaleTechnique(upscaleName);
168 // This might happen after the map is loaded and the effect chosen
169 SetPostEffect(m_PostProcEffect);
171 if (m_Device->GetCapabilities().computeShaders)
172 m_DownscaleComputeTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("compute_downscale"));
175 void CPostprocManager::Resize()
177 RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
179 // If the buffers were intialized, recreate them to the new size.
180 if (m_IsInitialized)
181 RecreateBuffers();
184 void CPostprocManager::RecreateBuffers()
186 Cleanup();
188 #define GEN_BUFFER_RGBA(name, w, h) \
189 name = m_Device->CreateTexture2D( \
190 "PostProc" #name, \
191 Renderer::Backend::ITexture::Usage::SAMPLED | \
192 Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT | \
193 Renderer::Backend::ITexture::Usage::TRANSFER_SRC | \
194 Renderer::Backend::ITexture::Usage::TRANSFER_DST, \
195 Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \
196 Renderer::Backend::Sampler::MakeDefaultSampler( \
197 Renderer::Backend::Sampler::Filter::LINEAR, \
198 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
200 // Two fullscreen ping-pong textures.
201 GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height);
202 GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height);
204 if (m_UnscaledWidth != m_Width && m_Device->GetCapabilities().computeShaders)
206 const uint32_t usage =
207 Renderer::Backend::ITexture::Usage::TRANSFER_SRC |
208 Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT |
209 Renderer::Backend::ITexture::Usage::SAMPLED |
210 Renderer::Backend::ITexture::Usage::STORAGE;
211 m_UnscaledTexture1 = m_Device->CreateTexture2D(
212 "PostProcUnscaledTexture1", usage,
213 Renderer::Backend::Format::R8G8B8A8_UNORM,
214 m_UnscaledWidth, m_UnscaledHeight,
215 Renderer::Backend::Sampler::MakeDefaultSampler(
216 Renderer::Backend::Sampler::Filter::LINEAR,
217 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
219 m_UnscaledTexture2 = m_Device->CreateTexture2D(
220 "PostProcUnscaledTexture2", usage,
221 Renderer::Backend::Format::R8G8B8A8_UNORM, m_UnscaledWidth, m_UnscaledHeight,
222 Renderer::Backend::Sampler::MakeDefaultSampler(
223 Renderer::Backend::Sampler::Filter::LINEAR,
224 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
226 Renderer::Backend::SColorAttachment colorAttachment{};
227 colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
228 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
229 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
231 colorAttachment.texture = m_UnscaledTexture1.get();
232 m_UnscaledFramebuffer1 = m_Device->CreateFramebuffer("PostprocUnscaledFramebuffer1",
233 &colorAttachment, nullptr);
235 colorAttachment.texture = m_UnscaledTexture2.get();
236 m_UnscaledFramebuffer2 = m_Device->CreateFramebuffer("PostprocUnscaledFramebuffer2",
237 &colorAttachment, nullptr);
240 // Textures for several blur sizes. It would be possible to reuse
241 // m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given
242 // that these are fairly small it's probably not worth complicating the coordinates passed
243 // to the blur helper functions.
244 uint32_t width = m_Width / 2, height = m_Height / 2;
245 for (BlurScale& scale : m_BlurScales)
247 for (BlurScale::Step& step : scale.steps)
249 GEN_BUFFER_RGBA(step.texture, width, height);
250 Renderer::Backend::SColorAttachment colorAttachment{};
251 colorAttachment.texture = step.texture.get();
252 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
253 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
254 colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
255 step.framebuffer = m_Device->CreateFramebuffer(
256 "BlurScaleStepFramebuffer", &colorAttachment, nullptr);
258 width = std::max(1u, width / 2);
259 height = std::max(1u, height / 2);
262 #undef GEN_BUFFER_RGBA
264 // Allocate the Depth/Stencil texture.
265 m_DepthTex = m_Device->CreateTexture2D("PostProcDepthTexture",
266 Renderer::Backend::ITexture::Usage::SAMPLED |
267 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
268 m_Device->GetPreferredDepthStencilFormat(
269 Renderer::Backend::ITexture::Usage::SAMPLED |
270 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
271 true, true),
272 m_Width, m_Height,
273 Renderer::Backend::Sampler::MakeDefaultSampler(
274 Renderer::Backend::Sampler::Filter::LINEAR,
275 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
277 // Set up the framebuffers with some initial textures.
278 Renderer::Backend::SColorAttachment colorAttachment{};
279 colorAttachment.texture = m_ColorTex1.get();
280 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
281 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
282 colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
284 Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
285 depthStencilAttachment.texture = m_DepthTex.get();
286 depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
287 depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
289 m_CaptureFramebuffer = m_Device->CreateFramebuffer("PostprocCaptureFramebuffer",
290 &colorAttachment, &depthStencilAttachment);
292 colorAttachment.texture = m_ColorTex1.get();
293 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
294 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
295 m_PingFramebuffer = m_Device->CreateFramebuffer("PostprocPingFramebuffer",
296 &colorAttachment, nullptr);
298 colorAttachment.texture = m_ColorTex2.get();
299 m_PongFramebuffer = m_Device->CreateFramebuffer("PostprocPongFramebuffer",
300 &colorAttachment, nullptr);
302 if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer)
304 LOGWARNING("Failed to create postproc framebuffers");
305 g_RenderingOptions.SetPostProc(false);
308 if (m_UsingMultisampleBuffer)
310 DestroyMultisampleBuffer();
311 CreateMultisampleBuffer();
316 void CPostprocManager::ApplyBlurDownscale2x(
317 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
318 Renderer::Backend::IFramebuffer* framebuffer,
319 Renderer::Backend::ITexture* inTex, int inWidth, int inHeight)
321 deviceCommandContext->BeginFramebufferPass(framebuffer);
323 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
324 viewportRect.width = inWidth / 2;
325 viewportRect.height = inHeight / 2;
326 deviceCommandContext->SetViewports(1, &viewportRect);
328 // Get bloom shader with instructions to simply copy texels.
329 CShaderDefines defines;
330 defines.Add(str_BLOOM_NOP, str_1);
331 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines);
333 deviceCommandContext->SetGraphicsPipelineState(
334 tech->GetGraphicsPipelineState());
335 deviceCommandContext->BeginPass();
336 Renderer::Backend::IShaderProgram* shader = tech->GetShader();
338 deviceCommandContext->SetTexture(
339 shader->GetBindingSlot(str_renderedTex), inTex);
341 DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
343 deviceCommandContext->EndPass();
344 deviceCommandContext->EndFramebufferPass();
347 void CPostprocManager::ApplyBlurGauss(
348 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
349 Renderer::Backend::ITexture* inTex,
350 Renderer::Backend::ITexture* tempTex,
351 Renderer::Backend::IFramebuffer* tempFramebuffer,
352 Renderer::Backend::IFramebuffer* outFramebuffer,
353 int inWidth, int inHeight)
355 deviceCommandContext->BeginFramebufferPass(tempFramebuffer);
357 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
358 viewportRect.width = inWidth;
359 viewportRect.height = inHeight;
360 deviceCommandContext->SetViewports(1, &viewportRect);
362 // Get bloom shader, for a horizontal Gaussian blur pass.
363 CShaderDefines defines2;
364 defines2.Add(str_BLOOM_PASS_H, str_1);
365 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines2);
367 deviceCommandContext->SetGraphicsPipelineState(
368 tech->GetGraphicsPipelineState());
369 deviceCommandContext->BeginPass();
370 Renderer::Backend::IShaderProgram* shader = tech->GetShader();
371 deviceCommandContext->SetTexture(
372 shader->GetBindingSlot(str_renderedTex), inTex);
373 deviceCommandContext->SetUniform(
374 shader->GetBindingSlot(str_texSize), inWidth, inHeight);
376 DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
378 deviceCommandContext->EndPass();
379 deviceCommandContext->EndFramebufferPass();
381 deviceCommandContext->BeginFramebufferPass(outFramebuffer);
383 deviceCommandContext->SetViewports(1, &viewportRect);
385 // Get bloom shader, for a vertical Gaussian blur pass.
386 CShaderDefines defines3;
387 defines3.Add(str_BLOOM_PASS_V, str_1);
388 tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines3);
389 deviceCommandContext->SetGraphicsPipelineState(
390 tech->GetGraphicsPipelineState());
392 deviceCommandContext->BeginPass();
393 shader = tech->GetShader();
395 // Our input texture to the shader is the output of the horizontal pass.
396 deviceCommandContext->SetTexture(
397 shader->GetBindingSlot(str_renderedTex), tempTex);
398 deviceCommandContext->SetUniform(
399 shader->GetBindingSlot(str_texSize), inWidth, inHeight);
401 DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
403 deviceCommandContext->EndPass();
404 deviceCommandContext->EndFramebufferPass();
407 void CPostprocManager::ApplyBlur(
408 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
410 uint32_t width = m_Width, height = m_Height;
411 Renderer::Backend::ITexture* previousTexture =
412 (m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
414 for (BlurScale& scale : m_BlurScales)
416 ApplyBlurDownscale2x(deviceCommandContext, scale.steps[0].framebuffer.get(), previousTexture, width, height);
417 width /= 2;
418 height /= 2;
419 ApplyBlurGauss(deviceCommandContext, scale.steps[0].texture.get(),
420 scale.steps[1].texture.get(), scale.steps[1].framebuffer.get(),
421 scale.steps[0].framebuffer.get(), width, height);
425 Renderer::Backend::IFramebuffer* CPostprocManager::PrepareAndGetOutputFramebuffer()
427 ENSURE(m_IsInitialized);
429 // Leaves m_PingFramebuffer selected for rendering; m_WhichBuffer stays true at this point.
431 m_WhichBuffer = true;
433 return m_UsingMultisampleBuffer ? m_MultisampleFramebuffer.get() : m_CaptureFramebuffer.get();
436 void CPostprocManager::UpscaleTextureByCompute(
437 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
438 CShaderTechnique* shaderTechnique,
439 Renderer::Backend::ITexture* source,
440 Renderer::Backend::ITexture* destination)
442 Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
444 const std::array<float, 4> screenSize{{
445 static_cast<float>(m_Width), static_cast<float>(m_Height),
446 static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)}};
448 constexpr uint32_t threadGroupWorkRegionDim = 16;
449 const uint32_t dispatchGroupCountX = DivideRoundUp(m_UnscaledWidth, threadGroupWorkRegionDim);
450 const uint32_t dispatchGroupCountY = DivideRoundUp(m_UnscaledHeight, threadGroupWorkRegionDim);
452 deviceCommandContext->BeginComputePass();
453 deviceCommandContext->SetComputePipelineState(
454 shaderTechnique->GetComputePipelineState());
455 deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
456 deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
457 deviceCommandContext->SetStorageTexture(shaderProgram->GetBindingSlot(str_outTex), destination);
458 deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
459 deviceCommandContext->EndComputePass();
462 void CPostprocManager::UpscaleTextureByFullscreenQuad(
463 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
464 CShaderTechnique* shaderTechnique,
465 Renderer::Backend::ITexture* source,
466 Renderer::Backend::IFramebuffer* destination)
468 Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
470 const std::array<float, 4> screenSize{{
471 static_cast<float>(m_Width), static_cast<float>(m_Height),
472 static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)}};
474 deviceCommandContext->BeginFramebufferPass(destination);
476 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
477 viewportRect.width = destination->GetWidth();
478 viewportRect.height = destination->GetHeight();
479 deviceCommandContext->SetViewports(1, &viewportRect);
481 deviceCommandContext->SetGraphicsPipelineState(
482 shaderTechnique->GetGraphicsPipelineState());
483 deviceCommandContext->BeginPass();
485 deviceCommandContext->SetTexture(
486 shaderProgram->GetBindingSlot(str_inTex), source);
487 deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
489 DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
491 deviceCommandContext->EndPass();
492 deviceCommandContext->EndFramebufferPass();
495 void CPostprocManager::ApplySharpnessAfterScale(
496 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
497 CShaderTechnique* shaderTechnique,
498 Renderer::Backend::ITexture* source,
499 Renderer::Backend::ITexture* destination)
501 Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
503 // Recommended sharpness for RCAS.
504 constexpr float sharpness = 0.2f;
506 const std::array<float, 4> screenSize{ {
507 static_cast<float>(m_Width), static_cast<float>(m_Height),
508 static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)} };
510 constexpr uint32_t threadGroupWorkRegionDim = 16;
511 const uint32_t dispatchGroupCountX = DivideRoundUp(m_UnscaledWidth, threadGroupWorkRegionDim);
512 const uint32_t dispatchGroupCountY = DivideRoundUp(m_UnscaledHeight, threadGroupWorkRegionDim);
514 deviceCommandContext->BeginComputePass();
515 deviceCommandContext->SetComputePipelineState(
516 shaderTechnique->GetComputePipelineState());
517 deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_sharpness), sharpness);
518 deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
519 deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
520 deviceCommandContext->SetStorageTexture(
521 shaderProgram->GetBindingSlot(str_outTex), destination);
522 deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
523 deviceCommandContext->EndComputePass();
526 void CPostprocManager::DownscaleTextureByCompute(
527 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
528 CShaderTechnique* shaderTechnique,
529 Renderer::Backend::ITexture* source,
530 Renderer::Backend::ITexture* destination)
532 Renderer::Backend::IShaderProgram* shaderProgram = shaderTechnique->GetShader();
534 const std::array<float, 4> screenSize{{
535 static_cast<float>(m_Width), static_cast<float>(m_Height),
536 static_cast<float>(m_UnscaledWidth), static_cast<float>(m_UnscaledHeight)}};
538 constexpr uint32_t threadGroupWorkRegionDim = 8;
539 const uint32_t dispatchGroupCountX = DivideRoundUp(m_UnscaledWidth, threadGroupWorkRegionDim);
540 const uint32_t dispatchGroupCountY = DivideRoundUp(m_UnscaledHeight, threadGroupWorkRegionDim);
542 deviceCommandContext->BeginComputePass();
543 deviceCommandContext->SetComputePipelineState(
544 shaderTechnique->GetComputePipelineState());
545 deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_screenSize), screenSize);
546 deviceCommandContext->SetTexture(shaderProgram->GetBindingSlot(str_inTex), source);
547 deviceCommandContext->SetStorageTexture(shaderProgram->GetBindingSlot(str_outTex), destination);
548 deviceCommandContext->Dispatch(dispatchGroupCountX, dispatchGroupCountY, 1);
549 deviceCommandContext->EndComputePass();
552 void CPostprocManager::BlitOutputFramebuffer(
553 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
554 Renderer::Backend::IFramebuffer* destination)
556 ENSURE(m_IsInitialized);
558 GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer");
560 Renderer::Backend::ITexture* previousTexture =
561 (m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
563 if (ShouldUpscale())
565 if (m_UpscaleComputeTech)
567 Renderer::Backend::ITexture* unscaledTexture = m_RCASComputeTech ? m_UnscaledTexture1.get() : m_UnscaledTexture2.get();
568 UpscaleTextureByCompute(deviceCommandContext, m_UpscaleComputeTech.get(), previousTexture, unscaledTexture);
569 if (m_RCASComputeTech)
570 ApplySharpnessAfterScale(deviceCommandContext, m_RCASComputeTech.get(), m_UnscaledTexture1.get(), m_UnscaledTexture2.get());
572 Renderer::Backend::IDeviceCommandContext::Rect sourceRegion{}, destinationRegion{};
573 sourceRegion.width = m_UnscaledTexture2->GetWidth();
574 sourceRegion.height = m_UnscaledTexture2->GetHeight();
575 destinationRegion.width = destination->GetWidth();
576 destinationRegion.height = destination->GetHeight();
577 deviceCommandContext->BlitFramebuffer(
578 m_UnscaledFramebuffer2.get(), destination, sourceRegion, destinationRegion,
579 Renderer::Backend::Sampler::Filter::NEAREST);
581 else
583 UpscaleTextureByFullscreenQuad(deviceCommandContext, m_UpscaleTech.get(), previousTexture, destination);
586 else if (ShouldDownscale())
588 Renderer::Backend::IDeviceCommandContext::Rect sourceRegion{};
589 Renderer::Backend::Sampler::Filter samplerFilter{
590 Renderer::Backend::Sampler::Filter::NEAREST};
591 Renderer::Backend::IFramebuffer* source{nullptr};
593 if (m_DownscaleComputeTech)
595 DownscaleTextureByCompute(deviceCommandContext, m_DownscaleComputeTech.get(), previousTexture, m_UnscaledTexture1.get());
597 source = m_UnscaledFramebuffer1.get();
598 sourceRegion.width = m_UnscaledTexture1->GetWidth();
599 sourceRegion.height = m_UnscaledTexture1->GetHeight();
601 else
603 source = (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get();
604 sourceRegion.width = source->GetWidth();
605 sourceRegion.height = source->GetHeight();
606 samplerFilter = Renderer::Backend::Sampler::Filter::LINEAR;
609 Renderer::Backend::IDeviceCommandContext::Rect destinationRegion{};
610 destinationRegion.width = destination->GetWidth();
611 destinationRegion.height = destination->GetHeight();
612 deviceCommandContext->BlitFramebuffer(
613 source, destination, sourceRegion, destinationRegion, samplerFilter);
615 else
617 Renderer::Backend::IFramebuffer* source =
618 (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get();
620 // We blit to the backbuffer from the previous active buffer.
621 Renderer::Backend::IDeviceCommandContext::Rect region{};
622 region.width = std::min(source->GetWidth(), destination->GetWidth());
623 region.height = std::min(source->GetHeight(), destination->GetHeight());
624 deviceCommandContext->BlitFramebuffer(
625 source, destination, region, region,
626 Renderer::Backend::Sampler::Filter::NEAREST);
630 void CPostprocManager::ApplyEffect(
631 Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
632 const CShaderTechniquePtr& shaderTech, int pass)
634 // Select the other framebuffer for rendering.
635 Renderer::Backend::IFramebuffer* framebuffer =
636 (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get();
637 deviceCommandContext->BeginFramebufferPass(framebuffer);
639 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
640 viewportRect.width = framebuffer->GetWidth();
641 viewportRect.height = framebuffer->GetHeight();
642 deviceCommandContext->SetViewports(1, &viewportRect);
644 deviceCommandContext->SetGraphicsPipelineState(
645 shaderTech->GetGraphicsPipelineState(pass));
646 deviceCommandContext->BeginPass();
647 Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(pass);
649 // Use the textures from the current framebuffer as input to the shader.
650 // We also bind a bunch of other textures and parameters, but since
651 // this only happens once per frame the overhead is negligible.
652 deviceCommandContext->SetTexture(
653 shader->GetBindingSlot(str_renderedTex),
654 m_WhichBuffer ? m_ColorTex1.get() : m_ColorTex2.get());
655 deviceCommandContext->SetTexture(
656 shader->GetBindingSlot(str_depthTex), m_DepthTex.get());
658 deviceCommandContext->SetTexture(
659 shader->GetBindingSlot(str_blurTex2), m_BlurScales[0].steps[0].texture.get());
660 deviceCommandContext->SetTexture(
661 shader->GetBindingSlot(str_blurTex4), m_BlurScales[1].steps[0].texture.get());
662 deviceCommandContext->SetTexture(
663 shader->GetBindingSlot(str_blurTex8), m_BlurScales[2].steps[0].texture.get());
665 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), m_Width);
666 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_height), m_Height);
667 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zNear), m_NearPlane);
668 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zFar), m_FarPlane);
670 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_sharpness), m_Sharpness);
672 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_brightness), g_LightEnv.m_Brightness);
673 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_hdr), g_LightEnv.m_Contrast);
674 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation);
675 deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom);
677 DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
679 deviceCommandContext->EndPass();
680 deviceCommandContext->EndFramebufferPass();
682 m_WhichBuffer = !m_WhichBuffer;
685 void CPostprocManager::ApplyPostproc(
686 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
688 ENSURE(m_IsInitialized);
690 // Don't do anything if we are using the default effect and no AA.
691 const bool hasEffects = m_PostProcEffect != L"default";
692 const bool hasARB = m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB;
693 const bool hasAA = m_AATech && !hasARB;
694 const bool hasSharp = m_SharpTech && !hasARB;
695 if (!hasEffects && !hasAA && !hasSharp)
696 return;
698 GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc");
700 if (hasEffects)
702 // First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied!
703 // (This may need to change depending on future usage, however that will have a fps hit)
704 ApplyBlur(deviceCommandContext);
705 for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass)
706 ApplyEffect(deviceCommandContext, m_PostProcTech, pass);
709 if (hasAA)
711 for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass)
712 ApplyEffect(deviceCommandContext, m_AATech, pass);
715 if (hasSharp && !ShouldUpscale())
717 for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
718 ApplyEffect(deviceCommandContext, m_SharpTech, pass);
723 // Generate list of available effect-sets
724 std::vector<CStrW> CPostprocManager::GetPostEffects()
726 std::vector<CStrW> effects;
728 const VfsPath folder(L"shaders/effects/postproc/");
730 VfsPaths pathnames;
731 if (vfs::GetPathnames(g_VFS, folder, 0, pathnames) < 0)
732 LOGERROR("Error finding Post effects in '%s'", folder.string8());
734 for (const VfsPath& path : pathnames)
735 if (path.Extension() == L".xml")
736 effects.push_back(path.Basename().string());
738 // Add the default "null" effect to the list.
739 effects.push_back(L"default");
741 sort(effects.begin(), effects.end());
743 return effects;
746 void CPostprocManager::SetPostEffect(const CStrW& name)
748 if (m_IsInitialized)
750 if (name != L"default")
752 CStrW n = L"postproc/" + name;
753 m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8()));
757 m_PostProcEffect = name;
760 void CPostprocManager::UpdateAntiAliasingTechnique()
762 if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
763 return;
765 CStr newAAName;
766 CFG_GET_VAL("antialiasing", newAAName);
767 if (m_AAName == newAAName)
768 return;
769 m_AAName = newAAName;
770 m_AATech.reset();
772 if (m_UsingMultisampleBuffer)
774 m_UsingMultisampleBuffer = false;
775 DestroyMultisampleBuffer();
778 // We have to hardcode names in the engine, because anti-aliasing
779 // techinques strongly depend on the graphics pipeline.
780 // We might use enums in future though.
781 constexpr std::string_view msaaPrefix{"msaa"};
782 if (m_AAName == str_fxaa.string())
784 m_AATech = g_Renderer.GetShaderManager().LoadEffect(str_fxaa);
786 else if (m_AAName.size() > msaaPrefix.size() &&
787 std::string_view{m_AAName}.substr(0, msaaPrefix.size()) == msaaPrefix)
789 // We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas.
790 if (g_AtlasGameLoop && g_AtlasGameLoop->running)
791 return;
792 if (!m_Device->GetCapabilities().multisampling || m_AllowedSampleCounts.empty())
794 LOGWARNING("MSAA is unsupported.");
795 return;
797 std::stringstream ss(m_AAName.substr(msaaPrefix.size()));
798 ss >> m_MultisampleCount;
799 if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) ==
800 std::end(m_AllowedSampleCounts))
802 m_MultisampleCount = std::min(4u, m_Device->GetCapabilities().maxSampleCount);
803 LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str());
805 m_UsingMultisampleBuffer = true;
806 CreateMultisampleBuffer();
810 void CPostprocManager::UpdateSharpeningTechnique()
812 if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
813 return;
815 CStr newSharpName;
816 CFG_GET_VAL("sharpening", newSharpName);
817 if (m_SharpName == newSharpName)
818 return;
819 m_SharpName = newSharpName;
820 m_SharpTech.reset();
822 if (m_SharpName == "cas")
824 m_SharpTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(m_SharpName));
828 void CPostprocManager::UpdateSharpnessFactor()
830 CFG_GET_VAL("sharpness", m_Sharpness);
833 void CPostprocManager::SetUpscaleTechnique(const CStr& upscaleName)
835 m_UpscaleTech.reset();
836 m_UpscaleComputeTech.reset();
837 m_RCASComputeTech.reset();
838 if (m_Device->GetCapabilities().computeShaders && upscaleName == "fsr")
840 m_UpscaleComputeTech = g_Renderer.GetShaderManager().LoadEffect(str_compute_upscale_fsr);
841 m_RCASComputeTech = g_Renderer.GetShaderManager().LoadEffect(str_compute_rcas);
843 else if (upscaleName == "pixelated")
845 m_UpscaleTech = g_Renderer.GetShaderManager().LoadEffect(str_upscale_nearest);
847 else
849 m_UpscaleTech = g_Renderer.GetShaderManager().LoadEffect(str_upscale_bilinear);
853 void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane)
855 m_NearPlane = nearPlane;
856 m_FarPlane = farPlane;
859 void CPostprocManager::CreateMultisampleBuffer()
861 m_MultisampleColorTex = m_Device->CreateTexture("PostProcColorMS",
862 Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
863 Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT |
864 Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
865 Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height,
866 Renderer::Backend::Sampler::MakeDefaultSampler(
867 Renderer::Backend::Sampler::Filter::LINEAR,
868 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
870 // Allocate the Depth/Stencil texture.
871 m_MultisampleDepthTex = m_Device->CreateTexture("PostProcDepthMS",
872 Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
873 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT |
874 Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
875 m_Device->GetPreferredDepthStencilFormat(
876 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT |
877 Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
878 true, true),
879 m_Width, m_Height,
880 Renderer::Backend::Sampler::MakeDefaultSampler(
881 Renderer::Backend::Sampler::Filter::LINEAR,
882 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
884 // Set up the framebuffers with some initial textures.
885 Renderer::Backend::SColorAttachment colorAttachment{};
886 colorAttachment.texture = m_MultisampleColorTex.get();
887 colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
888 colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
889 colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
891 Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
892 depthStencilAttachment.texture = m_MultisampleDepthTex.get();
893 depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
894 depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
896 m_MultisampleFramebuffer = m_Device->CreateFramebuffer(
897 "PostprocMultisampleFramebuffer", &colorAttachment, &depthStencilAttachment);
899 if (!m_MultisampleFramebuffer)
901 LOGERROR("Failed to create postproc multisample framebuffer");
902 m_UsingMultisampleBuffer = false;
903 DestroyMultisampleBuffer();
907 void CPostprocManager::DestroyMultisampleBuffer()
909 if (m_UsingMultisampleBuffer)
910 return;
911 m_MultisampleFramebuffer.reset();
912 m_MultisampleColorTex.reset();
913 m_MultisampleDepthTex.reset();
916 bool CPostprocManager::IsMultisampleEnabled() const
918 return m_UsingMultisampleBuffer;
921 void CPostprocManager::ResolveMultisampleFramebuffer(
922 Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
924 if (!m_UsingMultisampleBuffer)
925 return;
927 GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample");
928 deviceCommandContext->ResolveFramebuffer(
929 m_MultisampleFramebuffer.get(), m_PingFramebuffer.get());
932 void CPostprocManager::RecalculateSize(const uint32_t width, const uint32_t height)
934 if (m_Device->GetBackend() == Renderer::Backend::Backend::GL_ARB)
936 m_Scale = 1.0f;
937 return;
939 CFG_GET_VAL("renderer.scale", m_Scale);
940 if (m_Scale < 0.25f || m_Scale > 2.0f)
942 LOGWARNING("Invalid renderer scale: %0.2f", m_Scale);
943 m_Scale = 1.0f;
945 m_UnscaledWidth = width;
946 m_UnscaledHeight = height;
947 m_Width = m_UnscaledWidth * m_Scale;
948 m_Height = m_UnscaledHeight * m_Scale;
951 bool CPostprocManager::ShouldUpscale() const
953 return m_Width < m_UnscaledWidth;
956 bool CPostprocManager::ShouldDownscale() const
958 return m_Width > m_UnscaledWidth;