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"
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"
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>
43 void DrawFullscreenQuad(
44 Renderer::Backend::IVertexInputLayout
* vertexInputLayout
,
45 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
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
;
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),
91 CPostprocManager::~CPostprocManager()
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
;
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
113 m_CaptureFramebuffer
.reset();
115 m_PingFramebuffer
.reset();
116 m_PongFramebuffer
.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()
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};
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());
158 m_IsInitialized
= true;
160 // Once we have initialised the buffers, we can update the techniques.
161 UpdateAntiAliasingTechnique();
162 UpdateSharpeningTechnique();
163 UpdateSharpnessFactor();
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.
184 void CPostprocManager::RecreateBuffers()
188 #define GEN_BUFFER_RGBA(name, w, h) \
189 name = m_Device->CreateTexture2D( \
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
,
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
);
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();
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
);
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();
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
);
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
)
698 GPU_SCOPED_LABEL(deviceCommandContext
, "Render postproc");
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
);
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/");
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());
746 void CPostprocManager::SetPostEffect(const CStrW
& name
)
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
)
766 CFG_GET_VAL("antialiasing", newAAName
);
767 if (m_AAName
== newAAName
)
769 m_AAName
= newAAName
;
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
)
792 if (!m_Device
->GetCapabilities().multisampling
|| m_AllowedSampleCounts
.empty())
794 LOGWARNING("MSAA is unsupported.");
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
)
816 CFG_GET_VAL("sharpening", newSharpName
);
817 if (m_SharpName
== newSharpName
)
819 m_SharpName
= newSharpName
;
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
);
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
,
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
)
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
)
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
)
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
);
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
;