Index: ps/trunk/source/renderer/Renderer.cpp
===================================================================
--- ps/trunk/source/renderer/Renderer.cpp (revision 26801)
+++ ps/trunk/source/renderer/Renderer.cpp (revision 26802)
@@ -1,805 +1,799 @@
/* 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 .
*/
#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
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& 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 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& 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:
std::unique_ptr 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;
Internals() :
IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats),
deviceCommandContext(g_VideoMode.GetBackendDevice()->CreateCommandContext()),
textureManager(g_VFS, false, false)
{
}
};
CRenderer::CRenderer()
{
TIMER(L"InitRenderer");
m = std::make_unique();
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()
{
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->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());
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 =
g_VideoMode.GetBackendDevice()->GetCapabilities().ARBShaders ||
g_VideoMode.GetBackend() != CVideoMode::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_ShouldPreloadResourcesBeforeNextFrame)
{
m_ShouldPreloadResourcesBeforeNextFrame = false;
- // We don't meed to render logger for the preload.
+ // We don't need to render logger for the preload.
RenderFrameImpl(true, false);
}
if (m_ScreenShotType == ScreenShotType::BIG)
{
RenderBigScreenShot(needsPresent);
}
+ else if (m_ScreenShotType == ScreenShotType::DEFAULT)
+ {
+ RenderScreenShot(needsPresent);
+ }
else
{
- if (m_ScreenShotType == ScreenShotType::DEFAULT)
- RenderScreenShot();
- else
- RenderFrameImpl(true, true);
+ 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());
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();
ogl_WarnIfError();
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->Render();
ogl_WarnIfError();
}
m->deviceCommandContext->SetFramebuffer(
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
// 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();
}
RenderFrame2D(renderGUI, renderLogger);
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::RenderFrame2D(const bool renderGUI, const bool renderLogger)
{
CCanvas2D canvas(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);
ogl_WarnIfError();
}
// 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);
ogl_WarnIfError();
}
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render console");
g_Console->Render(canvas);
ogl_WarnIfError();
}
if (renderLogger)
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render logger");
g_Logger->Render(canvas);
ogl_WarnIfError();
}
{
GPU_SCOPED_LABEL(m->deviceCommandContext.get(), "Render profiler");
// Profile information
g_ProfileViewer.RenderProfile(canvas);
ogl_WarnIfError();
}
}
-void CRenderer::RenderScreenShot()
+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 w = (size_t)g_xres, h = (size_t)g_yres;
+ const size_t width = static_cast(g_xres), height = static_cast(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 img_size = width * height * bpp / 8;
const size_t hdr_size = tex_hdr_size(filename);
std::shared_ptr buf;
AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
- GLvoid* img = buf.get() + hdr_size;
+ void* img = buf.get() + hdr_size;
Tex t;
- if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
+ if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0)
return;
- glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
+
+ m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img);
+ m->deviceCommandContext->Flush();
+ if (needsPresent)
+ g_VideoMode.GetBackendDevice()->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();
+ 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;
}
// 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 imageBuffer;
AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize);
Tex t;
GLvoid* img = imageBuffer.get() + headerSize;
- if (t.wrap(imageWidth, imageHeight, bpp, flags, imageBuffer, headerSize) < 0)
+ if (t.wrap(imageWidth, imageHeight, bpp, TEX_BOTTOM_UP, 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);
+ m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData);
+ m->deviceCommandContext->Flush();
+ if (needsPresent)
+ g_VideoMode.GetBackendDevice()->Present();
+
// 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(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8;
void* src = static_cast(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();
}
void CRenderer::SetViewport(const SViewPort &vp)
{
m_Viewport = vp;
Renderer::Backend::GL::CDeviceCommandContext::Rect viewportRect;
viewportRect.x = vp.m_X;
viewportRect.y = vp.m_Y;
viewportRect.width = vp.m_Width;
viewportRect.height = vp.m_Height;
m->deviceCommandContext->SetViewports(1, &viewportRect);
}
SViewPort CRenderer::GetViewport()
{
return m_Viewport;
}
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();
}
Index: ps/trunk/source/renderer/Renderer.h
===================================================================
--- ps/trunk/source/renderer/Renderer.h (revision 26801)
+++ ps/trunk/source/renderer/Renderer.h (revision 26802)
@@ -1,181 +1,181 @@
/* 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 .
*/
#ifndef INCLUDED_RENDERER
#define INCLUDED_RENDERER
#include "graphics/Camera.h"
#include "graphics/ShaderDefines.h"
#include "graphics/ShaderProgramPtr.h"
#include "ps/Singleton.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/RenderingOptions.h"
#include "renderer/Scene.h"
#include
class CDebugRenderer;
class CFontManager;
class CPostprocManager;
class CSceneRenderer;
class CShaderManager;
class CTextureManager;
class CTimeManager;
#define g_Renderer CRenderer::GetSingleton()
/**
* Higher level interface on top of the whole frame rendering. It does know
* what should be rendered and via which renderer but shouldn't know how to
* render a particular area, like UI or scene.
*/
class CRenderer : public Singleton
{
public:
// stats class - per frame counts of number of draw calls, poly counts etc
struct Stats
{
// set all stats to zero
void Reset() { memset(this, 0, sizeof(*this)); }
// number of draw calls per frame - total DrawElements + Begin/End immediate mode loops
size_t m_DrawCalls;
// number of terrain triangles drawn
size_t m_TerrainTris;
// number of water triangles drawn
size_t m_WaterTris;
// number of (non-transparent) model triangles drawn
size_t m_ModelTris;
// number of overlay triangles drawn
size_t m_OverlayTris;
// number of splat passes for alphamapping
size_t m_BlendSplats;
// number of particles
size_t m_Particles;
};
enum class ScreenShotType
{
NONE,
DEFAULT,
BIG
};
public:
CRenderer();
~CRenderer();
// open up the renderer: performs any necessary initialisation
bool Open(int width, int height);
// resize renderer view
void Resize(int width, int height);
// return view width
int GetWidth() const { return m_Width; }
// return view height
int GetHeight() const { return m_Height; }
void RenderFrame(bool needsPresent);
// signal frame start
void BeginFrame();
// signal frame end
void EndFrame();
// trigger a reload of shaders (when parameters they depend on have changed)
void MakeShadersDirty();
// set the viewport
void SetViewport(const SViewPort &);
// get the last viewport
SViewPort GetViewport();
// return stats accumulated for current frame
Stats& GetStats() { return m_Stats; }
CTextureManager& GetTextureManager();
CShaderManager& GetShaderManager();
CFontManager& GetFontManager();
CTimeManager& GetTimeManager();
CPostprocManager& GetPostprocManager();
CSceneRenderer& GetSceneRenderer();
CDebugRenderer& GetDebugRenderer();
/**
* Performs a complete frame without presenting to force loading all needed
* resources. It's used for the first frame on a game start.
* TODO: It might be better to preload resources without a complete frame
* rendering.
*/
void PreloadResourcesBeforeNextFrame();
/**
* Makes a screenshot on the next RenderFrame according of the given
* screenshot type.
*/
void MakeScreenShotOnNextFrame(ScreenShotType screenShotType);
Renderer::Backend::GL::CDeviceCommandContext* GetDeviceCommandContext();
protected:
friend class CPatchRData;
friend class CDecalRData;
friend class HWLightingModelRenderer;
friend class ShaderModelVertexRenderer;
friend class InstancingModelRenderer;
friend class CRenderingOptions;
bool ShouldRender() const;
void RenderFrameImpl(const bool renderGUI, const bool renderLogger);
void RenderFrame2D(const bool renderGUI, const bool renderLogger);
- void RenderScreenShot();
+ void RenderScreenShot(const bool needsPresent);
void RenderBigScreenShot(const bool needsPresent);
// SetRenderPath: Select the preferred render path.
// This may only be called before Open(), because the layout of vertex arrays and other
// data may depend on the chosen render path.
void SetRenderPath(RenderPath rp);
void ReloadShaders();
// Private data that is not needed by inline functions.
class Internals;
std::unique_ptr m;
// view width
int m_Width = 0;
// view height
int m_Height = 0;
SViewPort m_Viewport;
// per-frame renderer stats
Stats m_Stats;
bool m_ShouldPreloadResourcesBeforeNextFrame = false;
ScreenShotType m_ScreenShotType = ScreenShotType::NONE;
};
#endif // INCLUDED_RENDERER
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26801)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26802)
@@ -1,866 +1,875 @@
/* 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 .
*/
#include "precompiled.h"
#include "DeviceCommandContext.h"
#include "ps/CLogger.h"
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Mapping.h"
#include "renderer/backend/gl/ShaderProgram.h"
#include "renderer/backend/gl/Texture.h"
#include
#include
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
bool operator==(const StencilOpState& lhs, const StencilOpState& rhs)
{
return
lhs.failOp == rhs.failOp &&
lhs.passOp == rhs.passOp &&
lhs.depthFailOp == rhs.depthFailOp &&
lhs.compareOp == rhs.compareOp;
}
bool operator!=(const StencilOpState& lhs, const StencilOpState& rhs)
{
return !operator==(lhs, rhs);
}
bool operator==(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return
lhs.x == rhs.x && lhs.y == rhs.y &&
lhs.width == rhs.width && lhs.height == rhs.height;
}
bool operator!=(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return !operator==(lhs, rhs);
}
void ApplyDepthMask(const bool depthWriteEnabled)
{
glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE);
}
void ApplyColorMask(const uint8_t colorWriteMask)
{
glColorMask(
(colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE,
(colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE);
}
void ApplyStencilMask(const uint32_t stencilWriteMask)
{
glStencilMask(stencilWriteMask);
}
GLenum BufferTypeToGLTarget(const CBuffer::Type type)
{
GLenum target = GL_ARRAY_BUFFER;
switch (type)
{
case CBuffer::Type::VERTEX:
target = GL_ARRAY_BUFFER;
break;
case CBuffer::Type::INDEX:
target = GL_ELEMENT_ARRAY_BUFFER;
break;
};
return target;
}
void UploadBufferRegionImpl(
const GLenum target, const uint32_t dataOffset, const uint32_t dataSize,
const CDeviceCommandContext::UploadBufferFunction& uploadFunction)
{
ENSURE(dataOffset < dataSize);
while (true)
{
void* mappedData = glMapBufferARB(target, GL_WRITE_ONLY);
if (mappedData == nullptr)
{
// This shouldn't happen unless we run out of virtual address space
LOGERROR("glMapBuffer failed");
break;
}
uploadFunction(static_cast(mappedData) + dataOffset);
if (glUnmapBufferARB(target) == GL_TRUE)
break;
// Unmap might fail on e.g. resolution switches, so just try again
// and hope it will eventually succeed
LOGMESSAGE("glUnmapBuffer failed, trying again...\n");
}
}
} // anonymous namespace
// static
std::unique_ptr CDeviceCommandContext::Create(CDevice* device)
{
std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device));
deviceCommandContext->m_Framebuffer = device->GetCurrentBackbuffer();
deviceCommandContext->ResetStates();
return deviceCommandContext;
}
CDeviceCommandContext::CDeviceCommandContext(CDevice* device)
: m_Device(device)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
for (std::pair& unit : m_BoundTextures)
unit.first = unit.second = 0;
}
CDeviceCommandContext::~CDeviceCommandContext() = default;
void CDeviceCommandContext::SetGraphicsPipelineState(
const GraphicsPipelineStateDesc& pipelineStateDesc)
{
SetGraphicsPipelineStateImpl(pipelineStateDesc, false);
}
void CDeviceCommandContext::UploadTexture(
CTexture* texture, const Format format,
const void* data, const size_t dataSize,
const uint32_t level, const uint32_t layer)
{
UploadTextureRegion(texture, format, data, dataSize,
0, 0,
std::max(1u, texture->GetWidth() >> level),
std::max(1u, texture->GetHeight() >> level),
level, layer);
}
void CDeviceCommandContext::UploadTextureRegion(
CTexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t xOffset, const uint32_t yOffset,
const uint32_t width, const uint32_t height,
const uint32_t level, const uint32_t layer)
{
ENSURE(texture);
ENSURE(width > 0 && height > 0);
if (texture->GetType() == CTexture::Type::TEXTURE_2D)
{
ENSURE(layer == 0);
if (texture->GetFormat() == Format::R8G8B8A8_UNORM ||
texture->GetFormat() == Format::R8G8B8_UNORM ||
texture->GetFormat() == Format::A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
size_t bytesPerPixel = 4;
GLenum pixelFormat = GL_RGBA;
switch (dataFormat)
{
case Format::R8G8B8A8_UNORM:
break;
case Format::R8G8B8_UNORM:
pixelFormat = GL_RGB;
bytesPerPixel = 3;
break;
case Format::A8_UNORM:
pixelFormat = GL_ALPHA;
bytesPerPixel = 1;
break;
case Format::L8_UNORM:
pixelFormat = GL_LUMINANCE;
bytesPerPixel = 1;
break;
default:
debug_warn("Unexpected format.");
break;
}
ENSURE(dataSize == width * height * bytesPerPixel);
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, level,
xOffset, yOffset, width, height,
pixelFormat, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
else if (
texture->GetFormat() == Format::BC1_RGB_UNORM ||
texture->GetFormat() == Format::BC1_RGBA_UNORM ||
texture->GetFormat() == Format::BC2_UNORM ||
texture->GetFormat() == Format::BC3_UNORM)
{
ENSURE(xOffset == 0 && yOffset == 0);
ENSURE(texture->GetFormat() == dataFormat);
// TODO: add data size check.
GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
switch (texture->GetFormat())
{
case Format::BC1_RGBA_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
case Format::BC2_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case Format::BC3_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
break;
}
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE)
{
if (texture->GetFormat() == Format::R8G8B8A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
ENSURE(level == 0 && layer < 6);
ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height);
const size_t bpp = 4;
ENSURE(dataSize == width * height * bpp);
// The order of layers should be the following:
// front, back, top, bottom, right, left
static const GLenum targets[6] =
{
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle());
glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else
debug_warn("Unsupported type");
}
void CDeviceCommandContext::UploadBuffer(CBuffer* buffer, const void* data, const uint32_t dataSize)
{
UploadBufferRegion(buffer, data, dataSize, 0);
}
void CDeviceCommandContext::UploadBuffer(
CBuffer* buffer, const UploadBufferFunction& uploadFunction)
{
UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction);
}
void CDeviceCommandContext::UploadBufferRegion(
CBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize)
{
ENSURE(data);
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
glBindBufferARB(target, buffer->GetHandle());
if (buffer->IsDynamic())
{
// Tell the driver that it can reallocate the whole VBO
glBufferDataARB(target, buffer->GetSize(), nullptr, buffer->IsDynamic() ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
// (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used
// here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with
// current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do
// the explicit glBufferData.)
UploadBufferRegion(buffer, dataOffset, dataSize, [data, dataSize](u8* mappedData)
{
std::memcpy(mappedData, data, dataSize);
});
}
else
{
glBufferSubDataARB(target, dataOffset, dataSize, data);
}
glBindBufferARB(target, 0);
}
void CDeviceCommandContext::UploadBufferRegion(
CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction)
{
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
glBindBufferARB(target, buffer->GetHandle());
ENSURE(buffer->IsDynamic());
UploadBufferRegionImpl(target, dataOffset, dataSize, uploadFunction);
glBindBufferARB(target, 0);
}
void CDeviceCommandContext::BeginScopedLabel(const char* name)
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
++m_ScopedLabelDepth;
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name);
}
void CDeviceCommandContext::EndScopedLabel()
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
ENSURE(m_ScopedLabelDepth > 0);
--m_ScopedLabelDepth;
glPopDebugGroup();
}
void CDeviceCommandContext::BindTexture(const uint32_t unit, const GLenum target, const GLuint handle)
{
ENSURE(unit < m_BoundTextures.size());
#if CONFIG2_GLES
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP);
#else
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE);
#endif
if (m_BoundTextures[unit].first == target && m_BoundTextures[unit].second == handle)
return;
if (m_ActiveTextureUnit != unit)
{
glActiveTexture(GL_TEXTURE0 + unit);
m_ActiveTextureUnit = unit;
}
if (m_BoundTextures[unit].first != target && m_BoundTextures[unit].first && m_BoundTextures[unit].second)
glBindTexture(m_BoundTextures[unit].first, 0);
if (m_BoundTextures[unit].second != handle)
glBindTexture(target, handle);
m_BoundTextures[unit] = {target, handle};
}
void CDeviceCommandContext::BindBuffer(const CBuffer::Type type, CBuffer* buffer)
{
ENSURE(!buffer || buffer->GetType() == type);
if (type == CBuffer::Type::INDEX)
{
if (!buffer)
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
}
glBindBufferARB(BufferTypeToGLTarget(type), buffer ? buffer->GetHandle() : 0);
}
void CDeviceCommandContext::OnTextureDestroy(CTexture* texture)
{
ENSURE(texture);
for (size_t index = 0; index < m_BoundTextures.size(); ++index)
if (m_BoundTextures[index].second == texture->GetHandle())
BindTexture(index, GL_TEXTURE_2D, 0);
}
void CDeviceCommandContext::Flush()
{
ResetStates();
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
BindTexture(0, GL_TEXTURE_2D, 0);
BindBuffer(CBuffer::Type::INDEX, nullptr);
BindBuffer(CBuffer::Type::VERTEX, nullptr);
ENSURE(m_ScopedLabelDepth == 0);
}
void CDeviceCommandContext::ResetStates()
{
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true);
SetScissors(0, nullptr);
SetFramebuffer(m_Device->GetCurrentBackbuffer());
}
void CDeviceCommandContext::SetGraphicsPipelineStateImpl(
const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force)
{
ENSURE(!m_InsidePass);
if (m_GraphicsPipelineStateDesc.shaderProgram != pipelineStateDesc.shaderProgram)
{
CShaderProgram* currentShaderProgram = nullptr;
if (m_GraphicsPipelineStateDesc.shaderProgram)
{
currentShaderProgram =
static_cast(m_GraphicsPipelineStateDesc.shaderProgram);
}
CShaderProgram* nextShaderProgram = nullptr;
if (pipelineStateDesc.shaderProgram)
{
nextShaderProgram =
static_cast(pipelineStateDesc.shaderProgram);
}
if (nextShaderProgram)
nextShaderProgram->Bind(currentShaderProgram);
else if (currentShaderProgram)
currentShaderProgram->Unbind();
}
const DepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState;
const DepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState;
if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled)
{
if (nextDepthStencilStateDesc.depthTestEnabled)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
}
if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp)
{
glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp));
}
if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled)
{
ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled);
}
if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled)
{
if (nextDepthStencilStateDesc.stencilTestEnabled)
glEnable(GL_STENCIL_TEST);
else
glDisable(GL_STENCIL_TEST);
}
if (force ||
currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace ||
currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOp(
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
else
{
if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace)
{
glStencilOpSeparate(
GL_FRONT,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOpSeparate(
GL_BACK,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp));
}
}
}
if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask)
{
ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask);
}
if (force ||
currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference ||
currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask ||
currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp ||
currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
glStencilFunc(
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
else
{
glStencilFuncSeparate(GL_FRONT,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
glStencilFuncSeparate(GL_BACK,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
}
const BlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState;
const BlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState;
if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled)
{
if (nextBlendStateDesc.enabled)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
}
if (force ||
currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor ||
currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor ||
currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor ||
currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor)
{
if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor &&
nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor)
{
glBlendFunc(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor));
}
else
{
glBlendFuncSeparate(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor));
}
}
if (force ||
currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp ||
currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp)
{
if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp)
{
glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp));
}
else
{
glBlendEquationSeparate(
Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp),
Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp));
}
}
if (force ||
currentBlendStateDesc.constant != nextBlendStateDesc.constant)
{
glBlendColor(
nextBlendStateDesc.constant.r,
nextBlendStateDesc.constant.g,
nextBlendStateDesc.constant.b,
nextBlendStateDesc.constant.a);
}
if (force ||
currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask)
{
ApplyColorMask(nextBlendStateDesc.colorWriteMask);
}
const RasterizationStateDesc& currentRasterizationStateDesc =
m_GraphicsPipelineStateDesc.rasterizationState;
const RasterizationStateDesc& nextRasterizationStateDesc =
pipelineStateDesc.rasterizationState;
if (force ||
currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode)
{
#if !CONFIG2_GLES
glPolygonMode(
GL_FRONT_AND_BACK,
nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL);
#endif
}
if (force ||
currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode)
{
if (nextRasterizationStateDesc.cullMode == CullMode::NONE)
{
glDisable(GL_CULL_FACE);
}
else
{
if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE)
glEnable(GL_CULL_FACE);
glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK);
}
}
if (force ||
currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace)
{
if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE)
glFrontFace(GL_CW);
else
glFrontFace(GL_CCW);
}
#if !CONFIG2_GLES
if (force ||
currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled)
{
if (nextRasterizationStateDesc.depthBiasEnabled)
glEnable(GL_POLYGON_OFFSET_FILL);
else
glDisable(GL_POLYGON_OFFSET_FILL);
}
if (force ||
currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor ||
currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor)
{
glPolygonOffset(
nextRasterizationStateDesc.depthBiasSlopeFactor,
nextRasterizationStateDesc.depthBiasConstantFactor);
}
#endif
m_GraphicsPipelineStateDesc = pipelineStateDesc;
}
void CDeviceCommandContext::BlitFramebuffer(
CFramebuffer* destinationFramebuffer, CFramebuffer* sourceFramebuffer)
{
#if CONFIG2_GLES
UNUSED2(destinationFramebuffer);
UNUSED2(sourceFramebuffer);
debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES");
#else
// Source framebuffer should not be backbuffer.
ENSURE( sourceFramebuffer->GetHandle() != 0);
ENSURE( destinationFramebuffer != sourceFramebuffer );
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
// TODO: add more check for internal formats. And currently we don't support
// scaling inside blit.
glBlitFramebufferEXT(
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
(sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()),
GL_NEAREST);
#endif
}
void CDeviceCommandContext::ClearFramebuffer()
{
ClearFramebuffer(true, true, true);
}
void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil)
{
const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0;
const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0;
const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0;
GLbitfield mask = 0;
if (needsColor)
{
ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA);
glClearColor(
m_Framebuffer->GetClearColor().r,
m_Framebuffer->GetClearColor().g,
m_Framebuffer->GetClearColor().b,
m_Framebuffer->GetClearColor().a);
mask |= GL_COLOR_BUFFER_BIT;
}
if (needsDepth)
{
ApplyDepthMask(true);
mask |= GL_DEPTH_BUFFER_BIT;
}
if (needsStencil)
{
ApplyStencilMask(std::numeric_limits::max());
mask |= GL_STENCIL_BUFFER_BIT;
}
glClear(mask);
if (needsColor)
ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask);
if (needsDepth)
ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled);
if (needsStencil)
ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask);
}
void CDeviceCommandContext::SetFramebuffer(CFramebuffer* framebuffer)
{
ENSURE(framebuffer);
ENSURE(framebuffer->GetHandle() == 0 || (framebuffer->GetWidth() > 0 && framebuffer->GetHeight() > 0));
m_Framebuffer = framebuffer;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->GetHandle());
}
+void CDeviceCommandContext::ReadbackFramebufferSync(
+ const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
+ void* data)
+{
+ ENSURE(m_Framebuffer);
+ glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
+ ogl_WarnIfError();
+}
+
void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors)
{
ENSURE(scissorCount <= 1);
if (scissorCount == 0)
{
if (m_ScissorCount != scissorCount)
glDisable(GL_SCISSOR_TEST);
}
else
{
if (m_ScissorCount != scissorCount)
glEnable(GL_SCISSOR_TEST);
ENSURE(scissors);
if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0])
{
m_Scissors[0] = scissors[0];
glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height);
}
}
m_ScissorCount = scissorCount;
}
void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports)
{
ENSURE(viewportCount == 1);
glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height);
}
void CDeviceCommandContext::SetIndexBuffer(CBuffer* buffer)
{
ENSURE(buffer->GetType() == CBuffer::Type::INDEX);
m_IndexBuffer = buffer;
m_IndexBufferData = nullptr;
BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer);
}
void CDeviceCommandContext::SetIndexBufferData(const void* data)
{
if (m_IndexBuffer)
{
BindBuffer(CBuffer::Type::INDEX, nullptr);
m_IndexBuffer = nullptr;
}
m_IndexBufferData = data;
}
void CDeviceCommandContext::BeginPass()
{
ENSURE(!m_InsidePass);
m_InsidePass = true;
}
void CDeviceCommandContext::EndPass()
{
ENSURE(m_InsidePass);
m_InsidePass = false;
}
void CDeviceCommandContext::Draw(
const uint32_t firstVertex, const uint32_t vertexCount)
{
ENSURE(m_GraphicsPipelineStateDesc.shaderProgram);
ENSURE(m_InsidePass);
// Some drivers apparently don't like count = 0 in glDrawArrays here, so skip
// all drawing in that case.
if (vertexCount == 0)
return;
static_cast(m_GraphicsPipelineStateDesc.shaderProgram)
->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount);
}
void CDeviceCommandContext::DrawIndexed(
const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset)
{
ENSURE(m_GraphicsPipelineStateDesc.shaderProgram);
ENSURE(m_InsidePass);
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
ENSURE(vertexOffset == 0);
if (m_IndexBuffer)
{
ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize());
}
static_cast(m_GraphicsPipelineStateDesc.shaderProgram)
->AssertPointersBound();
// Don't use glMultiDrawElements here since it doesn't have a significant
// performance impact and it suffers from various driver bugs (e.g. it breaks
// in Mesa 7.10 swrast with index VBOs).
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT,
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)));
}
void CDeviceCommandContext::DrawIndexedInRange(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end)
{
ENSURE(m_GraphicsPipelineStateDesc.shaderProgram);
ENSURE(m_InsidePass);
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
const void* indices =
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex));
static_cast(m_GraphicsPipelineStateDesc.shaderProgram)
->AssertPointersBound();
// Draw with DrawRangeElements where available, since it might be more
// efficient for slow hardware.
#if CONFIG2_GLES
UNUSED2(start);
UNUSED2(end);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);
#else
glDrawRangeElementsEXT(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices);
#endif
}
CDeviceCommandContext::ScopedBind::ScopedBind(
CDeviceCommandContext* deviceCommandContext,
const GLenum target, const GLuint handle)
: m_DeviceCommandContext(deviceCommandContext),
m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit])
{
m_DeviceCommandContext->BindTexture(
m_DeviceCommandContext->m_ActiveTextureUnit, target, handle);
}
CDeviceCommandContext::ScopedBind::~ScopedBind()
{
m_DeviceCommandContext->BindTexture(
m_DeviceCommandContext->m_ActiveTextureUnit, m_OldBindUnit.first, m_OldBindUnit.second);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26801)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26802)
@@ -1,185 +1,188 @@
/* 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 .
*/
#ifndef INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
#define INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
#include "lib/ogl.h"
#include "renderer/backend/Format.h"
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/PipelineState.h"
#include
#include
#include
#include
#include
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
class CDevice;
class CFramebuffer;
class CTexture;
class CDeviceCommandContext
{
public:
~CDeviceCommandContext();
CDevice* GetDevice() { return m_Device; }
void SetGraphicsPipelineState(const GraphicsPipelineStateDesc& pipelineStateDesc);
void BlitFramebuffer(CFramebuffer* destinationFramebuffer, CFramebuffer* sourceFramebuffer);
void ClearFramebuffer();
void ClearFramebuffer(const bool color, const bool depth, const bool stencil);
void SetFramebuffer(CFramebuffer* framebuffer);
+ void ReadbackFramebufferSync(
+ const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
+ void* data);
void UploadTexture(CTexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t level = 0, const uint32_t layer = 0);
void UploadTextureRegion(CTexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t xOffset, const uint32_t yOffset,
const uint32_t width, const uint32_t height,
const uint32_t level = 0, const uint32_t layer = 0);
using UploadBufferFunction = std::function;
void UploadBuffer(CBuffer* buffer, const void* data, const uint32_t dataSize);
void UploadBuffer(CBuffer* buffer, const UploadBufferFunction& uploadFunction);
void UploadBufferRegion(
CBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize);
void UploadBufferRegion(
CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction);
// TODO: maybe we should add a more common type, like CRectI.
struct Rect
{
int32_t x, y;
int32_t width, height;
};
void SetScissors(const uint32_t scissorCount, const Rect* scissors);
void SetViewports(const uint32_t viewportCount, const Rect* viewports);
void SetIndexBuffer(CBuffer* buffer);
void SetIndexBufferData(const void* data);
void BeginPass();
void EndPass();
void Draw(const uint32_t firstVertex, const uint32_t vertexCount);
void DrawIndexed(
const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset);
// TODO: should be removed when performance impact is minimal on slow hardware.
void DrawIndexedInRange(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end);
void BeginScopedLabel(const char* name);
void EndScopedLabel();
// TODO: remove direct binding after moving shaders.
void BindTexture(const uint32_t unit, const GLenum target, const GLuint handle);
void BindBuffer(const CBuffer::Type type, CBuffer* buffer);
// We need to know when to invalidate our texture bind cache.
void OnTextureDestroy(CTexture* texture);
void Flush();
private:
friend class CDevice;
static std::unique_ptr Create(CDevice* device);
CDeviceCommandContext(CDevice* device);
void ResetStates();
void SetGraphicsPipelineStateImpl(
const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force);
CDevice* m_Device = nullptr;
GraphicsPipelineStateDesc m_GraphicsPipelineStateDesc{};
CFramebuffer* m_Framebuffer = nullptr;
uint32_t m_ScissorCount = 0;
// GL2.1 doesn't support more than 1 scissor.
std::array m_Scissors;
uint32_t m_ScopedLabelDepth = 0;
CBuffer* m_IndexBuffer = nullptr;
const void* m_IndexBufferData = nullptr;
bool m_InsidePass = false;
uint32_t m_ActiveTextureUnit = 0;
using BindUnit = std::pair;
std::array m_BoundTextures;
class ScopedBind
{
public:
ScopedBind(CDeviceCommandContext* deviceCommandContext,
const GLenum target, const GLuint handle);
~ScopedBind();
private:
CDeviceCommandContext* m_DeviceCommandContext = nullptr;
BindUnit m_OldBindUnit;
};
};
} // namespace GL
} // namespace Backend
} // namespace Renderer
#define GPU_SCOPED_LABEL(deviceCommandContext, name) \
GPUScopedLabel scopedLabel((deviceCommandContext), (name));
class GPUScopedLabel
{
public:
GPUScopedLabel(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const char* name)
: m_DeviceCommandContext(deviceCommandContext)
{
m_DeviceCommandContext->BeginScopedLabel(name);
}
~GPUScopedLabel()
{
m_DeviceCommandContext->EndScopedLabel();
}
private:
Renderer::Backend::GL::CDeviceCommandContext* m_DeviceCommandContext = nullptr;
};
#endif // INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT