Compare commits

..

7 commits

Author SHA1 Message Date
Vladislav Belov
867bdf318b
Fixes missing material reference in ModelRenderer
Missed in 18a16aa135
2026-08-06 10:31:16 +02:00
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.
*
* 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
* for setting any shader uniforms etc.
*/
class CPUSkinnedModelVertexRenderer : public ModelVertexRenderer
class CPUSkinnedModelVertexRenderer final : public ModelVertexRenderer
{
public:
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.
*
* 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
* for setting any shader uniforms etc.
*/
class GPUSkinnedModelModelRenderer : public ModelVertexRenderer
class GPUSkinnedModelModelRenderer final : public ModelVertexRenderer
{
public:
GPUSkinnedModelModelRenderer();

View file

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

View file

@ -102,15 +102,11 @@ CMaterial::Pass GetMaterialPassFromCullGroup(const int cullGroup, const ERenderM
}
void ModelRenderer::Init()
{
}
// Helper function to copy object-space position and normal vectors into arrays.
// static
void ModelRenderer::CopyPositionAndNormals(
const CModelDefPtr& mdef,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal)
const CModelDefPtr& mdef,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal)
{
size_t numVertices = mdef->GetNumVertices();
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(
CModel* model,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal)
CModel* model,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal)
{
CModelDefPtr mdef = model->GetModelDef();
size_t numVertices = mdef->GetNumVertices();
@ -160,12 +156,11 @@ void ModelRenderer::BuildPositionAndNormals(
}
}
// Helper function for lighting
// static
void ModelRenderer::BuildColor4ub(
CModel* model,
const VertexArrayIterator<CVector3D>& Normal,
const VertexArrayIterator<SColor4ub>& Color)
CModel* model,
const VertexArrayIterator<CVector3D>& Normal,
const VertexArrayIterator<SColor4ub>& Color)
{
PROFILE("lighting vertices");
@ -184,19 +179,16 @@ void ModelRenderer::BuildColor4ub(
}
}
// static
void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices, bool gpuSkinning)
{
MikkTSpace ms(mdef, newVertices, gpuSkinning);
ms.Generate();
}
// Copy UV coordinates
// static
void ModelRenderer::BuildUV(
const CModelDefPtr& mdef,
const VertexArrayIterator<float[2]>& UV,
int UVset)
const CModelDefPtr& mdef, const VertexArrayIterator<float[2]>& UV, int UVset)
{
const size_t numVertices = mdef->GetNumVertices();
const size_t numberOfUVPerVertex = mdef->GetNumUVsPerVertex();
@ -209,11 +201,9 @@ void ModelRenderer::BuildUV(
}
}
// Build default indices array.
// static
void ModelRenderer::BuildIndices(
const CModelDefPtr& mdef,
const VertexArrayIterator<u16>& Indices)
const CModelDefPtr& mdef, const VertexArrayIterator<u16>& Indices)
{
size_t idxidx = 0;
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():
struct SMRSortByDistItem
@ -413,12 +304,12 @@ struct SMRCompareTechBucket
}
};
void ShaderModelRenderer::Render(
void ModelRenderer::Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const RenderModifierPtr& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode)
ModelVertexRenderer& modelVertexRenderer, RenderModifier& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode, std::span<CModel*> submissions)
{
if (m->submissions[cullGroup].empty())
if (submissions.empty())
return;
CMatrix3D worldToCam;
@ -429,7 +320,7 @@ void ShaderModelRenderer::Render(
/*
* 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:
* - CShaderTechnique
@ -497,9 +388,8 @@ void ShaderModelRenderer::Render(
{
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 CShaderDefines& defines{material.GetShaderDefines()};
const CStrIntern shaderEffect{material.GetShaderEffect(materialPass)};
@ -683,7 +573,7 @@ void ShaderModelRenderer::Render(
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.
bool boundTime = false;
@ -753,7 +643,7 @@ void ShaderModelRenderer::Render(
if (newModeldef != currentModeldef)
{
currentModeldef = newModeldef;
m->vertexRenderer->PrepareModelDef(deviceCommandContext, *currentModeldef);
modelVertexRenderer.PrepareModelDef(deviceCommandContext, *currentModeldef);
}
// 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());
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 <memory>
#include <span>
#include <vector>
class CModel;
@ -39,17 +40,8 @@ namespace Renderer::Backend { class IDeviceCommandContext; }
struct SColor4ub;
template <typename T> class VertexArrayIterator;
class RenderModifier;
typedef std::shared_ptr<RenderModifier> RenderModifierPtr;
class LitRenderModifier;
typedef std::shared_ptr<LitRenderModifier> LitRenderModifierPtr;
class ModelVertexRenderer;
typedef std::shared_ptr<ModelVertexRenderer> ModelVertexRendererPtr;
class ModelRenderer;
typedef std::shared_ptr<ModelRenderer> ModelRendererPtr;
class RenderModifier;
/**
* 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.
*
* 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 delegates vertex transformation/setup to a
* ModelVertexRenderer. It delegates fragment stage setup to a RenderModifier.
*
* ModelRenderer also contains a number of static helper functions
* for building vertex arrays.
@ -109,61 +88,10 @@ private:
class ModelRenderer
{
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
* 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
* submitted and before calling Render.
*
@ -172,10 +100,10 @@ public:
* If flags is non-zero, only models that contain flags in their
* CModel::GetFlags() are rendered.
*/
virtual void Render(
void Render(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const RenderModifierPtr& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode) = 0;
ModelVertexRenderer& modelVertexRenderer, RenderModifier& modifier, const CShaderDefines& context,
int cullGroup, int flags, const ERenderMode renderMode, std::span<CModel*> submissions);
/**
* 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.
*/
static void CopyPositionAndNormals(
const CModelDefPtr& mdef,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal);
const CModelDefPtr& mdef,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal);
/**
* BuildPositionAndNormals: Build animated vertices and normals,
@ -209,9 +137,9 @@ public:
* the Position array.
*/
static void BuildPositionAndNormals(
CModel* model,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal);
CModel* model,
const VertexArrayIterator<CVector3D>& Position,
const VertexArrayIterator<CVector3D>& Normal);
/**
* BuildColor4ub: Build lighting colors for the given model,
@ -225,9 +153,9 @@ public:
* model->GetModelDef()->GetNumVertices() vertices.
*/
static void BuildColor4ub(
CModel* model,
const VertexArrayIterator<CVector3D>& Normal,
const VertexArrayIterator<SColor4ub>& Color);
CModel* model,
const VertexArrayIterator<CVector3D>& Normal,
const VertexArrayIterator<SColor4ub>& Color);
/**
* BuildUV: Copy UV coordinates into the given vertex array.
@ -238,9 +166,9 @@ public:
* mdef->GetNumVertices() vertices.
*/
static void BuildUV(
const CModelDefPtr& mdef,
const VertexArrayIterator<float[2]>& UV,
int UVset);
const CModelDefPtr& mdef,
const VertexArrayIterator<float[2]>& UV,
int UVset);
/**
* BuildIndices: Create the indices array for the given CModelDef.
@ -250,8 +178,7 @@ public:
* mdef->GetNumFaces()*3 elements.
*/
static void BuildIndices(
const CModelDefPtr& mdef,
const VertexArrayIterator<u16>& Indices);
const CModelDefPtr& mdef, const VertexArrayIterator<u16>& Indices);
/**
* GenTangents: Generate tangents for the given CModelDef.
@ -263,33 +190,4 @@ public:
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

View file

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

View file

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

View file

@ -68,6 +68,7 @@
#include <algorithm>
#include <cmath>
#include <optional>
struct SScreenRect
{
@ -115,78 +116,192 @@ public:
SilhouetteRenderer silhouetteRenderer;
/// Various model renderers
// Various model renderers
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:
// - 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
// - Skinned vs Unskinned - with hardware lighting we don't need to
// duplicate mesh data per model instance (except for skinned models),
// so non-skinned models get different ModelVertexRenderers
// - Skinned vs Unskinned - we don't need to duplicate mesh data per
// model instance (except for skinned models), so non-skinned models
// get different ModelVertexRenderers
ModelRendererPtr NormalSkinned;
ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported
ModelRendererPtr TranspSkinned;
ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported
struct Submissions
{
std::vector<CModel*> submissions[CSceneRenderer::CULL_MAX];
};
ModelVertexRendererPtr VertexRendererShader;
ModelVertexRendererPtr VertexInstancingShader;
ModelVertexRendererPtr VertexGPUSkinningShader;
// Unskinned submissions should be prepared and rendered with
// VertexInstancingShader. Skinned - with Vertex*SkinningShader
// 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;
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.
*/
void CallModelRenderers(
void CallOpaqueModelRenderers(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode)
{
CShaderDefines contextSkinned = context;
if (g_RenderingOptions.GetGPUSkinning())
contextSkinned.Add(str_USE_INSTANCING, str_1);
Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode);
ModelVertexRenderer& modelVertexSkinningRenderer{
Model.GPUSkinningEnabled
? static_cast<ModelVertexRenderer&>(*Model.VertexGPUSkinningShader)
: static_cast<ModelVertexRenderer&>(Model.VertexCPUSkinningShader)};
if (Model.NormalUnskinned != Model.NormalSkinned)
{
CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode);
}
CShaderDefines contextSkinned = context;
if (Model.GPUSkinningEnabled)
contextSkinned.Add(str_USE_INSTANCING, str_1);
Model.modelRenderer.Render(deviceCommandContext, modelVertexSkinningRenderer, Model.ModShader, contextSkinned, cullGroup, flags, renderMode, Model.OpaqueSkinned.submissions[cullGroup]);
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.
*/
void CallTranspModelRenderers(
void CallTransparentModelRenderers(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags, const ERenderMode renderMode)
{
CShaderDefines contextSkinned = context;
if (g_RenderingOptions.GetGPUSkinning())
contextSkinned.Add(str_USE_INSTANCING, str_1);
Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode);
ModelVertexRenderer& modelVertexSkinningRenderer{
Model.GPUSkinningEnabled
? static_cast<ModelVertexRenderer&>(*Model.VertexGPUSkinningShader)
: static_cast<ModelVertexRenderer&>(Model.VertexCPUSkinningShader)};
if (Model.TranspUnskinned != Model.TranspSkinned)
{
CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode);
}
CShaderDefines contextSkinned = context;
if (Model.GPUSkinningEnabled)
contextSkinned.Add(str_USE_INSTANCING, str_1);
Model.modelRenderer.Render(deviceCommandContext, modelVertexSkinningRenderer, Model.ModShader, contextSkinned, cullGroup, flags, renderMode, Model.TransparentSkinned.submissions[cullGroup]);
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,
RenderDebugModeEnum::ToString(g_RenderingOptions.GetRenderDebugMode()));
m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier());
m->Model.VertexRendererShader = ModelVertexRendererPtr(new CPUSkinnedModelVertexRenderer());
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer());
if (g_RenderingOptions.GetGPUSkinning())
m->Model.GPUSkinningEnabled = g_RenderingOptions.GetGPUSkinning();
if (m->Model.GPUSkinningEnabled)
{
m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new GPUSkinnedModelModelRenderer());
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
if (!m->Model.VertexGPUSkinningShader.has_value())
m->Model.VertexGPUSkinningShader.emplace();
}
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()
@ -282,8 +383,8 @@ void CSceneRenderer::Resize(int /*width*/, int /*height*/)
void CSceneRenderer::BeginFrame()
{
// choose model renderers for this frame
m->Model.ModShader->SetShadowMap(&m->shadow);
m->Model.ModShader->SetLightEnv(m_LightEnv);
m->Model.ModShader.SetShadowMap(&m->shadow);
m->Model.ModShader.SetLightEnv(m_LightEnv);
}
void CSceneRenderer::SetSimulation(CSimulation2* simulation)
@ -315,12 +416,12 @@ void CSceneRenderer::RenderShadowMap(
{
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");
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{
m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID};
m->CallModelRenderers(deviceCommandContext, context, cullGroup, flags, modelRenderMode);
m->CallOpaqueModelRenderers(deviceCommandContext, context, cullGroup, flags, modelRenderMode);
if (m_ModelRenderMode == EDGED_FACES)
m->CallModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES);
m->CallOpaqueModelRenderers(deviceCommandContext, {}, cullGroup, flags, EDGED_FACES);
}
void CSceneRenderer::RenderTransparentModels(
@ -388,13 +489,13 @@ void CSceneRenderer::RenderTransparentModels(
m_ModelRenderMode == WIREFRAME ? WIREFRAME : SOLID};
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)
m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags, modelRenderMode);
m->CallTransparentModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags, modelRenderMode);
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)
@ -709,24 +810,24 @@ void CSceneRenderer::RenderSilhouettes(
{
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");
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
// a pixel once (using the color of whatever model happens to be drawn first).
{
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");
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;
// Prepare model renderers
{
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->PrepareModels(deviceCommandContext);
m->terrainRenderer.PrepareForRendering();
@ -786,15 +879,7 @@ void CSceneRenderer::PrepareSubmissions(
m->particleRenderer.PrepareForRendering(context);
{
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->UploadModels(deviceCommandContext);
m->overlayRenderer.Upload(deviceCommandContext);
@ -908,13 +993,13 @@ void CSceneRenderer::EndFrame()
m->particleRenderer.EndFrame();
m->silhouetteRenderer.EndFrame();
// Finish model renderers
m->Model.NormalSkinned->EndFrame();
m->Model.TranspSkinned->EndFrame();
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->EndFrame();
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->EndFrame();
for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup)
{
m->Model.OpaqueSkinned.submissions[cullGroup].clear();
m->Model.TransparentSkinned.submissions[cullGroup].clear();
m->Model.OpaqueUnskinned.submissions[cullGroup].clear();
m->Model.TransparentUnskinned.submissions[cullGroup].clear();
}
}
void CSceneRenderer::DisplayFrustum(Renderer::Backend::IDeviceCommandContext& deviceCommandContext)
@ -1029,21 +1114,25 @@ void CSceneRenderer::SubmitNonRecursive(CModel* model)
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 (requiresSkinning)
m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model);
m->Submit(m_CurrentCullGroup, modelVertexSkinningRenderer, m->Model.TransparentSkinned, model);
else
m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model);
m->Submit(m_CurrentCullGroup, m->Model.VertexInstancingShader, m->Model.TransparentUnskinned, model);
}
else
{
if (requiresSkinning)
m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model);
m->Submit(m_CurrentCullGroup, modelVertexSkinningRenderer, m->Model.OpaqueSkinned, model);
else
m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model);
m->Submit(m_CurrentCullGroup, m->Model.VertexInstancingShader, m->Model.OpaqueUnskinned, model);
}
}