Compare commits

...

6 commits

Author SHA1 Message Date
Vladislav Belov
e64a138f92
Fixes outdated state on disabling GPU skinning 2026-08-04 23:06:12 +02:00
Vladislav Belov
4a7b7e7df1
Removes smart pointers from ModelRenderer
We don't need to use pointers because we know lifetimes of all
renderers. And its list statically defined. A new renderer should follow
DOD and use a separate submission list with a separate inplace renderer.
2026-08-04 23:06:10 +02:00
Vladislav Belov
fa90ddc8b6
Refactors ModelRenderer and makes it closer to DOD
Separates submitted models and moder renderer and makes relations
explicit. Now it's clear that we don't need separate ModelRenderer and
ModelVertexRenderer.
2026-08-04 23:06:09 +02:00
Vladislav Belov
764a420797
Merges ModelRenderer and ShaderModelRenderer
We use only shaders to render models so we don't need a separate class.

BatchModelRenderer was renamed in 6bc33fe8bd.
m_Renderer became unused in 0346ba1b18.
2026-08-04 23:06:07 +02:00
Vladislav Belov
8a2bb10827
Renames ShaderModelRenderer instances 2026-08-04 23:06:06 +02:00
Vladislav Belov
af9d102126
Removes legacy logic of reused ModelRendererPtr
It became unused in 4a69e9117e.
2026-08-04 23:06:04 +02:00
8 changed files with 261 additions and 386 deletions

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games. /* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -33,7 +33,7 @@ namespace Renderer::Backend { class IDeviceCommandContext; }
* This computes and binds per-vertex data; the modifier is responsible * This computes and binds per-vertex data; the modifier is responsible
* for setting any shader uniforms etc. * for setting any shader uniforms etc.
*/ */
class CPUSkinnedModelVertexRenderer : public ModelVertexRenderer class CPUSkinnedModelVertexRenderer final : public ModelVertexRenderer
{ {
public: public:
CPUSkinnedModelVertexRenderer(); CPUSkinnedModelVertexRenderer();

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games. /* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -34,7 +34,7 @@ namespace Renderer::Backend { class IShaderProgram; }
* This computes and binds per-vertex data; the modifier is responsible * This computes and binds per-vertex data; the modifier is responsible
* for setting any shader uniforms etc. * for setting any shader uniforms etc.
*/ */
class GPUSkinnedModelModelRenderer : public ModelVertexRenderer class GPUSkinnedModelModelRenderer final : public ModelVertexRenderer
{ {
public: public:
GPUSkinnedModelModelRenderer(); GPUSkinnedModelModelRenderer();

View file

@ -34,7 +34,7 @@ struct InstancingModelRendererInternals;
* This computes and binds per-vertex data; the modifier is responsible * This computes and binds per-vertex data; the modifier is responsible
* for setting any shader uniforms etc (including the instancing transform). * for setting any shader uniforms etc (including the instancing transform).
*/ */
class InstancingModelRenderer : public ModelVertexRenderer class InstancingModelRenderer final : public ModelVertexRenderer
{ {
public: public:
InstancingModelRenderer(); InstancingModelRenderer();

View file

@ -102,15 +102,11 @@ CMaterial::Pass GetMaterialPassFromCullGroup(const int cullGroup, const ERenderM
} }
void ModelRenderer::Init() // static
{
}
// Helper function to copy object-space position and normal vectors into arrays.
void ModelRenderer::CopyPositionAndNormals( void ModelRenderer::CopyPositionAndNormals(
const CModelDefPtr& mdef, const CModelDefPtr& mdef,
const VertexArrayIterator<CVector3D>& Position, const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal) const VertexArrayIterator<CVector3D>& Normal)
{ {
size_t numVertices = mdef->GetNumVertices(); size_t numVertices = mdef->GetNumVertices();
SModelVertex* vertices = mdef->GetVertices(); SModelVertex* vertices = mdef->GetVertices();
@ -122,11 +118,11 @@ void ModelRenderer::CopyPositionAndNormals(
} }
} }
// Helper function to transform position and normal vectors into world-space. // static
void ModelRenderer::BuildPositionAndNormals( void ModelRenderer::BuildPositionAndNormals(
CModel* model, CModel* model,
const VertexArrayIterator<CVector3D>& Position, const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal) const VertexArrayIterator<CVector3D>& Normal)
{ {
CModelDefPtr mdef = model->GetModelDef(); CModelDefPtr mdef = model->GetModelDef();
size_t numVertices = mdef->GetNumVertices(); size_t numVertices = mdef->GetNumVertices();
@ -160,12 +156,11 @@ void ModelRenderer::BuildPositionAndNormals(
} }
} }
// static
// Helper function for lighting
void ModelRenderer::BuildColor4ub( void ModelRenderer::BuildColor4ub(
CModel* model, CModel* model,
const VertexArrayIterator<CVector3D>& Normal, const VertexArrayIterator<CVector3D>& Normal,
const VertexArrayIterator<SColor4ub>& Color) const VertexArrayIterator<SColor4ub>& Color)
{ {
PROFILE("lighting vertices"); PROFILE("lighting vertices");
@ -184,19 +179,16 @@ void ModelRenderer::BuildColor4ub(
} }
} }
// static
void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices, bool gpuSkinning) void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices, bool gpuSkinning)
{ {
MikkTSpace ms(mdef, newVertices, gpuSkinning); MikkTSpace ms(mdef, newVertices, gpuSkinning);
ms.Generate(); ms.Generate();
} }
// static
// Copy UV coordinates
void ModelRenderer::BuildUV( void ModelRenderer::BuildUV(
const CModelDefPtr& mdef, const CModelDefPtr& mdef, const VertexArrayIterator<float[2]>& UV, int UVset)
const VertexArrayIterator<float[2]>& UV,
int UVset)
{ {
const size_t numVertices = mdef->GetNumVertices(); const size_t numVertices = mdef->GetNumVertices();
const size_t numberOfUVPerVertex = mdef->GetNumUVsPerVertex(); const size_t numberOfUVPerVertex = mdef->GetNumUVsPerVertex();
@ -209,11 +201,9 @@ void ModelRenderer::BuildUV(
} }
} }
// static
// Build default indices array.
void ModelRenderer::BuildIndices( void ModelRenderer::BuildIndices(
const CModelDefPtr& mdef, const CModelDefPtr& mdef, const VertexArrayIterator<u16>& Indices)
const VertexArrayIterator<u16>& Indices)
{ {
size_t idxidx = 0; size_t idxidx = 0;
SModelFace* faces = mdef->GetFaces(); SModelFace* faces = mdef->GetFaces();
@ -227,105 +217,6 @@ void ModelRenderer::BuildIndices(
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////////
// ShaderModelRenderer implementation
/**
* Internal data of the ShaderModelRenderer.
*
* Separated into the source file to increase implementation hiding (and to
* avoid some causes of recompiles).
*/
struct ShaderModelRenderer::ShaderModelRendererInternals
{
ShaderModelRendererInternals(ShaderModelRenderer* r) : m_Renderer(r) { }
/// Back-link to "our" renderer
ShaderModelRenderer* m_Renderer;
/// ModelVertexRenderer used for vertex transformations
ModelVertexRendererPtr vertexRenderer;
/// List of submitted models for rendering in this frame
std::vector<CModel*> submissions[CSceneRenderer::CULL_MAX];
};
// Construction/Destruction
ShaderModelRenderer::ShaderModelRenderer(ModelVertexRendererPtr vertexrenderer)
{
m = new ShaderModelRendererInternals(this);
m->vertexRenderer = vertexrenderer;
}
ShaderModelRenderer::~ShaderModelRenderer()
{
delete m;
}
// Submit one model.
void ShaderModelRenderer::Submit(int cullGroup, CModel* model)
{
CModelRData* rdata = (CModelRData*)model->GetRenderData();
// Ensure model data is valid
const void* key = m->vertexRenderer.get();
if (!rdata || rdata->GetKey() != key)
{
model->InvalidatePosition();
rdata = m->vertexRenderer->CreateModelData(key, model);
model->SetRenderData(rdata);
model->SetDirty(~0u);
}
m->submissions[cullGroup].push_back(model);
}
// Call update for all submitted models and enter the rendering phase
void ShaderModelRenderer::PrepareModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
for (int cullGroup = 0; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
for (CModel* model : m->submissions[cullGroup])
{
model->ValidatePosition();
CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
ENSURE(rdata->GetKey() == m->vertexRenderer.get());
}
m->vertexRenderer->UpdateModelsData(deviceCommandContext, m->submissions[cullGroup]);
for (CModel* model : m->submissions[cullGroup])
{
CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
rdata->m_UpdateFlags = 0;
}
}
}
void ShaderModelRenderer::UploadModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
for (int cullGroup = 0; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
m->vertexRenderer->UploadModelsData(deviceCommandContext, m->submissions[cullGroup]);
}
}
// Clear the submissions list
void ShaderModelRenderer::EndFrame()
{
for (int cullGroup = 0; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
m->submissions[cullGroup].clear();
}
// Helper structs for ShaderModelRenderer::Render(): // Helper structs for ShaderModelRenderer::Render():
struct SMRSortByDistItem struct SMRSortByDistItem
@ -413,12 +304,12 @@ struct SMRCompareTechBucket
} }
}; };
void ShaderModelRenderer::Render( void ModelRenderer::Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const RenderModifierPtr& modifier, const CShaderDefines& context, ModelVertexRenderer& modelVertexRenderer, RenderModifier& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode) int cullGroup, int flags, const ERenderMode renderMode, std::span<CModel*> submissions)
{ {
if (m->submissions[cullGroup].empty()) if (submissions.empty())
return; return;
CMatrix3D worldToCam; CMatrix3D worldToCam;
@ -429,7 +320,7 @@ void ShaderModelRenderer::Render(
/* /*
* Rendering approach: * Rendering approach:
* *
* m->submissions contains the list of CModels to render. * submissions contains the list of CModels to render.
* *
* The data we need to render a model is: * The data we need to render a model is:
* - CShaderTechnique * - CShaderTechnique
@ -497,9 +388,8 @@ void ShaderModelRenderer::Render(
{ {
PROFILE3("bucketing by material"); PROFILE3("bucketing by material");
for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) for (CModel* model : submissions)
{ {
CModel* model = m->submissions[cullGroup][i];
const CMaterial material{model->GetMaterial()}; const CMaterial material{model->GetMaterial()};
const CShaderDefines& defines{material.GetShaderDefines()}; const CShaderDefines& defines{material.GetShaderDefines()};
const CStrIntern shaderEffect{material.GetShaderEffect(materialPass)}; const CStrIntern shaderEffect{material.GetShaderEffect(materialPass)};
@ -683,7 +573,7 @@ void ShaderModelRenderer::Render(
Renderer::Backend::IShaderProgram* shader = currentTech->GetShader(pass); Renderer::Backend::IShaderProgram* shader = currentTech->GetShader(pass);
modifier->BeginPass(deviceCommandContext, shader); modifier.BeginPass(deviceCommandContext, shader);
// TODO: Use a more generic approach to handle bound queries. // TODO: Use a more generic approach to handle bound queries.
bool boundTime = false; bool boundTime = false;
@ -753,7 +643,7 @@ void ShaderModelRenderer::Render(
if (newModeldef != currentModeldef) if (newModeldef != currentModeldef)
{ {
currentModeldef = newModeldef; currentModeldef = newModeldef;
m->vertexRenderer->PrepareModelDef(deviceCommandContext, *currentModeldef); modelVertexRenderer.PrepareModelDef(deviceCommandContext, *currentModeldef);
} }
// Bind all uniforms when any change // Bind all uniforms when any change
@ -813,12 +703,12 @@ void ShaderModelRenderer::Render(
} }
} }
modifier->PrepareModel(deviceCommandContext, model); modifier.PrepareModel(deviceCommandContext, model);
CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData()); CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
ENSURE(rdata->GetKey() == m->vertexRenderer.get()); ENSURE(rdata->GetKey() == &modelVertexRenderer);
m->vertexRenderer->RenderModel(deviceCommandContext, shader, model, rdata); modelVertexRenderer.RenderModel(deviceCommandContext, shader, model, rdata);
} }
} }

View file

@ -30,6 +30,7 @@
#include "lib/types.h" #include "lib/types.h"
#include <memory> #include <memory>
#include <span>
#include <vector> #include <vector>
class CModel; class CModel;
@ -39,17 +40,8 @@ namespace Renderer::Backend { class IDeviceCommandContext; }
struct SColor4ub; struct SColor4ub;
template <typename T> class VertexArrayIterator; template <typename T> class VertexArrayIterator;
class RenderModifier;
typedef std::shared_ptr<RenderModifier> RenderModifierPtr;
class LitRenderModifier;
typedef std::shared_ptr<LitRenderModifier> LitRenderModifierPtr;
class ModelVertexRenderer; class ModelVertexRenderer;
typedef std::shared_ptr<ModelVertexRenderer> ModelVertexRendererPtr; class RenderModifier;
class ModelRenderer;
typedef std::shared_ptr<ModelRenderer> ModelRendererPtr;
/** /**
* Class CModelRData: Render data that is maintained per CModel. * Class CModelRData: Render data that is maintained per CModel.
@ -83,25 +75,12 @@ private:
/** /**
* Class ModelRenderer: Abstract base class for all model renders. * ModelRenderer renders a per-frame list of models. It loads the appropriate
* shaders for rendering each model, and that batches by shader technique (and
* by mesh and texture).
* *
* A ModelRenderer manages a per-frame list of models. * ModelRenderer delegates vertex transformation/setup to a
* * ModelVertexRenderer. It delegates fragment stage setup to a RenderModifier.
* It is supposed to be derived in order to create new ways in which
* the per-frame list of models can be managed (for batching, for
* transparent rendering, etc.) or potentially for rarely used special
* effects.
*
* A typical ModelRenderer will delegate vertex transformation/setup
* to a ModelVertexRenderer.
* It will delegate fragment stage setup to a RenderModifier.
*
* For most purposes, you should use a BatchModelRenderer with
* specialized ModelVertexRenderer and RenderModifier implementations.
*
* It is suggested that a derived class implement the provided generic
* Render function, however in some cases it may be necessary to supply
* a Render function with a different prototype.
* *
* ModelRenderer also contains a number of static helper functions * ModelRenderer also contains a number of static helper functions
* for building vertex arrays. * for building vertex arrays.
@ -109,61 +88,10 @@ private:
class ModelRenderer class ModelRenderer
{ {
public: public:
ModelRenderer() { }
virtual ~ModelRenderer() { }
/**
* Initialise global settings.
* Should be called before using the class.
*/
static void Init();
/**
* Submit: Submit a model for rendering this frame.
*
* preconditions : The model must not have been submitted to any
* ModelRenderer in this frame. Submit may only be called
* after EndFrame and before PrepareModels.
*
* @param model The model that will be added to the list of models
* submitted this frame.
*/
virtual void Submit(int cullGroup, CModel* model) = 0;
/**
* PrepareModels: Calculate renderer data for all previously
* submitted models.
*
* Must be called before any rendering calls and after all models
* for this frame have been submitted.
*/
virtual void PrepareModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext) = 0;
/**
* Upload renderer data for all previously submitted models to backend.
*
* Must be called before any rendering calls and after all models
* for this frame have been prepared.
*/
virtual void UploadModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext) = 0;
/**
* EndFrame: Remove all models from the list of submitted
* models.
*/
virtual void EndFrame() = 0;
/** /**
* Render: Render submitted models, using the given RenderModifier to setup * Render: Render submitted models, using the given RenderModifier to setup
* the fragment stage. * the fragment stage.
* *
* @note It is suggested that derived model renderers implement and use
* this Render functions. However, a highly specialized model renderer
* may need to "disable" this function and provide its own Render function
* with a different prototype.
*
* preconditions : PrepareModels must be called after all models have been * preconditions : PrepareModels must be called after all models have been
* submitted and before calling Render. * submitted and before calling Render.
* *
@ -172,10 +100,10 @@ public:
* If flags is non-zero, only models that contain flags in their * If flags is non-zero, only models that contain flags in their
* CModel::GetFlags() are rendered. * CModel::GetFlags() are rendered.
*/ */
virtual void Render( void Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const RenderModifierPtr& modifier, const CShaderDefines& context, ModelVertexRenderer& modelVertexRenderer, RenderModifier& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode) = 0; int cullGroup, int flags, const ERenderMode renderMode, std::span<CModel*> submissions);
/** /**
* CopyPositionAndNormals: Copy unanimated object-space vertices and * CopyPositionAndNormals: Copy unanimated object-space vertices and
@ -190,9 +118,9 @@ public:
* The array behind the iterator must be as large as the Position array. * The array behind the iterator must be as large as the Position array.
*/ */
static void CopyPositionAndNormals( static void CopyPositionAndNormals(
const CModelDefPtr& mdef, const CModelDefPtr& mdef,
const VertexArrayIterator<CVector3D>& Position, const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal); const VertexArrayIterator<CVector3D>& Normal);
/** /**
* BuildPositionAndNormals: Build animated vertices and normals, * BuildPositionAndNormals: Build animated vertices and normals,
@ -209,9 +137,9 @@ public:
* the Position array. * the Position array.
*/ */
static void BuildPositionAndNormals( static void BuildPositionAndNormals(
CModel* model, CModel* model,
const VertexArrayIterator<CVector3D>& Position, const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal); const VertexArrayIterator<CVector3D>& Normal);
/** /**
* BuildColor4ub: Build lighting colors for the given model, * BuildColor4ub: Build lighting colors for the given model,
@ -225,9 +153,9 @@ public:
* model->GetModelDef()->GetNumVertices() vertices. * model->GetModelDef()->GetNumVertices() vertices.
*/ */
static void BuildColor4ub( static void BuildColor4ub(
CModel* model, CModel* model,
const VertexArrayIterator<CVector3D>& Normal, const VertexArrayIterator<CVector3D>& Normal,
const VertexArrayIterator<SColor4ub>& Color); const VertexArrayIterator<SColor4ub>& Color);
/** /**
* BuildUV: Copy UV coordinates into the given vertex array. * BuildUV: Copy UV coordinates into the given vertex array.
@ -238,9 +166,9 @@ public:
* mdef->GetNumVertices() vertices. * mdef->GetNumVertices() vertices.
*/ */
static void BuildUV( static void BuildUV(
const CModelDefPtr& mdef, const CModelDefPtr& mdef,
const VertexArrayIterator<float[2]>& UV, const VertexArrayIterator<float[2]>& UV,
int UVset); int UVset);
/** /**
* BuildIndices: Create the indices array for the given CModelDef. * BuildIndices: Create the indices array for the given CModelDef.
@ -250,8 +178,7 @@ public:
* mdef->GetNumFaces()*3 elements. * mdef->GetNumFaces()*3 elements.
*/ */
static void BuildIndices( static void BuildIndices(
const CModelDefPtr& mdef, const CModelDefPtr& mdef, const VertexArrayIterator<u16>& Indices);
const VertexArrayIterator<u16>& Indices);
/** /**
* GenTangents: Generate tangents for the given CModelDef. * GenTangents: Generate tangents for the given CModelDef.
@ -263,33 +190,4 @@ public:
static void GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices, bool gpuSkinning); static void GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices, bool gpuSkinning);
}; };
/**
* Implementation of ModelRenderer that loads the appropriate shaders for
* rendering each model, and that batches by shader technique (and by mesh and texture).
*/
class ShaderModelRenderer : public ModelRenderer
{
friend struct ShaderModelRendererInternals;
public:
ShaderModelRenderer(ModelVertexRendererPtr vertexrender);
~ShaderModelRenderer() override;
// Batching implementations
void Submit(int cullGroup, CModel* model) override;
void PrepareModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext) override;
void UploadModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext) override;
void EndFrame() override;
void Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const RenderModifierPtr& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode) override;
private:
struct ShaderModelRendererInternals;
ShaderModelRendererInternals* m;
};
#endif // INCLUDED_MODELRENDERER #endif // INCLUDED_MODELRENDERER

View file

@ -62,7 +62,6 @@
#include "ps/VideoMode.h" #include "ps/VideoMode.h"
#include "ps/World.h" #include "ps/World.h"
#include "renderer/DebugRenderer.h" #include "renderer/DebugRenderer.h"
#include "renderer/ModelRenderer.h"
#include "renderer/PostprocManager.h" #include "renderer/PostprocManager.h"
#include "renderer/RenderingOptions.h" #include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h" #include "renderer/SceneRenderer.h"
@ -456,7 +455,6 @@ CRenderer::CRenderer(Renderer::Backend::IDevice* device)
ModelDefActivateFastImpl(); ModelDefActivateFastImpl();
ColorActivateFastImpl(); ColorActivateFastImpl();
ModelRenderer::Init();
} }
CRenderer::~CRenderer() CRenderer::~CRenderer()

View file

@ -212,17 +212,17 @@ void CRenderingOptions::ReadConfigAndSetupHooks()
m_ConfigHooks->Setup("silhouettes", m_Silhouettes); m_ConfigHooks->Setup("silhouettes", m_Silhouettes);
m_ConfigHooks->Setup("gpuskinning", [this]() { m_ConfigHooks->Setup("gpuskinning", [this]() {
const Renderer::Backend::IDevice::Capabilities& capabilities{ if (g_ConfigDB.Get("gpuskinning", false))
g_VideoMode.GetBackendDevice()->GetCapabilities()};
if (!g_ConfigDB.Get("gpuskinning", false))
return;
if (capabilities.computeShaders && capabilities.storage)
m_GPUSkinning = true;
else
{ {
m_GPUSkinning = false; const Renderer::Backend::IDevice::Capabilities& capabilities{
LOGMESSAGE("GPU skinning isn't supported on the current hardware."); g_VideoMode.GetBackendDevice()->GetCapabilities()};
if (capabilities.computeShaders && capabilities.storage)
m_GPUSkinning = true;
else
{
m_GPUSkinning = false;
LOGMESSAGE("GPU skinning isn't supported on the current hardware.");
}
} }
if (CRenderer::IsInitialised()) if (CRenderer::IsInitialised())

View file

@ -68,6 +68,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <optional>
struct SScreenRect struct SScreenRect
{ {
@ -115,78 +116,192 @@ public:
SilhouetteRenderer silhouetteRenderer; SilhouetteRenderer silhouetteRenderer;
/// Various model renderers // Various model renderers
struct Models struct Models
{ {
// NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer,
// RenderModifier, etc) is mostly a relic of an older design that implemented
// the different materials and rendering modes through extensive subclassing
// and hooking objects together in various combinations.
// The new design uses the CShaderManager API to abstract away the details
// of rendering, and uses a data-driven approach to materials, so there are
// now a small number of generic subclasses instead of many specialised subclasses,
// but most of the old infrastructure hasn't been refactored out yet and leads to
// some unwanted complexity.
// Submitted models are split on two axes: // Submitted models are split on two axes:
// - Normal vs Transp[arent] - alpha-blended models are stored in a separate // - Opaque vs Transparent - alpha-blended models are stored in a separate
// list so we can draw them above/below the alpha-blended water plane correctly // list so we can draw them above/below the alpha-blended water plane correctly
// - Skinned vs Unskinned - with hardware lighting we don't need to // - Skinned vs Unskinned - we don't need to duplicate mesh data per
// duplicate mesh data per model instance (except for skinned models), // model instance (except for skinned models), so non-skinned models
// so non-skinned models get different ModelVertexRenderers // get different ModelVertexRenderers
ModelRendererPtr NormalSkinned; struct Submissions
ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported {
ModelRendererPtr TranspSkinned; std::vector<CModel*> submissions[CSceneRenderer::CULL_MAX];
ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported };
ModelVertexRendererPtr VertexRendererShader; // Unskinned submissions should be prepared and rendered with
ModelVertexRendererPtr VertexInstancingShader; // VertexInstancingShader. Skinned - with Vertex*SkinningShader
ModelVertexRendererPtr VertexGPUSkinningShader; // depending on whether GPU skinning is enabled.
Submissions OpaqueSkinned;
Submissions OpaqueUnskinned;
Submissions TransparentSkinned;
Submissions TransparentUnskinned;
LitRenderModifierPtr ModShader; ModelRenderer modelRenderer;
InstancingModelRenderer VertexInstancingShader;
CPUSkinnedModelVertexRenderer VertexCPUSkinningShader;
// We can't create GPU skinning renderer for renderer devices without
// its support.
std::optional<GPUSkinnedModelModelRenderer> VertexGPUSkinningShader;
ShaderRenderModifier ModShader;
bool GPUSkinningEnabled{false};
} Model; } Model;
CShaderDefines globalContext; CShaderDefines globalContext;
/**
* Upload renderer data for all previously submitted models to backend.
*
* Must be called before any rendering calls and after all models
* for this frame have been prepared.
*/
void UploadModels(Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
PROFILE3("upload models");
ModelVertexRenderer& modelVertexSkinningRenderer{
Model.GPUSkinningEnabled
? static_cast<ModelVertexRenderer&>(*Model.VertexGPUSkinningShader)
: static_cast<ModelVertexRenderer&>(Model.VertexCPUSkinningShader)};
for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
modelVertexSkinningRenderer.UploadModelsData(deviceCommandContext, Model.OpaqueSkinned.submissions[cullGroup]);
modelVertexSkinningRenderer.UploadModelsData(deviceCommandContext, Model.TransparentSkinned.submissions[cullGroup]);
}
for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
Model.VertexInstancingShader.UploadModelsData(deviceCommandContext, Model.OpaqueUnskinned.submissions[cullGroup]);
Model.VertexInstancingShader.UploadModelsData(deviceCommandContext, Model.TransparentUnskinned.submissions[cullGroup]);
}
}
/**
* PrepareModels: Calculate renderer data for all previously
* submitted models.
*
* Must be called before any rendering calls and after all models
* for this frame have been submitted.
*/
void PrepareModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
PROFILE3("prepare models");
ModelVertexRenderer& modelVertexSkinningRenderer{
Model.GPUSkinningEnabled
? static_cast<ModelVertexRenderer&>(*Model.VertexGPUSkinningShader)
: static_cast<ModelVertexRenderer&>(Model.VertexCPUSkinningShader)};
for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
PrepareModels(deviceCommandContext, modelVertexSkinningRenderer, Model.OpaqueSkinned.submissions[cullGroup]);
PrepareModels(deviceCommandContext, modelVertexSkinningRenderer, Model.TransparentSkinned.submissions[cullGroup]);
}
for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
PrepareModels(deviceCommandContext, Model.VertexInstancingShader, Model.OpaqueUnskinned.submissions[cullGroup]);
PrepareModels(deviceCommandContext, Model.VertexInstancingShader, Model.TransparentUnskinned.submissions[cullGroup]);
}
}
void PrepareModels(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, ModelVertexRenderer& modelVertexRenderer, std::span<CModel*> submissions)
{
for (CModel* model : submissions)
{
model->ValidatePosition();
CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
ENSURE(rdata->GetKey() == &modelVertexRenderer);
}
modelVertexRenderer.UpdateModelsData(deviceCommandContext, submissions);
for (CModel* model : submissions)
{
CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
rdata->m_UpdateFlags = 0;
}
}
/**
* Submit: Submit a model for rendering this frame.
*
* preconditions : The model must not have been submitted to any
* ModelRenderer in this frame. Submit may only be called
* after EndFrame and before PrepareModels.
*
* @param model The model that will be added to the list of models
* submitted this frame.
*/
void Submit(const int cullGroup, ModelVertexRenderer& modelVertexRenderer, Models::Submissions& submissions, CModel* model)
{
CModelRData* rdata{static_cast<CModelRData*>(model->GetRenderData())};
// Ensure model data is valid.
// TODO: using a pointer as a key is unsafe.
const void* key{&modelVertexRenderer};
if (!rdata || rdata->GetKey() != key)
{
model->InvalidatePosition();
rdata = modelVertexRenderer.CreateModelData(key, model);
model->SetRenderData(rdata);
model->SetDirty(~0u);
}
submissions.submissions[cullGroup].push_back(model);
}
/** /**
* Renders all non-alpha-blended models with the given context. * Renders all non-alpha-blended models with the given context.
*/ */
void CallModelRenderers( void CallOpaqueModelRenderers(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode) const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode)
{ {
CShaderDefines contextSkinned = context; ModelVertexRenderer& modelVertexSkinningRenderer{
if (g_RenderingOptions.GetGPUSkinning()) Model.GPUSkinningEnabled
contextSkinned.Add(str_USE_INSTANCING, str_1); ? static_cast<ModelVertexRenderer&>(*Model.VertexGPUSkinningShader)
Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode); : static_cast<ModelVertexRenderer&>(Model.VertexCPUSkinningShader)};
if (Model.NormalUnskinned != Model.NormalSkinned) CShaderDefines contextSkinned = context;
{ if (Model.GPUSkinningEnabled)
CShaderDefines contextUnskinned = context; contextSkinned.Add(str_USE_INSTANCING, str_1);
contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.modelRenderer.Render(deviceCommandContext, modelVertexSkinningRenderer, Model.ModShader, contextSkinned, cullGroup, flags, renderMode, Model.OpaqueSkinned.submissions[cullGroup]);
Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode);
} CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.modelRenderer.Render(deviceCommandContext, Model.VertexInstancingShader, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode, Model.OpaqueUnskinned.submissions[cullGroup]);
} }
/** /**
* Renders all alpha-blended models with the given context. * Renders all alpha-blended models with the given context.
*/ */
void CallTranspModelRenderers( void CallTransparentModelRenderers(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode) const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode)
{ {
CShaderDefines contextSkinned = context; ModelVertexRenderer& modelVertexSkinningRenderer{
if (g_RenderingOptions.GetGPUSkinning()) Model.GPUSkinningEnabled
contextSkinned.Add(str_USE_INSTANCING, str_1); ? static_cast<ModelVertexRenderer&>(*Model.VertexGPUSkinningShader)
Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode); : static_cast<ModelVertexRenderer&>(Model.VertexCPUSkinningShader)};
if (Model.TranspUnskinned != Model.TranspSkinned) CShaderDefines contextSkinned = context;
{ if (Model.GPUSkinningEnabled)
CShaderDefines contextUnskinned = context; contextSkinned.Add(str_USE_INSTANCING, str_1);
contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.modelRenderer.Render(deviceCommandContext, modelVertexSkinningRenderer, Model.ModShader, contextSkinned, cullGroup, flags, renderMode, Model.TransparentSkinned.submissions[cullGroup]);
Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode);
} CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.modelRenderer.Render(deviceCommandContext, Model.VertexInstancingShader, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode, Model.TransparentUnskinned.submissions[cullGroup]);
} }
}; };
@ -239,26 +354,12 @@ void CSceneRenderer::ReloadShaders([[maybe_unused]] Renderer::Backend::IDevice*
m->globalContext.Add(str_RENDER_DEBUG_MODE, m->globalContext.Add(str_RENDER_DEBUG_MODE,
RenderDebugModeEnum::ToString(g_RenderingOptions.GetRenderDebugMode())); RenderDebugModeEnum::ToString(g_RenderingOptions.GetRenderDebugMode()));
m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); m->Model.GPUSkinningEnabled = g_RenderingOptions.GetGPUSkinning();
if (m->Model.GPUSkinningEnabled)
m->Model.VertexRendererShader = ModelVertexRendererPtr(new CPUSkinnedModelVertexRenderer());
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer());
if (g_RenderingOptions.GetGPUSkinning())
{ {
m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new GPUSkinnedModelModelRenderer()); if (!m->Model.VertexGPUSkinningShader.has_value())
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.VertexGPUSkinningShader.emplace();
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
} }
else
{
m->Model.VertexGPUSkinningShader.reset();
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
}
m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
} }
void CSceneRenderer::Initialize() void CSceneRenderer::Initialize()
@ -282,8 +383,8 @@ void CSceneRenderer::Resize(int /*width*/, int /*height*/)
void CSceneRenderer::BeginFrame() void CSceneRenderer::BeginFrame()
{ {
// choose model renderers for this frame // choose model renderers for this frame
m->Model.ModShader->SetShadowMap(&m->shadow); m->Model.ModShader.SetShadowMap(&m->shadow);
m->Model.ModShader->SetLightEnv(m_LightEnv); m->Model.ModShader.SetLightEnv(m_LightEnv);
} }
void CSceneRenderer::SetSimulation(CSimulation2* simulation) void CSceneRenderer::SetSimulation(CSimulation2* simulation)
@ -315,12 +416,12 @@ void CSceneRenderer::RenderShadowMap(
{ {
PROFILE("render models"); PROFILE("render models");
m->CallModelRenderers(deviceCommandContext, {}, cullGroup, ModelFlag::CAST_SHADOWS, ERenderMode::SOLID); m->CallOpaqueModelRenderers(deviceCommandContext, {}, cullGroup, ModelFlag::CAST_SHADOWS, ERenderMode::SOLID);
} }
{ {
PROFILE("render transparent models"); PROFILE("render transparent models");
m->CallTranspModelRenderers(deviceCommandContext, {}, cullGroup, ModelFlag::CAST_SHADOWS, ERenderMode::SOLID); m->CallTransparentModelRenderers(deviceCommandContext, {}, cullGroup, ModelFlag::CAST_SHADOWS, ERenderMode::SOLID);
} }
} }
@ -363,10 +464,10 @@ void CSceneRenderer::RenderModels(
const ERenderMode modelRenderMode{ const ERenderMode modelRenderMode{
m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID}; m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID};
m->CallModelRenderers(deviceCommandContext, context, cullGroup, flags, modelRenderMode); m->CallOpaqueModelRenderers(deviceCommandContext, context, cullGroup, flags, modelRenderMode);
if (m_ModelRenderMode == EDGED_FACES) if (m_ModelRenderMode == EDGED_FACES)
m->CallModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES); m->CallOpaqueModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES);
} }
void CSceneRenderer::RenderTransparentModels( void CSceneRenderer::RenderTransparentModels(
@ -388,13 +489,13 @@ void CSceneRenderer::RenderTransparentModels(
m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID}; m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID};
if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE)
m->CallTranspModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags, modelRenderMode); m->CallTransparentModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags, modelRenderMode);
if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND)
m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags, modelRenderMode); m->CallTransparentModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags, modelRenderMode);
if (m_ModelRenderMode == EDGED_FACES) if (m_ModelRenderMode == EDGED_FACES)
m->CallTranspModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES); m->CallTransparentModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES);
} }
// SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space)
@ -709,24 +810,24 @@ void CSceneRenderer::RenderSilhouettes(
{ {
PROFILE("render model occluders"); PROFILE("render model occluders");
m->CallModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_OCCLUDER, 0, ERenderMode::SOLID); m->CallOpaqueModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_OCCLUDER, 0, ERenderMode::SOLID);
} }
{ {
PROFILE("render transparent occluders"); PROFILE("render transparent occluders");
m->CallTranspModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_OCCLUDER, 0, ERenderMode::SOLID); m->CallTransparentModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_OCCLUDER, 0, ERenderMode::SOLID);
} }
// Since we can't sort, we'll use the stencil buffer to ensure we only draw // Since we can't sort, we'll use the stencil buffer to ensure we only draw
// a pixel once (using the color of whatever model happens to be drawn first). // a pixel once (using the color of whatever model happens to be drawn first).
{ {
PROFILE("render model casters"); PROFILE("render model casters");
m->CallModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_CASTER, 0, ERenderMode::SOLID); m->CallOpaqueModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_CASTER, 0, ERenderMode::SOLID);
} }
{ {
PROFILE("render transparent casters"); PROFILE("render transparent casters");
m->CallTranspModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_CASTER, 0, ERenderMode::SOLID); m->CallTransparentModelRenderers(deviceCommandContext, {}, CULL_SILHOUETTE_CASTER, 0, ERenderMode::SOLID);
} }
} }
@ -770,15 +871,7 @@ void CSceneRenderer::PrepareSubmissions(
CShaderDefines context = m->globalContext; CShaderDefines context = m->globalContext;
// Prepare model renderers // Prepare model renderers
{ m->PrepareModels(deviceCommandContext);
PROFILE3("prepare models");
m->Model.NormalSkinned->PrepareModels(deviceCommandContext);
m->Model.TranspSkinned->PrepareModels(deviceCommandContext);
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->PrepareModels(deviceCommandContext);
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->PrepareModels(deviceCommandContext);
}
m->terrainRenderer.PrepareForRendering(); m->terrainRenderer.PrepareForRendering();
@ -786,15 +879,7 @@ void CSceneRenderer::PrepareSubmissions(
m->particleRenderer.PrepareForRendering(context); m->particleRenderer.PrepareForRendering(context);
{ m->UploadModels(deviceCommandContext);
PROFILE3("upload models");
m->Model.NormalSkinned->UploadModels(deviceCommandContext);
m->Model.TranspSkinned->UploadModels(deviceCommandContext);
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->UploadModels(deviceCommandContext);
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->UploadModels(deviceCommandContext);
}
m->overlayRenderer.Upload(deviceCommandContext); m->overlayRenderer.Upload(deviceCommandContext);
@ -908,13 +993,13 @@ void CSceneRenderer::EndFrame()
m->particleRenderer.EndFrame(); m->particleRenderer.EndFrame();
m->silhouetteRenderer.EndFrame(); m->silhouetteRenderer.EndFrame();
// Finish model renderers for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
m->Model.NormalSkinned->EndFrame(); {
m->Model.TranspSkinned->EndFrame(); m->Model.OpaqueSkinned.submissions[cullGroup].clear();
if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.TransparentSkinned.submissions[cullGroup].clear();
m->Model.NormalUnskinned->EndFrame(); m->Model.OpaqueUnskinned.submissions[cullGroup].clear();
if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TransparentUnskinned.submissions[cullGroup].clear();
m->Model.TranspUnskinned->EndFrame(); }
} }
void CSceneRenderer::DisplayFrustum(Renderer::Backend::IDeviceCommandContext& deviceCommandContext) void CSceneRenderer::DisplayFrustum(Renderer::Backend::IDeviceCommandContext& deviceCommandContext)
@ -1029,21 +1114,25 @@ void CSceneRenderer::SubmitNonRecursive(CModel* model)
m->shadow.AddShadowCasterBound(cascade, model->GetWorldBounds()); m->shadow.AddShadowCasterBound(cascade, model->GetWorldBounds());
} }
bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); const bool requiresSkinning{model->GetModelDef()->GetNumBones() != 0};
ModelVertexRenderer& modelVertexSkinningRenderer{
m->Model.GPUSkinningEnabled
? static_cast<ModelVertexRenderer&>(*m->Model.VertexGPUSkinningShader)
: static_cast<ModelVertexRenderer&>(m->Model.VertexCPUSkinningShader)};
if (model->GetMaterial().UsesAlphaBlending()) if (model->GetMaterial().UsesAlphaBlending())
{ {
if (requiresSkinning) if (requiresSkinning)
m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model); m->Submit(m_CurrentCullGroup, modelVertexSkinningRenderer, m->Model.TransparentSkinned, model);
else else
m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model); m->Submit(m_CurrentCullGroup, m->Model.VertexInstancingShader, m->Model.TransparentUnskinned, model);
} }
else else
{ {
if (requiresSkinning) if (requiresSkinning)
m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model); m->Submit(m_CurrentCullGroup, modelVertexSkinningRenderer, m->Model.OpaqueSkinned, model);
else else
m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model); m->Submit(m_CurrentCullGroup, m->Model.VertexInstancingShader, m->Model.OpaqueUnskinned, model);
} }
} }