Line data Source code
1 : /* Copyright (C) 2023 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
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.
8 : *
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.
13 : *
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/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "renderer/PostprocManager.h"
21 :
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/VideoMode.h"
33 : #include "ps/World.h"
34 : #include "renderer/backend/IDevice.h"
35 : #include "renderer/Renderer.h"
36 : #include "renderer/RenderingOptions.h"
37 : #include "tools/atlas/GameInterface/GameLoop.h"
38 :
39 : #include <string_view>
40 :
41 : namespace
42 : {
43 :
44 0 : void DrawFullscreenQuad(
45 : Renderer::Backend::IVertexInputLayout* vertexInputLayout,
46 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
47 : {
48 0 : float quadVerts[] =
49 : {
50 : 1.0f, 1.0f,
51 : -1.0f, 1.0f,
52 : -1.0f, -1.0f,
53 :
54 : -1.0f, -1.0f,
55 : 1.0f, -1.0f,
56 : 1.0f, 1.0f
57 : };
58 : const bool flip =
59 0 : deviceCommandContext->GetDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN;
60 0 : const float bottomV = flip ? 1.0 : 0.0f;
61 0 : const float topV = flip ? 0.0f : 1.0f;
62 0 : float quadTex[] =
63 : {
64 : 1.0f, topV,
65 : 0.0f, topV,
66 : 0.0f, bottomV,
67 :
68 : 0.0f, bottomV,
69 : 1.0f, bottomV,
70 : 1.0f, topV
71 0 : };
72 :
73 0 : deviceCommandContext->SetVertexInputLayout(vertexInputLayout);
74 :
75 0 : deviceCommandContext->SetVertexBufferData(
76 0 : 0, quadVerts, std::size(quadVerts) * sizeof(quadVerts[0]));
77 0 : deviceCommandContext->SetVertexBufferData(
78 0 : 1, quadTex, std::size(quadTex) * sizeof(quadTex[0]));
79 :
80 0 : deviceCommandContext->Draw(0, 6);
81 0 : }
82 :
83 : } // anonymous namespace
84 :
85 6 : CPostprocManager::CPostprocManager()
86 : : m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true),
87 6 : m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0)
88 : {
89 6 : }
90 :
91 12 : CPostprocManager::~CPostprocManager()
92 : {
93 6 : Cleanup();
94 6 : }
95 :
96 6 : bool CPostprocManager::IsEnabled() const
97 : {
98 6 : Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice();
99 : const bool isDepthStencilFormatPresent =
100 6 : device->GetPreferredDepthStencilFormat(
101 6 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT, true, true)
102 6 : != Renderer::Backend::Format::UNDEFINED;
103 : return
104 6 : g_RenderingOptions.GetPostProc() &&
105 6 : device->GetBackend() != Renderer::Backend::Backend::GL_ARB &&
106 6 : isDepthStencilFormatPresent;
107 : }
108 :
109 6 : void CPostprocManager::Cleanup()
110 : {
111 6 : if (!m_IsInitialized) // Only cleanup if previously used
112 6 : return;
113 :
114 0 : m_CaptureFramebuffer.reset();
115 :
116 0 : m_PingFramebuffer.reset();
117 0 : m_PongFramebuffer.reset();
118 :
119 0 : m_ColorTex1.reset();
120 0 : m_ColorTex2.reset();
121 0 : m_DepthTex.reset();
122 :
123 0 : for (BlurScale& scale : m_BlurScales)
124 : {
125 0 : for (BlurScale::Step& step : scale.steps)
126 : {
127 0 : step.framebuffer.reset();
128 0 : step.texture.reset();
129 : }
130 : }
131 : }
132 :
133 0 : void CPostprocManager::Initialize()
134 : {
135 0 : if (m_IsInitialized)
136 0 : return;
137 :
138 0 : const std::array<Renderer::Backend::SVertexAttributeFormat, 2> attributes{{
139 : {Renderer::Backend::VertexAttributeStream::POSITION,
140 : Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
141 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
142 : {Renderer::Backend::VertexAttributeStream::UV0,
143 : Renderer::Backend::Format::R32G32_SFLOAT, 0, sizeof(float) * 2,
144 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1},
145 : }};
146 0 : m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
147 :
148 0 : const uint32_t maxSamples = g_VideoMode.GetBackendDevice()->GetCapabilities().maxSampleCount;
149 0 : const uint32_t possibleSampleCounts[] = {2, 4, 8, 16};
150 0 : std::copy_if(
151 : std::begin(possibleSampleCounts), std::end(possibleSampleCounts),
152 : std::back_inserter(m_AllowedSampleCounts),
153 0 : [maxSamples](const uint32_t sampleCount) { return sampleCount <= maxSamples; } );
154 :
155 : // The screen size starts out correct and then must be updated with Resize()
156 0 : m_Width = g_Renderer.GetWidth();
157 0 : m_Height = g_Renderer.GetHeight();
158 :
159 0 : RecreateBuffers();
160 0 : m_IsInitialized = true;
161 :
162 : // Once we have initialised the buffers, we can update the techniques.
163 0 : UpdateAntiAliasingTechnique();
164 0 : UpdateSharpeningTechnique();
165 0 : UpdateSharpnessFactor();
166 :
167 : // This might happen after the map is loaded and the effect chosen
168 0 : SetPostEffect(m_PostProcEffect);
169 : }
170 :
171 0 : void CPostprocManager::Resize()
172 : {
173 0 : m_Width = g_Renderer.GetWidth();
174 0 : m_Height = g_Renderer.GetHeight();
175 :
176 : // If the buffers were intialized, recreate them to the new size.
177 0 : if (m_IsInitialized)
178 0 : RecreateBuffers();
179 0 : }
180 :
181 0 : void CPostprocManager::RecreateBuffers()
182 : {
183 0 : Cleanup();
184 :
185 0 : Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
186 :
187 : #define GEN_BUFFER_RGBA(name, w, h) \
188 : name = backendDevice->CreateTexture2D( \
189 : "PostProc" #name, \
190 : Renderer::Backend::ITexture::Usage::SAMPLED | \
191 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT | \
192 : Renderer::Backend::ITexture::Usage::TRANSFER_SRC | \
193 : Renderer::Backend::ITexture::Usage::TRANSFER_DST, \
194 : Renderer::Backend::Format::R8G8B8A8_UNORM, w, h, \
195 : Renderer::Backend::Sampler::MakeDefaultSampler( \
196 : Renderer::Backend::Sampler::Filter::LINEAR, \
197 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
198 :
199 : // Two fullscreen ping-pong textures.
200 0 : GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height);
201 0 : GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height);
202 :
203 : // Textures for several blur sizes. It would be possible to reuse
204 : // m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given
205 : // that these are fairly small it's probably not worth complicating the coordinates passed
206 : // to the blur helper functions.
207 0 : uint32_t width = m_Width / 2, height = m_Height / 2;
208 0 : for (BlurScale& scale : m_BlurScales)
209 : {
210 0 : for (BlurScale::Step& step : scale.steps)
211 : {
212 0 : GEN_BUFFER_RGBA(step.texture, width, height);
213 0 : Renderer::Backend::SColorAttachment colorAttachment{};
214 0 : colorAttachment.texture = step.texture.get();
215 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
216 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
217 0 : colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
218 0 : step.framebuffer = backendDevice->CreateFramebuffer(
219 0 : "BlurScaleSteoFramebuffer", &colorAttachment, nullptr);
220 : }
221 0 : width /= 2;
222 0 : height /= 2;
223 : }
224 :
225 : #undef GEN_BUFFER_RGBA
226 :
227 : // Allocate the Depth/Stencil texture.
228 0 : m_DepthTex = backendDevice->CreateTexture2D("PostProcDepthTexture",
229 : Renderer::Backend::ITexture::Usage::SAMPLED |
230 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
231 : backendDevice->GetPreferredDepthStencilFormat(
232 : Renderer::Backend::ITexture::Usage::SAMPLED |
233 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
234 0 : true, true),
235 0 : m_Width, m_Height,
236 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
237 : Renderer::Backend::Sampler::Filter::LINEAR,
238 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
239 :
240 : // Set up the framebuffers with some initial textures.
241 0 : Renderer::Backend::SColorAttachment colorAttachment{};
242 0 : colorAttachment.texture = m_ColorTex1.get();
243 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
244 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
245 0 : colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
246 :
247 0 : Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
248 0 : depthStencilAttachment.texture = m_DepthTex.get();
249 0 : depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
250 0 : depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
251 :
252 0 : m_CaptureFramebuffer = backendDevice->CreateFramebuffer("PostprocCaptureFramebuffer",
253 0 : &colorAttachment, &depthStencilAttachment);
254 :
255 0 : colorAttachment.texture = m_ColorTex1.get();
256 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
257 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
258 0 : m_PingFramebuffer = backendDevice->CreateFramebuffer("PostprocPingFramebuffer",
259 0 : &colorAttachment, nullptr);
260 :
261 0 : colorAttachment.texture = m_ColorTex2.get();
262 0 : m_PongFramebuffer = backendDevice->CreateFramebuffer("PostprocPongFramebuffer",
263 0 : &colorAttachment, nullptr);
264 :
265 0 : if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer)
266 : {
267 0 : LOGWARNING("Failed to create postproc framebuffers");
268 0 : g_RenderingOptions.SetPostProc(false);
269 : }
270 :
271 0 : if (m_UsingMultisampleBuffer)
272 : {
273 0 : DestroyMultisampleBuffer();
274 0 : CreateMultisampleBuffer();
275 : }
276 0 : }
277 :
278 :
279 0 : void CPostprocManager::ApplyBlurDownscale2x(
280 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
281 : Renderer::Backend::IFramebuffer* framebuffer,
282 : Renderer::Backend::ITexture* inTex, int inWidth, int inHeight)
283 : {
284 0 : deviceCommandContext->BeginFramebufferPass(framebuffer);
285 :
286 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
287 0 : viewportRect.width = inWidth / 2;
288 0 : viewportRect.height = inHeight / 2;
289 0 : deviceCommandContext->SetViewports(1, &viewportRect);
290 :
291 : // Get bloom shader with instructions to simply copy texels.
292 0 : CShaderDefines defines;
293 0 : defines.Add(str_BLOOM_NOP, str_1);
294 0 : CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines);
295 :
296 0 : deviceCommandContext->SetGraphicsPipelineState(
297 0 : tech->GetGraphicsPipelineState());
298 0 : deviceCommandContext->BeginPass();
299 0 : Renderer::Backend::IShaderProgram* shader = tech->GetShader();
300 :
301 0 : deviceCommandContext->SetTexture(
302 0 : shader->GetBindingSlot(str_renderedTex), inTex);
303 :
304 0 : DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
305 :
306 0 : deviceCommandContext->EndPass();
307 0 : deviceCommandContext->EndFramebufferPass();
308 0 : }
309 :
310 0 : void CPostprocManager::ApplyBlurGauss(
311 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
312 : Renderer::Backend::ITexture* inTex,
313 : Renderer::Backend::ITexture* tempTex,
314 : Renderer::Backend::IFramebuffer* tempFramebuffer,
315 : Renderer::Backend::IFramebuffer* outFramebuffer,
316 : int inWidth, int inHeight)
317 : {
318 0 : deviceCommandContext->BeginFramebufferPass(tempFramebuffer);
319 :
320 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
321 0 : viewportRect.width = inWidth;
322 0 : viewportRect.height = inHeight;
323 0 : deviceCommandContext->SetViewports(1, &viewportRect);
324 :
325 : // Get bloom shader, for a horizontal Gaussian blur pass.
326 0 : CShaderDefines defines2;
327 0 : defines2.Add(str_BLOOM_PASS_H, str_1);
328 0 : CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines2);
329 :
330 0 : deviceCommandContext->SetGraphicsPipelineState(
331 0 : tech->GetGraphicsPipelineState());
332 0 : deviceCommandContext->BeginPass();
333 0 : Renderer::Backend::IShaderProgram* shader = tech->GetShader();
334 0 : deviceCommandContext->SetTexture(
335 0 : shader->GetBindingSlot(str_renderedTex), inTex);
336 0 : deviceCommandContext->SetUniform(
337 0 : shader->GetBindingSlot(str_texSize), inWidth, inHeight);
338 :
339 0 : DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
340 :
341 0 : deviceCommandContext->EndPass();
342 0 : deviceCommandContext->EndFramebufferPass();
343 :
344 0 : deviceCommandContext->BeginFramebufferPass(outFramebuffer);
345 :
346 0 : deviceCommandContext->SetViewports(1, &viewportRect);
347 :
348 : // Get bloom shader, for a vertical Gaussian blur pass.
349 0 : CShaderDefines defines3;
350 0 : defines3.Add(str_BLOOM_PASS_V, str_1);
351 0 : tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines3);
352 0 : deviceCommandContext->SetGraphicsPipelineState(
353 0 : tech->GetGraphicsPipelineState());
354 :
355 0 : deviceCommandContext->BeginPass();
356 0 : shader = tech->GetShader();
357 :
358 : // Our input texture to the shader is the output of the horizontal pass.
359 0 : deviceCommandContext->SetTexture(
360 0 : shader->GetBindingSlot(str_renderedTex), tempTex);
361 0 : deviceCommandContext->SetUniform(
362 0 : shader->GetBindingSlot(str_texSize), inWidth, inHeight);
363 :
364 0 : DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
365 :
366 0 : deviceCommandContext->EndPass();
367 0 : deviceCommandContext->EndFramebufferPass();
368 0 : }
369 :
370 0 : void CPostprocManager::ApplyBlur(
371 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
372 : {
373 0 : uint32_t width = m_Width, height = m_Height;
374 : Renderer::Backend::ITexture* previousTexture =
375 0 : (m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
376 :
377 0 : for (BlurScale& scale : m_BlurScales)
378 : {
379 0 : ApplyBlurDownscale2x(deviceCommandContext, scale.steps[0].framebuffer.get(), previousTexture, width, height);
380 0 : width /= 2;
381 0 : height /= 2;
382 0 : ApplyBlurGauss(deviceCommandContext, scale.steps[0].texture.get(),
383 0 : scale.steps[1].texture.get(), scale.steps[1].framebuffer.get(),
384 0 : scale.steps[0].framebuffer.get(), width, height);
385 : }
386 0 : }
387 :
388 0 : Renderer::Backend::IFramebuffer* CPostprocManager::PrepareAndGetOutputFramebuffer()
389 : {
390 0 : ENSURE(m_IsInitialized);
391 :
392 : // Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point.
393 :
394 0 : m_WhichBuffer = true;
395 :
396 0 : return m_UsingMultisampleBuffer ? m_MultisampleFramebuffer.get() : m_CaptureFramebuffer.get();
397 : }
398 :
399 0 : void CPostprocManager::BlitOutputFramebuffer(
400 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
401 : Renderer::Backend::IFramebuffer* destination)
402 : {
403 0 : ENSURE(m_IsInitialized);
404 :
405 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer");
406 :
407 : // We blit to the backbuffer from the previous active buffer.
408 0 : deviceCommandContext->BlitFramebuffer(
409 0 : destination, (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get());
410 0 : }
411 :
412 0 : void CPostprocManager::ApplyEffect(
413 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
414 : const CShaderTechniquePtr& shaderTech, int pass)
415 : {
416 : // select the other FBO for rendering
417 : Renderer::Backend::IFramebuffer* framebuffer =
418 0 : (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get();
419 0 : deviceCommandContext->BeginFramebufferPass(framebuffer);
420 :
421 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
422 0 : viewportRect.width = framebuffer->GetWidth();
423 0 : viewportRect.height = framebuffer->GetHeight();
424 0 : deviceCommandContext->SetViewports(1, &viewportRect);
425 :
426 0 : deviceCommandContext->SetGraphicsPipelineState(
427 0 : shaderTech->GetGraphicsPipelineState(pass));
428 0 : deviceCommandContext->BeginPass();
429 0 : Renderer::Backend::IShaderProgram* shader = shaderTech->GetShader(pass);
430 :
431 : // Use the textures from the current FBO as input to the shader.
432 : // We also bind a bunch of other textures and parameters, but since
433 : // this only happens once per frame the overhead is negligible.
434 0 : deviceCommandContext->SetTexture(
435 0 : shader->GetBindingSlot(str_renderedTex),
436 0 : m_WhichBuffer ? m_ColorTex1.get() : m_ColorTex2.get());
437 0 : deviceCommandContext->SetTexture(
438 0 : shader->GetBindingSlot(str_depthTex), m_DepthTex.get());
439 :
440 0 : deviceCommandContext->SetTexture(
441 0 : shader->GetBindingSlot(str_blurTex2), m_BlurScales[0].steps[0].texture.get());
442 0 : deviceCommandContext->SetTexture(
443 0 : shader->GetBindingSlot(str_blurTex4), m_BlurScales[1].steps[0].texture.get());
444 0 : deviceCommandContext->SetTexture(
445 0 : shader->GetBindingSlot(str_blurTex8), m_BlurScales[2].steps[0].texture.get());
446 :
447 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_width), m_Width);
448 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_height), m_Height);
449 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zNear), m_NearPlane);
450 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_zFar), m_FarPlane);
451 :
452 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_sharpness), m_Sharpness);
453 :
454 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_brightness), g_LightEnv.m_Brightness);
455 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_hdr), g_LightEnv.m_Contrast);
456 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_saturation), g_LightEnv.m_Saturation);
457 0 : deviceCommandContext->SetUniform(shader->GetBindingSlot(str_bloom), g_LightEnv.m_Bloom);
458 :
459 0 : DrawFullscreenQuad(m_VertexInputLayout, deviceCommandContext);
460 :
461 0 : deviceCommandContext->EndPass();
462 0 : deviceCommandContext->EndFramebufferPass();
463 :
464 0 : m_WhichBuffer = !m_WhichBuffer;
465 0 : }
466 :
467 0 : void CPostprocManager::ApplyPostproc(
468 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
469 : {
470 0 : ENSURE(m_IsInitialized);
471 :
472 : // Don't do anything if we are using the default effect and no AA.
473 0 : const bool hasEffects = m_PostProcEffect != L"default";
474 0 : const bool hasARB = g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB;
475 0 : const bool hasAA = m_AATech && !hasARB;
476 0 : const bool hasSharp = m_SharpTech && !hasARB;
477 0 : if (!hasEffects && !hasAA && !hasSharp)
478 0 : return;
479 :
480 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc");
481 :
482 0 : if (hasEffects)
483 : {
484 : // First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied!
485 : // (This may need to change depending on future usage, however that will have a fps hit)
486 0 : ApplyBlur(deviceCommandContext);
487 0 : for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass)
488 0 : ApplyEffect(deviceCommandContext, m_PostProcTech, pass);
489 : }
490 :
491 0 : if (hasAA)
492 : {
493 0 : for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass)
494 0 : ApplyEffect(deviceCommandContext, m_AATech, pass);
495 : }
496 :
497 0 : if (hasSharp)
498 : {
499 0 : for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
500 0 : ApplyEffect(deviceCommandContext, m_SharpTech, pass);
501 : }
502 : }
503 :
504 :
505 : // Generate list of available effect-sets
506 0 : std::vector<CStrW> CPostprocManager::GetPostEffects()
507 : {
508 0 : std::vector<CStrW> effects;
509 :
510 0 : const VfsPath folder(L"shaders/effects/postproc/");
511 :
512 0 : VfsPaths pathnames;
513 0 : if (vfs::GetPathnames(g_VFS, folder, 0, pathnames) < 0)
514 0 : LOGERROR("Error finding Post effects in '%s'", folder.string8());
515 :
516 0 : for (const VfsPath& path : pathnames)
517 0 : if (path.Extension() == L".xml")
518 0 : effects.push_back(path.Basename().string());
519 :
520 : // Add the default "null" effect to the list.
521 0 : effects.push_back(L"default");
522 :
523 0 : sort(effects.begin(), effects.end());
524 :
525 0 : return effects;
526 : }
527 :
528 0 : void CPostprocManager::SetPostEffect(const CStrW& name)
529 : {
530 0 : if (m_IsInitialized)
531 : {
532 0 : if (name != L"default")
533 : {
534 0 : CStrW n = L"postproc/" + name;
535 0 : m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8()));
536 : }
537 : }
538 :
539 0 : m_PostProcEffect = name;
540 0 : }
541 :
542 0 : void CPostprocManager::UpdateAntiAliasingTechnique()
543 : {
544 0 : Renderer::Backend::IDevice* device = g_VideoMode.GetBackendDevice();
545 0 : if (device->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
546 0 : return;
547 :
548 0 : CStr newAAName;
549 0 : CFG_GET_VAL("antialiasing", newAAName);
550 0 : if (m_AAName == newAAName)
551 0 : return;
552 0 : m_AAName = newAAName;
553 0 : m_AATech.reset();
554 :
555 0 : if (m_UsingMultisampleBuffer)
556 : {
557 0 : m_UsingMultisampleBuffer = false;
558 0 : DestroyMultisampleBuffer();
559 : }
560 :
561 : // We have to hardcode names in the engine, because anti-aliasing
562 : // techinques strongly depend on the graphics pipeline.
563 : // We might use enums in future though.
564 0 : constexpr std::string_view msaaPrefix{"msaa"};
565 0 : if (m_AAName == "fxaa")
566 : {
567 0 : m_AATech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("fxaa"));
568 : }
569 0 : else if (m_AAName.size() > msaaPrefix.size() &&
570 0 : std::string_view{m_AAName}.substr(0, msaaPrefix.size()) == msaaPrefix)
571 : {
572 : // We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas.
573 0 : if (g_AtlasGameLoop && g_AtlasGameLoop->running)
574 0 : return;
575 0 : if (!device->GetCapabilities().multisampling || m_AllowedSampleCounts.empty())
576 : {
577 0 : LOGWARNING("MSAA is unsupported.");
578 0 : return;
579 : }
580 0 : std::stringstream ss(m_AAName.substr(msaaPrefix.size()));
581 0 : ss >> m_MultisampleCount;
582 0 : if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) ==
583 0 : std::end(m_AllowedSampleCounts))
584 : {
585 0 : m_MultisampleCount = std::min(4u, device->GetCapabilities().maxSampleCount);
586 0 : LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str());
587 : }
588 0 : m_UsingMultisampleBuffer = true;
589 0 : CreateMultisampleBuffer();
590 : }
591 : }
592 :
593 0 : void CPostprocManager::UpdateSharpeningTechnique()
594 : {
595 0 : if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB || !m_IsInitialized)
596 0 : return;
597 :
598 0 : CStr newSharpName;
599 0 : CFG_GET_VAL("sharpening", newSharpName);
600 0 : if (m_SharpName == newSharpName)
601 0 : return;
602 0 : m_SharpName = newSharpName;
603 0 : m_SharpTech.reset();
604 :
605 0 : if (m_SharpName == "cas")
606 : {
607 0 : m_SharpTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(m_SharpName));
608 : }
609 : }
610 :
611 0 : void CPostprocManager::UpdateSharpnessFactor()
612 : {
613 0 : CFG_GET_VAL("sharpness", m_Sharpness);
614 0 : }
615 :
616 0 : void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane)
617 : {
618 0 : m_NearPlane = nearPlane;
619 0 : m_FarPlane = farPlane;
620 0 : }
621 :
622 0 : void CPostprocManager::CreateMultisampleBuffer()
623 : {
624 0 : Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
625 :
626 0 : m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS",
627 : Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
628 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT |
629 : Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
630 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, m_Width, m_Height,
631 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
632 : Renderer::Backend::Sampler::Filter::LINEAR,
633 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
634 :
635 : // Allocate the Depth/Stencil texture.
636 0 : m_MultisampleDepthTex = backendDevice->CreateTexture("PostProcDepthMS",
637 : Renderer::Backend::ITexture::Type::TEXTURE_2D_MULTISAMPLE,
638 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT |
639 : Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
640 : backendDevice->GetPreferredDepthStencilFormat(
641 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT |
642 : Renderer::Backend::ITexture::Usage::TRANSFER_SRC,
643 0 : true, true),
644 0 : m_Width, m_Height,
645 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
646 : Renderer::Backend::Sampler::Filter::LINEAR,
647 0 : Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
648 :
649 : // Set up the framebuffers with some initial textures.
650 0 : Renderer::Backend::SColorAttachment colorAttachment{};
651 0 : colorAttachment.texture = m_MultisampleColorTex.get();
652 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::DONT_CARE;
653 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
654 0 : colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
655 :
656 0 : Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
657 0 : depthStencilAttachment.texture = m_MultisampleDepthTex.get();
658 0 : depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
659 0 : depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
660 :
661 0 : m_MultisampleFramebuffer = backendDevice->CreateFramebuffer(
662 0 : "PostprocMultisampleFramebuffer", &colorAttachment, &depthStencilAttachment);
663 :
664 0 : if (!m_MultisampleFramebuffer)
665 : {
666 0 : LOGERROR("Failed to create postproc multisample framebuffer");
667 0 : m_UsingMultisampleBuffer = false;
668 0 : DestroyMultisampleBuffer();
669 : }
670 0 : }
671 :
672 0 : void CPostprocManager::DestroyMultisampleBuffer()
673 : {
674 0 : if (m_UsingMultisampleBuffer)
675 0 : return;
676 0 : m_MultisampleFramebuffer.reset();
677 0 : m_MultisampleColorTex.reset();
678 0 : m_MultisampleDepthTex.reset();
679 : }
680 :
681 0 : bool CPostprocManager::IsMultisampleEnabled() const
682 : {
683 0 : return m_UsingMultisampleBuffer;
684 : }
685 :
686 0 : void CPostprocManager::ResolveMultisampleFramebuffer(
687 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
688 : {
689 0 : if (!m_UsingMultisampleBuffer)
690 0 : return;
691 :
692 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample");
693 0 : deviceCommandContext->BlitFramebuffer(
694 0 : m_PingFramebuffer.get(), m_MultisampleFramebuffer.get());
695 3 : }
|