1
0
forked from 0ad/0ad
0ad/source/renderer/Renderer.cpp
wraitii 330b570ba8 Remove RENDERDATA_UPDATE_COLOR, which is not used, and cleanup.
RENDERDATA_UPDATE_COLOR was used to precompute lightEnv-dependent data
on the CPU. This is no longer done following engine upgrades, and in
particular d7d02a4740 which explictly always did this on the GPU.

ModelAbstract had a 'SetDirtyRec' hack for it because of decals, which
can also be removed. The 'dirty' bit of CRenderableObject is renderdata
for the specific item, never its props, so it never actually needs to be
recursive.

CheckLightEnv is also useless as a result, and removed.

Differential Revision: https://code.wildfiregames.com/D4453
This was SVN commit r26249.
2022-01-25 16:59:29 +00:00

835 lines
20 KiB
C++

/* Copyright (C) 2022 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/ogl.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/gl/Device.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();
CStr GetTitle();
size_t GetNumberRows();
const std::vector<ProfileColumn>& GetColumns();
CStr GetCellText(size_t row, size_t col);
AbstractProfileTable* GetChild(size_t row);
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:
/// 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;
std::unique_ptr<Renderer::Backend::GL::CDeviceCommandContext> deviceCommandContext;
Internals() :
IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false)
{
}
};
CRenderer::CRenderer()
{
TIMER(L"InitRenderer");
m = std::make_unique<Internals>();
g_ProfileViewer.AddRootTable(&m->profileTable);
m_Width = 0;
m_Height = 0;
m_Stats.Reset();
// Create terrain related stuff.
new CTerrainTextureManager;
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);
// I haven't seen the camera affecting GUI rendering and such, but the
// viewport has to be updated according to the video mode
SViewPort vp;
vp.m_X = 0;
vp.m_Y = 0;
vp.m_Width = g_xres;
vp.m_Height = g_yres;
SetViewport(vp);
ModelDefActivateFastImpl();
ColorActivateFastImpl();
ModelRenderer::Init();
}
CRenderer::~CRenderer()
{
// 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::EnumCaps()
{
// assume support for nothing
m_Caps.m_ARBProgram = false;
m_Caps.m_ARBProgramShadow = false;
m_Caps.m_VertexShader = false;
m_Caps.m_FragmentShader = false;
m_Caps.m_Shadows = false;
m_Caps.m_PrettyWater = false;
// now start querying extensions
if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL))
{
m_Caps.m_ARBProgram = true;
if (ogl_HaveExtension("GL_ARB_fragment_program_shadow"))
m_Caps.m_ARBProgramShadow = true;
}
// GLSL shaders are in core since GL2.0.
if (ogl_HaveVersion(2, 0))
m_Caps.m_VertexShader = m_Caps.m_FragmentShader = true;
#if CONFIG2_GLES
m_Caps.m_Shadows = true;
#else
if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", NULL))
{
if (ogl_max_tex_units >= 4)
m_Caps.m_Shadows = true;
}
#endif
#if CONFIG2_GLES
m_Caps.m_PrettyWater = true;
#else
if (m_Caps.m_VertexShader && m_Caps.m_FragmentShader)
m_Caps.m_PrettyWater = true;
#endif
}
void CRenderer::ReloadShaders()
{
ENSURE(m->IsOpen);
m->sceneRenderer.ReloadShaders();
m->ShadersDirty = false;
}
bool CRenderer::Open(int width, int height)
{
m->IsOpen = true;
// Must query card capabilities before creating renderers that depend
// on card capabilities.
EnumCaps();
// Dimensions
m_Width = width;
m_Height = height;
// Validate the currently selected render path
SetRenderPath(g_RenderingOptions.GetRenderPath());
m->deviceCommandContext = Renderer::Backend::GL::CDeviceCommandContext::Create();
if (g_RenderingOptions.GetPostProc())
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
if (rp == RenderPath::DEFAULT)
{
if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB))
rp = RenderPath::SHADER;
else
rp = RenderPath::FIXED;
}
if (rp == RenderPath::SHADER)
{
if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB)))
{
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_ShouldPreloadResourcesBeforeNextFrame)
{
m_ShouldPreloadResourcesBeforeNextFrame = false;
// We don't meed to render logger for the preload.
RenderFrameImpl(true, false);
}
if (m_ScreenShotType == ScreenShotType::BIG)
{
RenderBigScreenShot(needsPresent);
}
else
{
if (m_ScreenShotType == ScreenShotType::DEFAULT)
RenderScreenShot();
else
RenderFrameImpl(true, true);
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
}
}
void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
{
PROFILE3("render");
g_Profiler2.RecordGPUFrameStart();
ogl_WarnIfError();
g_TexMan.UploadResourcesIfNeeded(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();
ogl_WarnIfError();
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->Render();
ogl_WarnIfError();
}
m->sceneRenderer.RenderTextOverlays();
// If we're in Atlas game view, render special tools
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
g_AtlasGameLoop->view->DrawCinemaPathTool();
ogl_WarnIfError();
}
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->GetCinema()->Render();
ogl_WarnIfError();
}
glDisable(GL_DEPTH_TEST);
if (renderGUI)
{
OGL_SCOPED_DEBUG_GROUP("Draw GUI");
// All GUI elements are drawn in Z order to render semi-transparent
// objects correctly.
g_GUI->Draw();
ogl_WarnIfError();
}
// If we're in Atlas game view, render special overlays (e.g. editor bandbox).
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
CCanvas2D canvas;
g_AtlasGameLoop->view->DrawOverlays(canvas);
ogl_WarnIfError();
}
g_Console->Render();
ogl_WarnIfError();
if (renderLogger)
{
g_Logger->Render();
ogl_WarnIfError();
}
// Profile information
g_ProfileViewer.RenderProfile();
ogl_WarnIfError();
glEnable(GL_DEPTH_TEST);
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);
ogl_WarnIfError();
g_Profiler2.RecordGPUFrameEnd();
ogl_WarnIfError();
}
void CRenderer::RenderScreenShot()
{
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 w = (size_t)g_xres, h = (size_t)g_yres;
const size_t bpp = 24;
GLenum fmt = GL_RGB;
int flags = TEX_BOTTOM_UP;
// Hide log messages and re-render
RenderFrameImpl(true, false);
const size_t img_size = w * h * bpp / 8;
const size_t hdr_size = tex_hdr_size(filename);
std::shared_ptr<u8> buf;
AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
GLvoid* img = buf.get() + hdr_size;
Tex t;
if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
return;
glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
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();
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;
}
// 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);
// Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
// hope the screen is actually large enough for that.
ENSURE(g_xres >= tileWidth && g_yres >= tileHeight);
const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles;
const int bpp = 24;
// we want writing BMP to be as fast as possible,
// so read data from OpenGL in BMP format to obviate conversion.
#if CONFIG2_GLES // GLES doesn't support BGR
const GLenum fmt = GL_RGB;
const int flags = TEX_BOTTOM_UP;
#else
const GLenum fmt = GL_BGR;
const int flags = TEX_BOTTOM_UP | TEX_BGR;
#endif
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;
GLvoid* img = imageBuffer.get() + headerSize;
if (t.wrap(imageWidth, imageHeight, bpp, flags, imageBuffer, headerSize) < 0)
{
free(tileData);
return;
}
ogl_WarnIfError();
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);
RenderFrameImpl(false, false);
// Copy the tile pixels into the main image
glReadPixels(0, 0, tileWidth, tileHeight, fmt, GL_UNSIGNED_BYTE, tileData);
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);
}
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
}
}
// 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();
BindTexture(0, 0);
}
void CRenderer::SetViewport(const SViewPort &vp)
{
m_Viewport = vp;
glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height);
}
SViewPort CRenderer::GetViewport()
{
return m_Viewport;
}
void CRenderer::BindTexture(int unit, GLuint tex)
{
glActiveTextureARB(GL_TEXTURE0+unit);
glBindTexture(GL_TEXTURE_2D, tex);
}
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::GL::CDeviceCommandContext* CRenderer::GetDeviceCommandContext()
{
return m->deviceCommandContext.get();
}