1
0
forked from 0ad/0ad
0ad/source/renderer/Renderer.cpp
bb 157c6af18e Make the space in 0 A.D. non-breaking throughout the codebase.
Avoid cases of filenames
Update years in terms and other legal(ish) documents
Don't update years in license headers, since change is not meaningful

Will add linter rule in seperate commit

Happy recompiling everyone!

Original Patch By: Nescio
Comment By: Gallaecio
Differential Revision: D2620
This was SVN commit r27786.
2023-07-27 20:54:46 +00:00

906 lines
24 KiB
C++

/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "Renderer.h"
#include "graphics/Canvas2D.h"
#include "graphics/CinemaManager.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/ModelDef.h"
#include "graphics/TerrainTextureManager.h"
#include "i18n/L10n.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/hash.h"
#include "lib/tex/tex.h"
#include "gui/GUIManager.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Globals.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Filesystem.h"
#include "ps/World.h"
#include "ps/ProfileViewer.h"
#include "graphics/Camera.h"
#include "graphics/FontManager.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "renderer/backend/IDevice.h"
#include "renderer/DebugRenderer.h"
#include "renderer/PostprocManager.h"
#include "renderer/RenderingOptions.h"
#include "renderer/RenderModifiers.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TimeManager.h"
#include "renderer/VertexBufferManager.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "tools/atlas/GameInterface/View.h"
#include <algorithm>
namespace
{
size_t g_NextScreenShotNumber = 0;
///////////////////////////////////////////////////////////////////////////////////
// CRendererStatsTable - Profile display of rendering stats
/**
* Class CRendererStatsTable: Implementation of AbstractProfileTable to
* display the renderer stats in-game.
*
* Accesses CRenderer::m_Stats by keeping the reference passed to the
* constructor.
*/
class CRendererStatsTable : public AbstractProfileTable
{
NONCOPYABLE(CRendererStatsTable);
public:
CRendererStatsTable(const CRenderer::Stats& st);
// Implementation of AbstractProfileTable interface
CStr GetName() override;
CStr GetTitle() override;
size_t GetNumberRows() override;
const std::vector<ProfileColumn>& GetColumns() override;
CStr GetCellText(size_t row, size_t col) override;
AbstractProfileTable* GetChild(size_t row) override;
private:
/// Reference to the renderer singleton's stats
const CRenderer::Stats& Stats;
/// Column descriptions
std::vector<ProfileColumn> columnDescriptions;
enum
{
Row_DrawCalls = 0,
Row_TerrainTris,
Row_WaterTris,
Row_ModelTris,
Row_OverlayTris,
Row_BlendSplats,
Row_Particles,
Row_VBReserved,
Row_VBAllocated,
Row_TextureMemory,
Row_ShadersLoaded,
// Must be last to count number of rows
NumberRows
};
};
// Construction
CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
: Stats(st)
{
columnDescriptions.push_back(ProfileColumn("Name", 230));
columnDescriptions.push_back(ProfileColumn("Value", 100));
}
// Implementation of AbstractProfileTable interface
CStr CRendererStatsTable::GetName()
{
return "renderer";
}
CStr CRendererStatsTable::GetTitle()
{
return "Renderer statistics";
}
size_t CRendererStatsTable::GetNumberRows()
{
return NumberRows;
}
const std::vector<ProfileColumn>& CRendererStatsTable::GetColumns()
{
return columnDescriptions;
}
CStr CRendererStatsTable::GetCellText(size_t row, size_t col)
{
char buf[256];
switch(row)
{
case Row_DrawCalls:
if (col == 0)
return "# draw calls";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls);
return buf;
case Row_TerrainTris:
if (col == 0)
return "# terrain tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris);
return buf;
case Row_WaterTris:
if (col == 0)
return "# water tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris);
return buf;
case Row_ModelTris:
if (col == 0)
return "# model tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris);
return buf;
case Row_OverlayTris:
if (col == 0)
return "# overlay tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris);
return buf;
case Row_BlendSplats:
if (col == 0)
return "# blend splats";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats);
return buf;
case Row_Particles:
if (col == 0)
return "# particles";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles);
return buf;
case Row_VBReserved:
if (col == 0)
return "VB reserved";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024);
return buf;
case Row_VBAllocated:
if (col == 0)
return "VB allocated";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024);
return buf;
case Row_TextureMemory:
if (col == 0)
return "textures uploaded";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024);
return buf;
case Row_ShadersLoaded:
if (col == 0)
return "shader effects loaded";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded());
return buf;
default:
return "???";
}
}
AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row))
{
return 0;
}
} // anonymous namespace
///////////////////////////////////////////////////////////////////////////////////
// CRenderer implementation
/**
* Struct CRendererInternals: Truly hide data that is supposed to be hidden
* in this structure so it won't even appear in header files.
*/
class CRenderer::Internals
{
NONCOPYABLE(Internals);
public:
Renderer::Backend::IDevice* device;
std::unique_ptr<Renderer::Backend::IDeviceCommandContext> deviceCommandContext;
/// true if CRenderer::Open has been called
bool IsOpen;
/// true if shaders need to be reloaded
bool ShadersDirty;
/// Table to display renderer stats in-game via profile system
CRendererStatsTable profileTable;
/// Shader manager
CShaderManager shaderManager;
/// Texture manager
CTextureManager textureManager;
/// Time manager
CTimeManager timeManager;
/// Postprocessing effect manager
CPostprocManager postprocManager;
CSceneRenderer sceneRenderer;
CDebugRenderer debugRenderer;
CFontManager fontManager;
struct VertexAttributesHash
{
size_t operator()(const std::vector<Renderer::Backend::SVertexAttributeFormat>& attributes) const;
};
std::unordered_map<
std::vector<Renderer::Backend::SVertexAttributeFormat>,
std::unique_ptr<Renderer::Backend::IVertexInputLayout>, VertexAttributesHash> vertexInputLayouts;
Internals(Renderer::Backend::IDevice* device) :
device(device),
deviceCommandContext(device->CreateCommandContext()),
IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats),
shaderManager(device), textureManager(g_VFS, false, device),
postprocManager(device), sceneRenderer(device)
{
}
};
size_t CRenderer::Internals::VertexAttributesHash::operator()(
const std::vector<Renderer::Backend::SVertexAttributeFormat>& attributes) const
{
size_t seed = 0;
hash_combine(seed, attributes.size());
for (const Renderer::Backend::SVertexAttributeFormat& attribute : attributes)
{
hash_combine(seed, attribute.stream);
hash_combine(seed, attribute.format);
hash_combine(seed, attribute.offset);
hash_combine(seed, attribute.stride);
hash_combine(seed, attribute.rate);
hash_combine(seed, attribute.bindingSlot);
}
return seed;
}
CRenderer::CRenderer(Renderer::Backend::IDevice* device)
{
TIMER(L"InitRenderer");
m = std::make_unique<Internals>(device);
g_ProfileViewer.AddRootTable(&m->profileTable);
m_Width = 0;
m_Height = 0;
m_Stats.Reset();
// Create terrain related stuff.
new CTerrainTextureManager(device);
Open(g_xres, g_yres);
// Setup lighting environment. Since the Renderer accesses the
// lighting environment through a pointer, this has to be done before
// the first Frame.
GetSceneRenderer().SetLightEnv(&g_LightEnv);
ModelDefActivateFastImpl();
ColorActivateFastImpl();
ModelRenderer::Init();
}
CRenderer::~CRenderer()
{
delete &g_TexMan;
// We no longer UnloadWaterTextures here -
// that is the responsibility of the module that asked for
// them to be loaded (i.e. CGameView).
m.reset();
}
void CRenderer::ReloadShaders()
{
ENSURE(m->IsOpen);
m->sceneRenderer.ReloadShaders(m->device);
m->ShadersDirty = false;
}
bool CRenderer::Open(int width, int height)
{
m->IsOpen = true;
// Dimensions
m_Width = width;
m_Height = height;
// Validate the currently selected render path
SetRenderPath(g_RenderingOptions.GetRenderPath());
m->debugRenderer.Initialize();
if (m->postprocManager.IsEnabled())
m->postprocManager.Initialize();
m->sceneRenderer.Initialize();
return true;
}
void CRenderer::Resize(int width, int height)
{
m_Width = width;
m_Height = height;
m->postprocManager.Resize();
m->sceneRenderer.Resize(width, height);
}
void CRenderer::SetRenderPath(RenderPath rp)
{
if (!m->IsOpen)
{
// Delay until Open() is called.
return;
}
// Renderer has been opened, so validate the selected renderpath
const bool hasShadersSupport =
m->device->GetCapabilities().ARBShaders ||
m->device->GetBackend() != Renderer::Backend::Backend::GL_ARB;
if (rp == RenderPath::DEFAULT)
{
if (hasShadersSupport)
rp = RenderPath::SHADER;
else
rp = RenderPath::FIXED;
}
if (rp == RenderPath::SHADER)
{
if (!hasShadersSupport)
{
LOGWARNING("Falling back to fixed function\n");
rp = RenderPath::FIXED;
}
}
// TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere.
g_RenderingOptions.m_RenderPath = rp;
MakeShadersDirty();
}
bool CRenderer::ShouldRender() const
{
return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
}
void CRenderer::RenderFrame(const bool needsPresent)
{
// Do not render if not focused while in fullscreen or minimised,
// as that triggers a difficult-to-reproduce crash on some graphic cards.
if (!ShouldRender())
return;
if (m_ScreenShotType == ScreenShotType::BIG)
{
RenderBigScreenShot(needsPresent);
}
else if (m_ScreenShotType == ScreenShotType::DEFAULT)
{
RenderScreenShot(needsPresent);
}
else
{
if (needsPresent)
{
// In case of no acquired backbuffer we have nothing render to.
if (!m->device->AcquireNextBackbuffer())
return;
}
if (m_ShouldPreloadResourcesBeforeNextFrame)
{
m_ShouldPreloadResourcesBeforeNextFrame = false;
// We don't need to render logger for the preload.
RenderFrameImpl(true, false);
}
RenderFrameImpl(true, true);
m->deviceCommandContext->Flush();
if (needsPresent)
m->device->Present();
}
}
void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
{
PROFILE3("render");
g_Profiler2.RecordGPUFrameStart();
g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get());
m->textureManager.MakeUploadProgress(m->deviceCommandContext.get());
// prepare before starting the renderer frame
if (g_Game && g_Game->IsGameStarted())
g_Game->GetView()->BeginFrame();
if (g_Game)
m->sceneRenderer.SetSimulation(g_Game->GetSimulation2());
// start new frame
BeginFrame();
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->Prepare(m->deviceCommandContext.get());
Renderer::Backend::IFramebuffer* framebuffer = nullptr;
CPostprocManager& postprocManager = GetPostprocManager();
if (postprocManager.IsEnabled())
{
// We have to update the post process manager with real near/far planes
// that we use for the scene rendering.
postprocManager.SetDepthBufferClipPlanes(
m->sceneRenderer.GetViewCamera().GetNearPlane(),
m->sceneRenderer.GetViewCamera().GetFarPlane()
);
postprocManager.Initialize();
framebuffer = postprocManager.PrepareAndGetOutputFramebuffer();
}
else
{
// We don't need to clear the color attachment of the framebuffer as the sky
// is going to be rendered anyway.
framebuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::CLEAR,
Renderer::Backend::AttachmentStoreOp::DONT_CARE);
}
m->deviceCommandContext->BeginFramebufferPass(framebuffer);
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
viewportRect.width = m_Width;
viewportRect.height = m_Height;
m->deviceCommandContext->SetViewports(1, &viewportRect);
g_Game->GetView()->Render(m->deviceCommandContext.get());
if (postprocManager.IsEnabled())
{
m->deviceCommandContext->EndFramebufferPass();
if (postprocManager.IsMultisampleEnabled())
postprocManager.ResolveMultisampleFramebuffer(m->deviceCommandContext.get());
postprocManager.ApplyPostproc(m->deviceCommandContext.get());
Renderer::Backend::IFramebuffer* backbuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::LOAD,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::LOAD,
Renderer::Backend::AttachmentStoreOp::DONT_CARE);
postprocManager.BlitOutputFramebuffer(
m->deviceCommandContext.get(), backbuffer);
m->deviceCommandContext->BeginFramebufferPass(backbuffer);
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
viewportRect.width = m_Width;
viewportRect.height = m_Height;
m->deviceCommandContext->SetViewports(1, &viewportRect);
}
g_Game->GetView()->RenderOverlays(m->deviceCommandContext.get());
g_Game->GetView()->GetCinema()->Render();
}
else
{
// We have a fullscreen background in our UI so we don't need
// to clear the color attachment.
// We don't need a depth test to render so we don't care about the
// depth-stencil attachment content.
// In case of Atlas we don't have g_Game, so we still need to clear depth.
const Renderer::Backend::AttachmentLoadOp depthStencilLoadOp =
g_AtlasGameLoop && g_AtlasGameLoop->view
? Renderer::Backend::AttachmentLoadOp::CLEAR
: Renderer::Backend::AttachmentLoadOp::DONT_CARE;
Renderer::Backend::IFramebuffer* backbuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
depthStencilLoadOp,
Renderer::Backend::AttachmentStoreOp::DONT_CARE);
m->deviceCommandContext->BeginFramebufferPass(backbuffer);
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
viewportRect.width = m_Width;
viewportRect.height = m_Height;
m->deviceCommandContext->SetViewports(1, &viewportRect);
}
// If we're in Atlas game view, render special tools
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
g_AtlasGameLoop->view->DrawCinemaPathTool();
}
RenderFrame2D(renderGUI, renderLogger);
m->deviceCommandContext->EndFramebufferPass();
EndFrame();
const Stats& stats = GetStats();
PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls);
PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris);
PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris);
PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris);
PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris);
PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats);
PROFILE2_ATTR("particles: %zu", stats.m_Particles);
g_Profiler2.RecordGPUFrameEnd();
}
void CRenderer::RenderFrame2D(const bool renderGUI, const bool renderLogger)
{
CCanvas2D canvas(g_xres, g_yres, g_VideoMode.GetScale(), m->deviceCommandContext.get());
m->sceneRenderer.RenderTextOverlays(canvas);
if (renderGUI)
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render GUI");
// All GUI elements are drawn in Z order to render semi-transparent
// objects correctly.
g_GUI->Draw(canvas);
}
// If we're in Atlas game view, render special overlays (e.g. editor bandbox).
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
g_AtlasGameLoop->view->DrawOverlays(canvas);
}
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render console");
g_Console->Render(canvas);
}
if (renderLogger)
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render logger");
g_Logger->Render(canvas);
}
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render profiler");
// Profile information
g_ProfileViewer.RenderProfile(canvas);
}
}
void CRenderer::RenderScreenShot(const bool needsPresent)
{
m_ScreenShotType = ScreenShotType::NONE;
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath filenameFormat(L"screenshots/screenshot%04d.png");
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
const size_t width = static_cast<size_t>(g_xres), height = static_cast<size_t>(g_yres);
const size_t bpp = 24;
if (needsPresent && !m->device->AcquireNextBackbuffer())
return;
// Hide log messages and re-render
RenderFrameImpl(true, false);
const size_t img_size = width * height * bpp / 8;
const size_t hdr_size = tex_hdr_size(filename);
std::shared_ptr<u8> buf;
AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
void* img = buf.get() + hdr_size;
Tex t;
if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0)
return;
m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img);
m->deviceCommandContext->Flush();
if (needsPresent)
m->device->Present();
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
}
void CRenderer::RenderBigScreenShot(const bool needsPresent)
{
m_ScreenShotType = ScreenShotType::NONE;
// If the game hasn't started yet then use WriteScreenshot to generate the image.
if (!g_Game)
return RenderScreenShot(needsPresent);
int tiles = 4, tileWidth = 256, tileHeight = 256;
CFG_GET_VAL("screenshot.tiles", tiles);
CFG_GET_VAL("screenshot.tilewidth", tileWidth);
CFG_GET_VAL("screenshot.tileheight", tileHeight);
if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0)
{
LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight);
return;
}
if (g_xres < tileWidth && g_yres < tileHeight)
{
LOGWARNING(
"The window size is too small for a big screenshot, increase the"
" window size %dx%d or decrease the tile size %dx%d",
g_xres, g_yres, tileWidth, tileHeight);
return;
}
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp");
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles;
const int bpp = 24;
const size_t imageSize = imageWidth * imageHeight * bpp / 8;
const size_t tileSize = tileWidth * tileHeight * bpp / 8;
const size_t headerSize = tex_hdr_size(filename);
void* tileData = malloc(tileSize);
if (!tileData)
{
WARN_IF_ERR(ERR::NO_MEM);
return;
}
std::shared_ptr<u8> imageBuffer;
AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize);
Tex t;
void* img = imageBuffer.get() + headerSize;
if (t.wrap(imageWidth, imageHeight, bpp, TEX_BOTTOM_UP, imageBuffer, headerSize) < 0)
{
free(tileData);
return;
}
CCamera oldCamera = *g_Game->GetView()->GetCamera();
// Resize various things so that the sizes and aspect ratios are correct
{
g_Renderer.Resize(tileWidth, tileHeight);
SViewPort vp = { 0, 0, tileWidth, tileHeight };
g_Game->GetView()->SetViewport(vp);
}
// Render each tile
CMatrix3D projection;
projection.SetIdentity();
const float aspectRatio = 1.0f * tileWidth / tileHeight;
for (int tileY = 0; tileY < tiles; ++tileY)
{
for (int tileX = 0; tileX < tiles; ++tileX)
{
// Adjust the camera to render the appropriate region
if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
projection.SetPerspectiveTile(
oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(),
tiles, tileX, tileY);
}
g_Game->GetView()->GetCamera()->SetProjection(projection);
if (!needsPresent || m->device->AcquireNextBackbuffer())
{
RenderFrameImpl(false, false);
m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData);
m->deviceCommandContext->Flush();
if (needsPresent)
m->device->Present();
}
// Copy the tile pixels into the main image
for (int y = 0; y < tileHeight; ++y)
{
void* dest = static_cast<char*>(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8;
void* src = static_cast<char*>(tileData) + y * tileWidth * bpp / 8;
memcpy(dest, src, tileWidth * bpp / 8);
}
}
}
// Restore the viewport settings
{
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->SetViewport(vp);
g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
}
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
free(tileData);
}
void CRenderer::BeginFrame()
{
PROFILE("begin frame");
// Zero out all the per-frame stats.
m_Stats.Reset();
if (m->ShadersDirty)
ReloadShaders();
m->sceneRenderer.BeginFrame();
}
void CRenderer::EndFrame()
{
PROFILE3("end frame");
m->sceneRenderer.EndFrame();
}
void CRenderer::MakeShadersDirty()
{
m->ShadersDirty = true;
m->sceneRenderer.MakeShadersDirty();
}
CTextureManager& CRenderer::GetTextureManager()
{
return m->textureManager;
}
CShaderManager& CRenderer::GetShaderManager()
{
return m->shaderManager;
}
CTimeManager& CRenderer::GetTimeManager()
{
return m->timeManager;
}
CPostprocManager& CRenderer::GetPostprocManager()
{
return m->postprocManager;
}
CSceneRenderer& CRenderer::GetSceneRenderer()
{
return m->sceneRenderer;
}
CDebugRenderer& CRenderer::GetDebugRenderer()
{
return m->debugRenderer;
}
CFontManager& CRenderer::GetFontManager()
{
return m->fontManager;
}
void CRenderer::PreloadResourcesBeforeNextFrame()
{
m_ShouldPreloadResourcesBeforeNextFrame = true;
}
void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType)
{
m_ScreenShotType = screenShotType;
}
Renderer::Backend::IDeviceCommandContext* CRenderer::GetDeviceCommandContext()
{
return m->deviceCommandContext.get();
}
Renderer::Backend::IVertexInputLayout* CRenderer::GetVertexInputLayout(
const PS::span<const Renderer::Backend::SVertexAttributeFormat> attributes)
{
const auto [it, inserted] = m->vertexInputLayouts.emplace(
std::vector<Renderer::Backend::SVertexAttributeFormat>{attributes.begin(), attributes.end()}, nullptr);
if (inserted)
it->second = m->device->CreateVertexInputLayout(attributes);
return it->second.get();
}