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.h"
21 :
22 : #include "graphics/Canvas2D.h"
23 : #include "graphics/CinemaManager.h"
24 : #include "graphics/GameView.h"
25 : #include "graphics/LightEnv.h"
26 : #include "graphics/ModelDef.h"
27 : #include "graphics/TerrainTextureManager.h"
28 : #include "i18n/L10n.h"
29 : #include "lib/allocators/shared_ptr.h"
30 : #include "lib/hash.h"
31 : #include "lib/tex/tex.h"
32 : #include "gui/GUIManager.h"
33 : #include "ps/CConsole.h"
34 : #include "ps/CLogger.h"
35 : #include "ps/ConfigDB.h"
36 : #include "ps/CStrInternStatic.h"
37 : #include "ps/Game.h"
38 : #include "ps/GameSetup/Config.h"
39 : #include "ps/GameSetup/GameSetup.h"
40 : #include "ps/Globals.h"
41 : #include "ps/Loader.h"
42 : #include "ps/Profile.h"
43 : #include "ps/Filesystem.h"
44 : #include "ps/World.h"
45 : #include "ps/ProfileViewer.h"
46 : #include "graphics/Camera.h"
47 : #include "graphics/FontManager.h"
48 : #include "graphics/ShaderManager.h"
49 : #include "graphics/Terrain.h"
50 : #include "graphics/Texture.h"
51 : #include "graphics/TextureManager.h"
52 : #include "ps/Util.h"
53 : #include "ps/VideoMode.h"
54 : #include "renderer/backend/IDevice.h"
55 : #include "renderer/DebugRenderer.h"
56 : #include "renderer/PostprocManager.h"
57 : #include "renderer/RenderingOptions.h"
58 : #include "renderer/RenderModifiers.h"
59 : #include "renderer/SceneRenderer.h"
60 : #include "renderer/TimeManager.h"
61 : #include "renderer/VertexBufferManager.h"
62 : #include "tools/atlas/GameInterface/GameLoop.h"
63 : #include "tools/atlas/GameInterface/View.h"
64 :
65 : #include <algorithm>
66 :
67 : namespace
68 : {
69 :
70 : size_t g_NextScreenShotNumber = 0;
71 :
72 : ///////////////////////////////////////////////////////////////////////////////////
73 : // CRendererStatsTable - Profile display of rendering stats
74 :
75 : /**
76 : * Class CRendererStatsTable: Implementation of AbstractProfileTable to
77 : * display the renderer stats in-game.
78 : *
79 : * Accesses CRenderer::m_Stats by keeping the reference passed to the
80 : * constructor.
81 : */
82 6 : class CRendererStatsTable : public AbstractProfileTable
83 : {
84 : NONCOPYABLE(CRendererStatsTable);
85 : public:
86 : CRendererStatsTable(const CRenderer::Stats& st);
87 :
88 : // Implementation of AbstractProfileTable interface
89 : CStr GetName() override;
90 : CStr GetTitle() override;
91 : size_t GetNumberRows() override;
92 : const std::vector<ProfileColumn>& GetColumns() override;
93 : CStr GetCellText(size_t row, size_t col) override;
94 : AbstractProfileTable* GetChild(size_t row) override;
95 :
96 : private:
97 : /// Reference to the renderer singleton's stats
98 : const CRenderer::Stats& Stats;
99 :
100 : /// Column descriptions
101 : std::vector<ProfileColumn> columnDescriptions;
102 :
103 : enum
104 : {
105 : Row_DrawCalls = 0,
106 : Row_TerrainTris,
107 : Row_WaterTris,
108 : Row_ModelTris,
109 : Row_OverlayTris,
110 : Row_BlendSplats,
111 : Row_Particles,
112 : Row_VBReserved,
113 : Row_VBAllocated,
114 : Row_TextureMemory,
115 : Row_ShadersLoaded,
116 :
117 : // Must be last to count number of rows
118 : NumberRows
119 : };
120 : };
121 :
122 : // Construction
123 6 : CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
124 6 : : Stats(st)
125 : {
126 6 : columnDescriptions.push_back(ProfileColumn("Name", 230));
127 6 : columnDescriptions.push_back(ProfileColumn("Value", 100));
128 6 : }
129 :
130 : // Implementation of AbstractProfileTable interface
131 0 : CStr CRendererStatsTable::GetName()
132 : {
133 0 : return "renderer";
134 : }
135 :
136 0 : CStr CRendererStatsTable::GetTitle()
137 : {
138 0 : return "Renderer statistics";
139 : }
140 :
141 0 : size_t CRendererStatsTable::GetNumberRows()
142 : {
143 0 : return NumberRows;
144 : }
145 :
146 0 : const std::vector<ProfileColumn>& CRendererStatsTable::GetColumns()
147 : {
148 0 : return columnDescriptions;
149 : }
150 :
151 0 : CStr CRendererStatsTable::GetCellText(size_t row, size_t col)
152 : {
153 : char buf[256];
154 :
155 0 : switch(row)
156 : {
157 0 : case Row_DrawCalls:
158 0 : if (col == 0)
159 0 : return "# draw calls";
160 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls);
161 0 : return buf;
162 :
163 0 : case Row_TerrainTris:
164 0 : if (col == 0)
165 0 : return "# terrain tris";
166 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris);
167 0 : return buf;
168 :
169 0 : case Row_WaterTris:
170 0 : if (col == 0)
171 0 : return "# water tris";
172 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris);
173 0 : return buf;
174 :
175 0 : case Row_ModelTris:
176 0 : if (col == 0)
177 0 : return "# model tris";
178 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris);
179 0 : return buf;
180 :
181 0 : case Row_OverlayTris:
182 0 : if (col == 0)
183 0 : return "# overlay tris";
184 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris);
185 0 : return buf;
186 :
187 0 : case Row_BlendSplats:
188 0 : if (col == 0)
189 0 : return "# blend splats";
190 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats);
191 0 : return buf;
192 :
193 0 : case Row_Particles:
194 0 : if (col == 0)
195 0 : return "# particles";
196 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles);
197 0 : return buf;
198 :
199 0 : case Row_VBReserved:
200 0 : if (col == 0)
201 0 : return "VB reserved";
202 0 : sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024);
203 0 : return buf;
204 :
205 0 : case Row_VBAllocated:
206 0 : if (col == 0)
207 0 : return "VB allocated";
208 0 : sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024);
209 0 : return buf;
210 :
211 0 : case Row_TextureMemory:
212 0 : if (col == 0)
213 0 : return "textures uploaded";
214 0 : sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024);
215 0 : return buf;
216 :
217 0 : case Row_ShadersLoaded:
218 0 : if (col == 0)
219 0 : return "shader effects loaded";
220 0 : sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded());
221 0 : return buf;
222 :
223 0 : default:
224 0 : return "???";
225 : }
226 : }
227 :
228 0 : AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row))
229 : {
230 0 : return 0;
231 : }
232 :
233 : } // anonymous namespace
234 :
235 : ///////////////////////////////////////////////////////////////////////////////////
236 : // CRenderer implementation
237 :
238 : /**
239 : * Struct CRendererInternals: Truly hide data that is supposed to be hidden
240 : * in this structure so it won't even appear in header files.
241 : */
242 6 : class CRenderer::Internals
243 : {
244 : NONCOPYABLE(Internals);
245 : public:
246 : std::unique_ptr<Renderer::Backend::IDeviceCommandContext> deviceCommandContext;
247 :
248 : /// true if CRenderer::Open has been called
249 : bool IsOpen;
250 :
251 : /// true if shaders need to be reloaded
252 : bool ShadersDirty;
253 :
254 : /// Table to display renderer stats in-game via profile system
255 : CRendererStatsTable profileTable;
256 :
257 : /// Shader manager
258 : CShaderManager shaderManager;
259 :
260 : /// Texture manager
261 : CTextureManager textureManager;
262 :
263 : /// Time manager
264 : CTimeManager timeManager;
265 :
266 : /// Postprocessing effect manager
267 : CPostprocManager postprocManager;
268 :
269 : CSceneRenderer sceneRenderer;
270 :
271 : CDebugRenderer debugRenderer;
272 :
273 : CFontManager fontManager;
274 :
275 : struct VertexAttributesHash
276 : {
277 : size_t operator()(const std::vector<Renderer::Backend::SVertexAttributeFormat>& attributes) const;
278 : };
279 :
280 : std::unordered_map<
281 : std::vector<Renderer::Backend::SVertexAttributeFormat>,
282 : std::unique_ptr<Renderer::Backend::IVertexInputLayout>, VertexAttributesHash> vertexInputLayouts;
283 :
284 6 : Internals() :
285 6 : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats),
286 6 : deviceCommandContext(g_VideoMode.GetBackendDevice()->CreateCommandContext()),
287 12 : textureManager(g_VFS, false, g_VideoMode.GetBackendDevice())
288 : {
289 6 : }
290 : };
291 :
292 96 : size_t CRenderer::Internals::VertexAttributesHash::operator()(
293 : const std::vector<Renderer::Backend::SVertexAttributeFormat>& attributes) const
294 : {
295 96 : size_t seed = 0;
296 96 : hash_combine(seed, attributes.size());
297 318 : for (const Renderer::Backend::SVertexAttributeFormat& attribute : attributes)
298 : {
299 222 : hash_combine(seed, attribute.stream);
300 222 : hash_combine(seed, attribute.format);
301 222 : hash_combine(seed, attribute.offset);
302 222 : hash_combine(seed, attribute.stride);
303 222 : hash_combine(seed, attribute.rate);
304 222 : hash_combine(seed, attribute.bindingSlot);
305 : }
306 96 : return seed;
307 : }
308 :
309 6 : CRenderer::CRenderer()
310 : {
311 12 : TIMER(L"InitRenderer");
312 :
313 6 : m = std::make_unique<Internals>();
314 :
315 6 : g_ProfileViewer.AddRootTable(&m->profileTable);
316 :
317 6 : m_Width = 0;
318 6 : m_Height = 0;
319 :
320 6 : m_Stats.Reset();
321 :
322 : // Create terrain related stuff.
323 6 : new CTerrainTextureManager;
324 :
325 6 : Open(g_xres, g_yres);
326 :
327 : // Setup lighting environment. Since the Renderer accesses the
328 : // lighting environment through a pointer, this has to be done before
329 : // the first Frame.
330 6 : GetSceneRenderer().SetLightEnv(&g_LightEnv);
331 :
332 6 : ModelDefActivateFastImpl();
333 6 : ColorActivateFastImpl();
334 6 : ModelRenderer::Init();
335 6 : }
336 :
337 12 : CRenderer::~CRenderer()
338 : {
339 6 : delete &g_TexMan;
340 :
341 : // We no longer UnloadWaterTextures here -
342 : // that is the responsibility of the module that asked for
343 : // them to be loaded (i.e. CGameView).
344 6 : m.reset();
345 6 : }
346 :
347 0 : void CRenderer::ReloadShaders()
348 : {
349 0 : ENSURE(m->IsOpen);
350 :
351 0 : m->sceneRenderer.ReloadShaders();
352 0 : m->ShadersDirty = false;
353 0 : }
354 :
355 6 : bool CRenderer::Open(int width, int height)
356 : {
357 6 : m->IsOpen = true;
358 :
359 : // Dimensions
360 6 : m_Width = width;
361 6 : m_Height = height;
362 :
363 : // Validate the currently selected render path
364 6 : SetRenderPath(g_RenderingOptions.GetRenderPath());
365 :
366 6 : m->debugRenderer.Initialize();
367 :
368 6 : if (m->postprocManager.IsEnabled())
369 0 : m->postprocManager.Initialize();
370 :
371 6 : m->sceneRenderer.Initialize();
372 :
373 6 : return true;
374 : }
375 :
376 0 : void CRenderer::Resize(int width, int height)
377 : {
378 0 : m_Width = width;
379 0 : m_Height = height;
380 :
381 0 : m->postprocManager.Resize();
382 :
383 0 : m->sceneRenderer.Resize(width, height);
384 0 : }
385 :
386 6 : void CRenderer::SetRenderPath(RenderPath rp)
387 : {
388 6 : if (!m->IsOpen)
389 : {
390 : // Delay until Open() is called.
391 0 : return;
392 : }
393 :
394 : // Renderer has been opened, so validate the selected renderpath
395 : const bool hasShadersSupport =
396 12 : g_VideoMode.GetBackendDevice()->GetCapabilities().ARBShaders ||
397 12 : g_VideoMode.GetBackendDevice()->GetBackend() != Renderer::Backend::Backend::GL_ARB;
398 6 : if (rp == RenderPath::DEFAULT)
399 : {
400 1 : if (hasShadersSupport)
401 1 : rp = RenderPath::SHADER;
402 : else
403 0 : rp = RenderPath::FIXED;
404 : }
405 :
406 6 : if (rp == RenderPath::SHADER)
407 : {
408 6 : if (!hasShadersSupport)
409 : {
410 0 : LOGWARNING("Falling back to fixed function\n");
411 0 : rp = RenderPath::FIXED;
412 : }
413 : }
414 :
415 : // TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere.
416 6 : g_RenderingOptions.m_RenderPath = rp;
417 :
418 6 : MakeShadersDirty();
419 : }
420 :
421 0 : bool CRenderer::ShouldRender() const
422 : {
423 0 : return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
424 : }
425 :
426 0 : void CRenderer::RenderFrame(const bool needsPresent)
427 : {
428 : // Do not render if not focused while in fullscreen or minimised,
429 : // as that triggers a difficult-to-reproduce crash on some graphic cards.
430 0 : if (!ShouldRender())
431 0 : return;
432 :
433 0 : if (m_ScreenShotType == ScreenShotType::BIG)
434 : {
435 0 : RenderBigScreenShot(needsPresent);
436 : }
437 0 : else if (m_ScreenShotType == ScreenShotType::DEFAULT)
438 : {
439 0 : RenderScreenShot(needsPresent);
440 : }
441 : else
442 : {
443 0 : if (needsPresent)
444 : {
445 : // In case of no acquired backbuffer we have nothing render to.
446 0 : if (!g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
447 0 : return;
448 : }
449 :
450 0 : if (m_ShouldPreloadResourcesBeforeNextFrame)
451 : {
452 0 : m_ShouldPreloadResourcesBeforeNextFrame = false;
453 : // We don't need to render logger for the preload.
454 0 : RenderFrameImpl(true, false);
455 : }
456 :
457 0 : RenderFrameImpl(true, true);
458 :
459 0 : m->deviceCommandContext->Flush();
460 0 : if (needsPresent)
461 0 : g_VideoMode.GetBackendDevice()->Present();
462 : }
463 : }
464 :
465 0 : void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
466 : {
467 0 : PROFILE3("render");
468 :
469 0 : g_Profiler2.RecordGPUFrameStart();
470 :
471 0 : g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get());
472 :
473 0 : m->textureManager.MakeUploadProgress(m->deviceCommandContext.get());
474 :
475 : // prepare before starting the renderer frame
476 0 : if (g_Game && g_Game->IsGameStarted())
477 0 : g_Game->GetView()->BeginFrame();
478 :
479 0 : if (g_Game)
480 0 : m->sceneRenderer.SetSimulation(g_Game->GetSimulation2());
481 :
482 : // start new frame
483 0 : BeginFrame();
484 :
485 0 : if (g_Game && g_Game->IsGameStarted())
486 : {
487 0 : g_Game->GetView()->Prepare(m->deviceCommandContext.get());
488 :
489 0 : Renderer::Backend::IFramebuffer* framebuffer = nullptr;
490 :
491 0 : CPostprocManager& postprocManager = g_Renderer.GetPostprocManager();
492 0 : if (postprocManager.IsEnabled())
493 : {
494 : // We have to update the post process manager with real near/far planes
495 : // that we use for the scene rendering.
496 0 : postprocManager.SetDepthBufferClipPlanes(
497 0 : m->sceneRenderer.GetViewCamera().GetNearPlane(),
498 0 : m->sceneRenderer.GetViewCamera().GetFarPlane()
499 : );
500 0 : postprocManager.Initialize();
501 0 : framebuffer = postprocManager.PrepareAndGetOutputFramebuffer();
502 : }
503 : else
504 : {
505 : // We don't need to clear the color attachment of the framebuffer as the sky
506 : // is going to be rendered anyway.
507 0 : framebuffer =
508 0 : m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
509 : Renderer::Backend::AttachmentLoadOp::DONT_CARE,
510 : Renderer::Backend::AttachmentStoreOp::STORE,
511 : Renderer::Backend::AttachmentLoadOp::CLEAR,
512 0 : Renderer::Backend::AttachmentStoreOp::DONT_CARE);
513 : }
514 :
515 0 : m->deviceCommandContext->BeginFramebufferPass(framebuffer);
516 :
517 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
518 0 : viewportRect.width = framebuffer->GetWidth();
519 0 : viewportRect.height = framebuffer->GetHeight();
520 0 : m->deviceCommandContext->SetViewports(1, &viewportRect);
521 :
522 0 : g_Game->GetView()->Render(m->deviceCommandContext.get());
523 :
524 0 : if (postprocManager.IsEnabled())
525 : {
526 0 : m->deviceCommandContext->EndFramebufferPass();
527 :
528 0 : if (postprocManager.IsMultisampleEnabled())
529 0 : postprocManager.ResolveMultisampleFramebuffer(m->deviceCommandContext.get());
530 :
531 0 : postprocManager.ApplyPostproc(m->deviceCommandContext.get());
532 :
533 : Renderer::Backend::IFramebuffer* backbuffer =
534 0 : m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
535 : Renderer::Backend::AttachmentLoadOp::LOAD,
536 : Renderer::Backend::AttachmentStoreOp::STORE,
537 : Renderer::Backend::AttachmentLoadOp::LOAD,
538 0 : Renderer::Backend::AttachmentStoreOp::DONT_CARE);
539 0 : postprocManager.BlitOutputFramebuffer(
540 0 : m->deviceCommandContext.get(), backbuffer);
541 :
542 0 : m->deviceCommandContext->BeginFramebufferPass(backbuffer);
543 :
544 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
545 0 : viewportRect.width = backbuffer->GetWidth();
546 0 : viewportRect.height = backbuffer->GetHeight();
547 0 : m->deviceCommandContext->SetViewports(1, &viewportRect);
548 : }
549 :
550 0 : g_Game->GetView()->RenderOverlays(m->deviceCommandContext.get());
551 :
552 0 : g_Game->GetView()->GetCinema()->Render();
553 : }
554 : else
555 : {
556 : // We have a fullscreen background in our UI so we don't need
557 : // to clear the color attachment.
558 : // We don't need a depth test to render so we don't care about the
559 : // depth-stencil attachment content.
560 : // In case of Atlas we don't have g_Game, so we still need to clear depth.
561 0 : const Renderer::Backend::AttachmentLoadOp depthStencilLoadOp =
562 0 : g_AtlasGameLoop && g_AtlasGameLoop->view
563 0 : ? Renderer::Backend::AttachmentLoadOp::CLEAR
564 : : Renderer::Backend::AttachmentLoadOp::DONT_CARE;
565 : Renderer::Backend::IFramebuffer* backbuffer =
566 0 : m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
567 : Renderer::Backend::AttachmentLoadOp::DONT_CARE,
568 : Renderer::Backend::AttachmentStoreOp::STORE,
569 : depthStencilLoadOp,
570 0 : Renderer::Backend::AttachmentStoreOp::DONT_CARE);
571 0 : m->deviceCommandContext->BeginFramebufferPass(backbuffer);
572 :
573 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
574 0 : viewportRect.width = backbuffer->GetWidth();
575 0 : viewportRect.height = backbuffer->GetHeight();
576 0 : m->deviceCommandContext->SetViewports(1, &viewportRect);
577 : }
578 :
579 : // If we're in Atlas game view, render special tools
580 0 : if (g_AtlasGameLoop && g_AtlasGameLoop->view)
581 : {
582 0 : g_AtlasGameLoop->view->DrawCinemaPathTool();
583 : }
584 :
585 0 : RenderFrame2D(renderGUI, renderLogger);
586 :
587 0 : m->deviceCommandContext->EndFramebufferPass();
588 :
589 0 : EndFrame();
590 :
591 0 : const Stats& stats = GetStats();
592 0 : PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls);
593 0 : PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris);
594 0 : PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris);
595 0 : PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris);
596 0 : PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris);
597 0 : PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats);
598 0 : PROFILE2_ATTR("particles: %zu", stats.m_Particles);
599 :
600 0 : g_Profiler2.RecordGPUFrameEnd();
601 0 : }
602 :
603 0 : void CRenderer::RenderFrame2D(const bool renderGUI, const bool renderLogger)
604 : {
605 0 : CCanvas2D canvas(g_xres, g_yres, g_VideoMode.GetScale(), m->deviceCommandContext.get());
606 :
607 0 : m->sceneRenderer.RenderTextOverlays(canvas);
608 :
609 0 : if (renderGUI)
610 : {
611 0 : GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render GUI");
612 : // All GUI elements are drawn in Z order to render semi-transparent
613 : // objects correctly.
614 0 : g_GUI->Draw(canvas);
615 : }
616 :
617 : // If we're in Atlas game view, render special overlays (e.g. editor bandbox).
618 0 : if (g_AtlasGameLoop && g_AtlasGameLoop->view)
619 : {
620 0 : g_AtlasGameLoop->view->DrawOverlays(canvas);
621 : }
622 :
623 : {
624 0 : GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render console");
625 0 : g_Console->Render(canvas);
626 : }
627 :
628 0 : if (renderLogger)
629 : {
630 0 : GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render logger");
631 0 : g_Logger->Render(canvas);
632 : }
633 :
634 : {
635 0 : GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render profiler");
636 : // Profile information
637 0 : g_ProfileViewer.RenderProfile(canvas);
638 : }
639 0 : }
640 :
641 0 : void CRenderer::RenderScreenShot(const bool needsPresent)
642 : {
643 0 : m_ScreenShotType = ScreenShotType::NONE;
644 :
645 : // get next available numbered filename
646 : // note: %04d -> always 4 digits, so sorting by filename works correctly.
647 0 : const VfsPath filenameFormat(L"screenshots/screenshot%04d.png");
648 0 : VfsPath filename;
649 0 : vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
650 :
651 0 : const size_t width = static_cast<size_t>(g_xres), height = static_cast<size_t>(g_yres);
652 0 : const size_t bpp = 24;
653 :
654 0 : if (needsPresent && !g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
655 0 : return;
656 :
657 : // Hide log messages and re-render
658 0 : RenderFrameImpl(true, false);
659 :
660 0 : const size_t img_size = width * height * bpp / 8;
661 0 : const size_t hdr_size = tex_hdr_size(filename);
662 0 : std::shared_ptr<u8> buf;
663 0 : AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
664 0 : void* img = buf.get() + hdr_size;
665 0 : Tex t;
666 0 : if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0)
667 0 : return;
668 :
669 0 : m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img);
670 0 : m->deviceCommandContext->Flush();
671 0 : if (needsPresent)
672 0 : g_VideoMode.GetBackendDevice()->Present();
673 :
674 0 : if (tex_write(&t, filename) == INFO::OK)
675 : {
676 0 : OsPath realPath;
677 0 : g_VFS->GetRealPath(filename, realPath);
678 :
679 0 : LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
680 :
681 0 : debug_printf(
682 0 : CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
683 0 : realPath.string8().c_str());
684 : }
685 : else
686 0 : LOGERROR("Error writing screenshot to '%s'", filename.string8());
687 : }
688 :
689 0 : void CRenderer::RenderBigScreenShot(const bool needsPresent)
690 : {
691 0 : m_ScreenShotType = ScreenShotType::NONE;
692 :
693 : // If the game hasn't started yet then use WriteScreenshot to generate the image.
694 0 : if (!g_Game)
695 0 : return RenderScreenShot(needsPresent);
696 :
697 0 : int tiles = 4, tileWidth = 256, tileHeight = 256;
698 0 : CFG_GET_VAL("screenshot.tiles", tiles);
699 0 : CFG_GET_VAL("screenshot.tilewidth", tileWidth);
700 0 : CFG_GET_VAL("screenshot.tileheight", tileHeight);
701 0 : if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0)
702 : {
703 0 : LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight);
704 0 : return;
705 : }
706 :
707 : // get next available numbered filename
708 : // note: %04d -> always 4 digits, so sorting by filename works correctly.
709 0 : const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp");
710 0 : VfsPath filename;
711 0 : vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
712 :
713 : // Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
714 : // hope the screen is actually large enough for that.
715 0 : ENSURE(g_xres >= tileWidth && g_yres >= tileHeight);
716 :
717 0 : const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles;
718 0 : const int bpp = 24;
719 :
720 0 : const size_t imageSize = imageWidth * imageHeight * bpp / 8;
721 0 : const size_t tileSize = tileWidth * tileHeight * bpp / 8;
722 0 : const size_t headerSize = tex_hdr_size(filename);
723 0 : void* tileData = malloc(tileSize);
724 0 : if (!tileData)
725 : {
726 0 : WARN_IF_ERR(ERR::NO_MEM);
727 0 : return;
728 : }
729 0 : std::shared_ptr<u8> imageBuffer;
730 0 : AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize);
731 :
732 0 : Tex t;
733 0 : void* img = imageBuffer.get() + headerSize;
734 0 : if (t.wrap(imageWidth, imageHeight, bpp, TEX_BOTTOM_UP, imageBuffer, headerSize) < 0)
735 : {
736 0 : free(tileData);
737 0 : return;
738 : }
739 :
740 0 : CCamera oldCamera = *g_Game->GetView()->GetCamera();
741 :
742 : // Resize various things so that the sizes and aspect ratios are correct
743 : {
744 0 : g_Renderer.Resize(tileWidth, tileHeight);
745 0 : SViewPort vp = { 0, 0, tileWidth, tileHeight };
746 0 : g_Game->GetView()->SetViewport(vp);
747 : }
748 :
749 : // Render each tile
750 0 : CMatrix3D projection;
751 0 : projection.SetIdentity();
752 0 : const float aspectRatio = 1.0f * tileWidth / tileHeight;
753 0 : for (int tileY = 0; tileY < tiles; ++tileY)
754 : {
755 0 : for (int tileX = 0; tileX < tiles; ++tileX)
756 : {
757 : // Adjust the camera to render the appropriate region
758 0 : if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
759 : {
760 0 : projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY);
761 : }
762 0 : g_Game->GetView()->GetCamera()->SetProjection(projection);
763 :
764 0 : if (!needsPresent || g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
765 : {
766 0 : RenderFrameImpl(false, false);
767 :
768 0 : m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData);
769 0 : m->deviceCommandContext->Flush();
770 :
771 0 : if (needsPresent)
772 0 : g_VideoMode.GetBackendDevice()->Present();
773 : }
774 :
775 : // Copy the tile pixels into the main image
776 0 : for (int y = 0; y < tileHeight; ++y)
777 : {
778 0 : void* dest = static_cast<char*>(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8;
779 0 : void* src = static_cast<char*>(tileData) + y * tileWidth * bpp / 8;
780 0 : memcpy(dest, src, tileWidth * bpp / 8);
781 : }
782 : }
783 : }
784 :
785 : // Restore the viewport settings
786 : {
787 0 : g_Renderer.Resize(g_xres, g_yres);
788 0 : SViewPort vp = { 0, 0, g_xres, g_yres };
789 0 : g_Game->GetView()->SetViewport(vp);
790 0 : g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
791 : }
792 :
793 0 : if (tex_write(&t, filename) == INFO::OK)
794 : {
795 0 : OsPath realPath;
796 0 : g_VFS->GetRealPath(filename, realPath);
797 :
798 0 : LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
799 :
800 0 : debug_printf(
801 0 : CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
802 0 : realPath.string8().c_str());
803 : }
804 : else
805 0 : LOGERROR("Error writing screenshot to '%s'", filename.string8());
806 :
807 0 : free(tileData);
808 : }
809 :
810 0 : void CRenderer::BeginFrame()
811 : {
812 0 : PROFILE("begin frame");
813 :
814 : // Zero out all the per-frame stats.
815 0 : m_Stats.Reset();
816 :
817 0 : if (m->ShadersDirty)
818 0 : ReloadShaders();
819 :
820 0 : m->sceneRenderer.BeginFrame();
821 0 : }
822 :
823 0 : void CRenderer::EndFrame()
824 : {
825 0 : PROFILE3("end frame");
826 :
827 0 : m->sceneRenderer.EndFrame();
828 0 : }
829 :
830 6 : void CRenderer::MakeShadersDirty()
831 : {
832 6 : m->ShadersDirty = true;
833 6 : m->sceneRenderer.MakeShadersDirty();
834 6 : }
835 :
836 5 : CTextureManager& CRenderer::GetTextureManager()
837 : {
838 5 : return m->textureManager;
839 : }
840 :
841 60 : CShaderManager& CRenderer::GetShaderManager()
842 : {
843 60 : return m->shaderManager;
844 : }
845 :
846 0 : CTimeManager& CRenderer::GetTimeManager()
847 : {
848 0 : return m->timeManager;
849 : }
850 :
851 0 : CPostprocManager& CRenderer::GetPostprocManager()
852 : {
853 0 : return m->postprocManager;
854 : }
855 :
856 6 : CSceneRenderer& CRenderer::GetSceneRenderer()
857 : {
858 6 : return m->sceneRenderer;
859 : }
860 :
861 0 : CDebugRenderer& CRenderer::GetDebugRenderer()
862 : {
863 0 : return m->debugRenderer;
864 : }
865 :
866 3083 : CFontManager& CRenderer::GetFontManager()
867 : {
868 3083 : return m->fontManager;
869 : }
870 :
871 0 : void CRenderer::PreloadResourcesBeforeNextFrame()
872 : {
873 0 : m_ShouldPreloadResourcesBeforeNextFrame = true;
874 0 : }
875 :
876 0 : void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType)
877 : {
878 0 : m_ScreenShotType = screenShotType;
879 0 : }
880 :
881 6 : Renderer::Backend::IDeviceCommandContext* CRenderer::GetDeviceCommandContext()
882 : {
883 6 : return m->deviceCommandContext.get();
884 : }
885 :
886 96 : Renderer::Backend::IVertexInputLayout* CRenderer::GetVertexInputLayout(
887 : const PS::span<const Renderer::Backend::SVertexAttributeFormat> attributes)
888 : {
889 96 : const auto [it, inserted] = m->vertexInputLayouts.emplace(
890 96 : std::vector<Renderer::Backend::SVertexAttributeFormat>{attributes.begin(), attributes.end()}, nullptr);
891 96 : if (inserted)
892 78 : it->second = g_VideoMode.GetBackendDevice()->CreateVertexInputLayout(attributes);
893 96 : return it->second.get();
894 3 : }
|