Merge 'remotes/trunk'
[0ad.git] / source / renderer / Renderer.cpp
blob70918faebc9bf2a581ac4fafb2cd65f3f0418db8
1 /* Copyright (C) 2023 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.h"
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"
65 #include <algorithm>
67 namespace
70 size_t g_NextScreenShotNumber = 0;
72 ///////////////////////////////////////////////////////////////////////////////////
73 // CRendererStatsTable - Profile display of rendering stats
75 /**
76 * Class CRendererStatsTable: Implementation of AbstractProfileTable to
77 * display the renderer stats in-game.
79 * Accesses CRenderer::m_Stats by keeping the reference passed to the
80 * constructor.
82 class CRendererStatsTable : public AbstractProfileTable
84 NONCOPYABLE(CRendererStatsTable);
85 public:
86 CRendererStatsTable(const CRenderer::Stats& st);
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;
96 private:
97 /// Reference to the renderer singleton's stats
98 const CRenderer::Stats& Stats;
100 /// Column descriptions
101 std::vector<ProfileColumn> columnDescriptions;
103 enum
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,
117 // Must be last to count number of rows
118 NumberRows
122 // Construction
123 CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
124 : Stats(st)
126 columnDescriptions.push_back(ProfileColumn("Name", 230));
127 columnDescriptions.push_back(ProfileColumn("Value", 100));
130 // Implementation of AbstractProfileTable interface
131 CStr CRendererStatsTable::GetName()
133 return "renderer";
136 CStr CRendererStatsTable::GetTitle()
138 return "Renderer statistics";
141 size_t CRendererStatsTable::GetNumberRows()
143 return NumberRows;
146 const std::vector<ProfileColumn>& CRendererStatsTable::GetColumns()
148 return columnDescriptions;
151 CStr CRendererStatsTable::GetCellText(size_t row, size_t col)
153 char buf[256];
155 switch(row)
157 case Row_DrawCalls:
158 if (col == 0)
159 return "# draw calls";
160 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls);
161 return buf;
163 case Row_TerrainTris:
164 if (col == 0)
165 return "# terrain tris";
166 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris);
167 return buf;
169 case Row_WaterTris:
170 if (col == 0)
171 return "# water tris";
172 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris);
173 return buf;
175 case Row_ModelTris:
176 if (col == 0)
177 return "# model tris";
178 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris);
179 return buf;
181 case Row_OverlayTris:
182 if (col == 0)
183 return "# overlay tris";
184 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris);
185 return buf;
187 case Row_BlendSplats:
188 if (col == 0)
189 return "# blend splats";
190 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats);
191 return buf;
193 case Row_Particles:
194 if (col == 0)
195 return "# particles";
196 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles);
197 return buf;
199 case Row_VBReserved:
200 if (col == 0)
201 return "VB reserved";
202 sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024);
203 return buf;
205 case Row_VBAllocated:
206 if (col == 0)
207 return "VB allocated";
208 sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024);
209 return buf;
211 case Row_TextureMemory:
212 if (col == 0)
213 return "textures uploaded";
214 sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024);
215 return buf;
217 case Row_ShadersLoaded:
218 if (col == 0)
219 return "shader effects loaded";
220 sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded());
221 return buf;
223 default:
224 return "???";
228 AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row))
230 return 0;
233 } // anonymous namespace
235 ///////////////////////////////////////////////////////////////////////////////////
236 // CRenderer implementation
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.
242 class CRenderer::Internals
244 NONCOPYABLE(Internals);
245 public:
246 std::unique_ptr<Renderer::Backend::IDeviceCommandContext> deviceCommandContext;
248 /// true if CRenderer::Open has been called
249 bool IsOpen;
251 /// true if shaders need to be reloaded
252 bool ShadersDirty;
254 /// Table to display renderer stats in-game via profile system
255 CRendererStatsTable profileTable;
257 /// Shader manager
258 CShaderManager shaderManager;
260 /// Texture manager
261 CTextureManager textureManager;
263 /// Time manager
264 CTimeManager timeManager;
266 /// Postprocessing effect manager
267 CPostprocManager postprocManager;
269 CSceneRenderer sceneRenderer;
271 CDebugRenderer debugRenderer;
273 CFontManager fontManager;
275 struct VertexAttributesHash
277 size_t operator()(const std::vector<Renderer::Backend::SVertexAttributeFormat>& attributes) const;
280 std::unordered_map<
281 std::vector<Renderer::Backend::SVertexAttributeFormat>,
282 std::unique_ptr<Renderer::Backend::IVertexInputLayout>, VertexAttributesHash> vertexInputLayouts;
284 Internals() :
285 IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats),
286 deviceCommandContext(g_VideoMode.GetBackendDevice()->CreateCommandContext()),
287 textureManager(g_VFS, false, g_VideoMode.GetBackendDevice())
292 size_t CRenderer::Internals::VertexAttributesHash::operator()(
293 const std::vector<Renderer::Backend::SVertexAttributeFormat>& attributes) const
295 size_t seed = 0;
296 hash_combine(seed, attributes.size());
297 for (const Renderer::Backend::SVertexAttributeFormat& attribute : attributes)
299 hash_combine(seed, attribute.stream);
300 hash_combine(seed, attribute.format);
301 hash_combine(seed, attribute.offset);
302 hash_combine(seed, attribute.stride);
303 hash_combine(seed, attribute.rate);
304 hash_combine(seed, attribute.bindingSlot);
306 return seed;
309 CRenderer::CRenderer()
311 TIMER(L"InitRenderer");
313 m = std::make_unique<Internals>();
315 g_ProfileViewer.AddRootTable(&m->profileTable);
317 m_Width = 0;
318 m_Height = 0;
320 m_Stats.Reset();
322 // Create terrain related stuff.
323 new CTerrainTextureManager;
325 Open(g_xres, g_yres);
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 GetSceneRenderer().SetLightEnv(&g_LightEnv);
332 ModelDefActivateFastImpl();
333 ColorActivateFastImpl();
334 ModelRenderer::Init();
337 CRenderer::~CRenderer()
339 delete &g_TexMan;
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 m.reset();
347 void CRenderer::ReloadShaders()
349 ENSURE(m->IsOpen);
351 m->sceneRenderer.ReloadShaders();
352 m->ShadersDirty = false;
355 bool CRenderer::Open(int width, int height)
357 m->IsOpen = true;
359 // Dimensions
360 m_Width = width;
361 m_Height = height;
363 // Validate the currently selected render path
364 SetRenderPath(g_RenderingOptions.GetRenderPath());
366 m->debugRenderer.Initialize();
368 if (m->postprocManager.IsEnabled())
369 m->postprocManager.Initialize();
371 m->sceneRenderer.Initialize();
373 return true;
376 void CRenderer::Resize(int width, int height)
378 m_Width = width;
379 m_Height = height;
381 m->postprocManager.Resize();
383 m->sceneRenderer.Resize(width, height);
386 void CRenderer::SetRenderPath(RenderPath rp)
388 if (!m->IsOpen)
390 // Delay until Open() is called.
391 return;
394 // Renderer has been opened, so validate the selected renderpath
395 const bool hasShadersSupport =
396 g_VideoMode.GetBackendDevice()->GetCapabilities().ARBShaders ||
397 g_VideoMode.GetBackendDevice()->GetBackend() != Renderer::Backend::Backend::GL_ARB;
398 if (rp == RenderPath::DEFAULT)
400 if (hasShadersSupport)
401 rp = RenderPath::SHADER;
402 else
403 rp = RenderPath::FIXED;
406 if (rp == RenderPath::SHADER)
408 if (!hasShadersSupport)
410 LOGWARNING("Falling back to fixed function\n");
411 rp = RenderPath::FIXED;
415 // TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere.
416 g_RenderingOptions.m_RenderPath = rp;
418 MakeShadersDirty();
421 bool CRenderer::ShouldRender() const
423 return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
426 void CRenderer::RenderFrame(const bool needsPresent)
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 if (!ShouldRender())
431 return;
433 if (m_ScreenShotType == ScreenShotType::BIG)
435 RenderBigScreenShot(needsPresent);
437 else if (m_ScreenShotType == ScreenShotType::DEFAULT)
439 RenderScreenShot(needsPresent);
441 else
443 if (needsPresent)
445 // In case of no acquired backbuffer we have nothing render to.
446 if (!g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
447 return;
450 if (m_ShouldPreloadResourcesBeforeNextFrame)
452 m_ShouldPreloadResourcesBeforeNextFrame = false;
453 // We don't need to render logger for the preload.
454 RenderFrameImpl(true, false);
457 RenderFrameImpl(true, true);
459 m->deviceCommandContext->Flush();
460 if (needsPresent)
461 g_VideoMode.GetBackendDevice()->Present();
465 void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
467 PROFILE3("render");
469 g_Profiler2.RecordGPUFrameStart();
471 g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get());
473 m->textureManager.MakeUploadProgress(m->deviceCommandContext.get());
475 // prepare before starting the renderer frame
476 if (g_Game && g_Game->IsGameStarted())
477 g_Game->GetView()->BeginFrame();
479 if (g_Game)
480 m->sceneRenderer.SetSimulation(g_Game->GetSimulation2());
482 // start new frame
483 BeginFrame();
485 if (g_Game && g_Game->IsGameStarted())
487 g_Game->GetView()->Prepare(m->deviceCommandContext.get());
489 Renderer::Backend::IFramebuffer* framebuffer = nullptr;
491 CPostprocManager& postprocManager = g_Renderer.GetPostprocManager();
492 if (postprocManager.IsEnabled())
494 // We have to update the post process manager with real near/far planes
495 // that we use for the scene rendering.
496 postprocManager.SetDepthBufferClipPlanes(
497 m->sceneRenderer.GetViewCamera().GetNearPlane(),
498 m->sceneRenderer.GetViewCamera().GetFarPlane()
500 postprocManager.Initialize();
501 framebuffer = postprocManager.PrepareAndGetOutputFramebuffer();
503 else
505 // We don't need to clear the color attachment of the framebuffer as the sky
506 // is going to be rendered anyway.
507 framebuffer =
508 m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
509 Renderer::Backend::AttachmentLoadOp::DONT_CARE,
510 Renderer::Backend::AttachmentStoreOp::STORE,
511 Renderer::Backend::AttachmentLoadOp::CLEAR,
512 Renderer::Backend::AttachmentStoreOp::DONT_CARE);
515 m->deviceCommandContext->BeginFramebufferPass(framebuffer);
517 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
518 viewportRect.width = framebuffer->GetWidth();
519 viewportRect.height = framebuffer->GetHeight();
520 m->deviceCommandContext->SetViewports(1, &viewportRect);
522 g_Game->GetView()->Render(m->deviceCommandContext.get());
524 if (postprocManager.IsEnabled())
526 m->deviceCommandContext->EndFramebufferPass();
528 if (postprocManager.IsMultisampleEnabled())
529 postprocManager.ResolveMultisampleFramebuffer(m->deviceCommandContext.get());
531 postprocManager.ApplyPostproc(m->deviceCommandContext.get());
533 Renderer::Backend::IFramebuffer* backbuffer =
534 m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
535 Renderer::Backend::AttachmentLoadOp::LOAD,
536 Renderer::Backend::AttachmentStoreOp::STORE,
537 Renderer::Backend::AttachmentLoadOp::LOAD,
538 Renderer::Backend::AttachmentStoreOp::DONT_CARE);
539 postprocManager.BlitOutputFramebuffer(
540 m->deviceCommandContext.get(), backbuffer);
542 m->deviceCommandContext->BeginFramebufferPass(backbuffer);
544 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
545 viewportRect.width = backbuffer->GetWidth();
546 viewportRect.height = backbuffer->GetHeight();
547 m->deviceCommandContext->SetViewports(1, &viewportRect);
550 g_Game->GetView()->RenderOverlays(m->deviceCommandContext.get());
552 g_Game->GetView()->GetCinema()->Render();
554 else
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 const Renderer::Backend::AttachmentLoadOp depthStencilLoadOp =
562 g_AtlasGameLoop && g_AtlasGameLoop->view
563 ? Renderer::Backend::AttachmentLoadOp::CLEAR
564 : Renderer::Backend::AttachmentLoadOp::DONT_CARE;
565 Renderer::Backend::IFramebuffer* backbuffer =
566 m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
567 Renderer::Backend::AttachmentLoadOp::DONT_CARE,
568 Renderer::Backend::AttachmentStoreOp::STORE,
569 depthStencilLoadOp,
570 Renderer::Backend::AttachmentStoreOp::DONT_CARE);
571 m->deviceCommandContext->BeginFramebufferPass(backbuffer);
573 Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
574 viewportRect.width = backbuffer->GetWidth();
575 viewportRect.height = backbuffer->GetHeight();
576 m->deviceCommandContext->SetViewports(1, &viewportRect);
579 // If we're in Atlas game view, render special tools
580 if (g_AtlasGameLoop && g_AtlasGameLoop->view)
582 g_AtlasGameLoop->view->DrawCinemaPathTool();
585 RenderFrame2D(renderGUI, renderLogger);
587 m->deviceCommandContext->EndFramebufferPass();
589 EndFrame();
591 const Stats& stats = GetStats();
592 PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls);
593 PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris);
594 PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris);
595 PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris);
596 PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris);
597 PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats);
598 PROFILE2_ATTR("particles: %zu", stats.m_Particles);
600 g_Profiler2.RecordGPUFrameEnd();
603 void CRenderer::RenderFrame2D(const bool renderGUI, const bool renderLogger)
605 CCanvas2D canvas(g_xres, g_yres, g_VideoMode.GetScale(), m->deviceCommandContext.get());
607 m->sceneRenderer.RenderTextOverlays(canvas);
609 if (renderGUI)
611 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 g_GUI->Draw(canvas);
617 // If we're in Atlas game view, render special overlays (e.g. editor bandbox).
618 if (g_AtlasGameLoop && g_AtlasGameLoop->view)
620 g_AtlasGameLoop->view->DrawOverlays(canvas);
624 GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render console");
625 g_Console->Render(canvas);
628 if (renderLogger)
630 GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render logger");
631 g_Logger->Render(canvas);
635 GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render profiler");
636 // Profile information
637 g_ProfileViewer.RenderProfile(canvas);
641 void CRenderer::RenderScreenShot(const bool needsPresent)
643 m_ScreenShotType = ScreenShotType::NONE;
645 // get next available numbered filename
646 // note: %04d -> always 4 digits, so sorting by filename works correctly.
647 const VfsPath filenameFormat(L"screenshots/screenshot%04d.png");
648 VfsPath filename;
649 vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
651 const size_t width = static_cast<size_t>(g_xres), height = static_cast<size_t>(g_yres);
652 const size_t bpp = 24;
654 if (needsPresent && !g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
655 return;
657 // Hide log messages and re-render
658 RenderFrameImpl(true, false);
660 const size_t img_size = width * height * bpp / 8;
661 const size_t hdr_size = tex_hdr_size(filename);
662 std::shared_ptr<u8> buf;
663 AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
664 void* img = buf.get() + hdr_size;
665 Tex t;
666 if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0)
667 return;
669 m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img);
670 m->deviceCommandContext->Flush();
671 if (needsPresent)
672 g_VideoMode.GetBackendDevice()->Present();
674 if (tex_write(&t, filename) == INFO::OK)
676 OsPath realPath;
677 g_VFS->GetRealPath(filename, realPath);
679 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
681 debug_printf(
682 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
683 realPath.string8().c_str());
685 else
686 LOGERROR("Error writing screenshot to '%s'", filename.string8());
689 void CRenderer::RenderBigScreenShot(const bool needsPresent)
691 m_ScreenShotType = ScreenShotType::NONE;
693 // If the game hasn't started yet then use WriteScreenshot to generate the image.
694 if (!g_Game)
695 return RenderScreenShot(needsPresent);
697 int tiles = 4, tileWidth = 256, tileHeight = 256;
698 CFG_GET_VAL("screenshot.tiles", tiles);
699 CFG_GET_VAL("screenshot.tilewidth", tileWidth);
700 CFG_GET_VAL("screenshot.tileheight", tileHeight);
701 if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0)
703 LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight);
704 return;
707 // get next available numbered filename
708 // note: %04d -> always 4 digits, so sorting by filename works correctly.
709 const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp");
710 VfsPath filename;
711 vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
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 ENSURE(g_xres >= tileWidth && g_yres >= tileHeight);
717 const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles;
718 const int bpp = 24;
720 const size_t imageSize = imageWidth * imageHeight * bpp / 8;
721 const size_t tileSize = tileWidth * tileHeight * bpp / 8;
722 const size_t headerSize = tex_hdr_size(filename);
723 void* tileData = malloc(tileSize);
724 if (!tileData)
726 WARN_IF_ERR(ERR::NO_MEM);
727 return;
729 std::shared_ptr<u8> imageBuffer;
730 AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize);
732 Tex t;
733 void* img = imageBuffer.get() + headerSize;
734 if (t.wrap(imageWidth, imageHeight, bpp, TEX_BOTTOM_UP, imageBuffer, headerSize) < 0)
736 free(tileData);
737 return;
740 CCamera oldCamera = *g_Game->GetView()->GetCamera();
742 // Resize various things so that the sizes and aspect ratios are correct
744 g_Renderer.Resize(tileWidth, tileHeight);
745 SViewPort vp = { 0, 0, tileWidth, tileHeight };
746 g_Game->GetView()->SetViewport(vp);
749 // Render each tile
750 CMatrix3D projection;
751 projection.SetIdentity();
752 const float aspectRatio = 1.0f * tileWidth / tileHeight;
753 for (int tileY = 0; tileY < tiles; ++tileY)
755 for (int tileX = 0; tileX < tiles; ++tileX)
757 // Adjust the camera to render the appropriate region
758 if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
760 projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY);
762 g_Game->GetView()->GetCamera()->SetProjection(projection);
764 if (!needsPresent || g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
766 RenderFrameImpl(false, false);
768 m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData);
769 m->deviceCommandContext->Flush();
771 if (needsPresent)
772 g_VideoMode.GetBackendDevice()->Present();
775 // Copy the tile pixels into the main image
776 for (int y = 0; y < tileHeight; ++y)
778 void* dest = static_cast<char*>(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8;
779 void* src = static_cast<char*>(tileData) + y * tileWidth * bpp / 8;
780 memcpy(dest, src, tileWidth * bpp / 8);
785 // Restore the viewport settings
787 g_Renderer.Resize(g_xres, g_yres);
788 SViewPort vp = { 0, 0, g_xres, g_yres };
789 g_Game->GetView()->SetViewport(vp);
790 g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
793 if (tex_write(&t, filename) == INFO::OK)
795 OsPath realPath;
796 g_VFS->GetRealPath(filename, realPath);
798 LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
800 debug_printf(
801 CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
802 realPath.string8().c_str());
804 else
805 LOGERROR("Error writing screenshot to '%s'", filename.string8());
807 free(tileData);
810 void CRenderer::BeginFrame()
812 PROFILE("begin frame");
814 // Zero out all the per-frame stats.
815 m_Stats.Reset();
817 if (m->ShadersDirty)
818 ReloadShaders();
820 m->sceneRenderer.BeginFrame();
823 void CRenderer::EndFrame()
825 PROFILE3("end frame");
827 m->sceneRenderer.EndFrame();
830 void CRenderer::MakeShadersDirty()
832 m->ShadersDirty = true;
833 m->sceneRenderer.MakeShadersDirty();
836 CTextureManager& CRenderer::GetTextureManager()
838 return m->textureManager;
841 CShaderManager& CRenderer::GetShaderManager()
843 return m->shaderManager;
846 CTimeManager& CRenderer::GetTimeManager()
848 return m->timeManager;
851 CPostprocManager& CRenderer::GetPostprocManager()
853 return m->postprocManager;
856 CSceneRenderer& CRenderer::GetSceneRenderer()
858 return m->sceneRenderer;
861 CDebugRenderer& CRenderer::GetDebugRenderer()
863 return m->debugRenderer;
866 CFontManager& CRenderer::GetFontManager()
868 return m->fontManager;
871 void CRenderer::PreloadResourcesBeforeNextFrame()
873 m_ShouldPreloadResourcesBeforeNextFrame = true;
876 void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType)
878 m_ScreenShotType = screenShotType;
881 Renderer::Backend::IDeviceCommandContext* CRenderer::GetDeviceCommandContext()
883 return m->deviceCommandContext.get();
886 Renderer::Backend::IVertexInputLayout* CRenderer::GetVertexInputLayout(
887 const PS::span<const Renderer::Backend::SVertexAttributeFormat> attributes)
889 const auto [it, inserted] = m->vertexInputLayouts.emplace(
890 std::vector<Renderer::Backend::SVertexAttributeFormat>{attributes.begin(), attributes.end()}, nullptr);
891 if (inserted)
892 it->second = g_VideoMode.GetBackendDevice()->CreateVertexInputLayout(attributes);
893 return it->second.get();