From fa90ddc8b6e93b40ea1a6220d00b9d9f65988529 Mon Sep 17 00:00:00 2001 From: Vladislav Belov Date: Tue, 4 Aug 2026 23:06:09 +0200 Subject: [PATCH] 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. --- source/renderer/ModelRenderer.cpp | 100 ++--------------- source/renderer/ModelRenderer.h | 51 +-------- source/renderer/SceneRenderer.cpp | 180 +++++++++++++++++++++--------- 3 files changed, 142 insertions(+), 189 deletions(-) diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index fc9aad9171..97d02a74fc 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -217,89 +217,6 @@ void ModelRenderer::BuildIndices( } } -/** - * Internal data of the ModelRenderer. - * - * Separated into the source file to increase implementation hiding (and to - * avoid some causes of recompiles). - */ -struct ModelRenderer::ModelRendererInternals -{ - /// ModelVertexRenderer used for vertex transformations - ModelVertexRendererPtr vertexRenderer; - - /// List of submitted models for rendering in this frame - std::vector submissions[CSceneRenderer::CULL_MAX]; -}; - -ModelRenderer::ModelRenderer(ModelVertexRendererPtr vertexrenderer) -{ - m = std::unique_ptr(new ModelRendererInternals()); - m->vertexRenderer = vertexrenderer; -} - -ModelRenderer::~ModelRenderer() = default; - -// Submit one model. -void ModelRenderer::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 ModelRenderer::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(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(model->GetRenderData()); - rdata->m_UpdateFlags = 0; - } - } -} - -void ModelRenderer::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 ModelRenderer::EndFrame() -{ - for (int cullGroup = 0; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup) - m->submissions[cullGroup].clear(); -} - - // Helper structs for ShaderModelRenderer::Render(): struct SMRSortByDistItem @@ -389,10 +306,10 @@ struct SMRCompareTechBucket void ModelRenderer::Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const RenderModifierPtr& modifier, const CShaderDefines& context, - int cullGroup, int flags, const ERenderMode renderMode) + ModelVertexRenderer& modelVertexRenderer, const RenderModifierPtr& modifier, const CShaderDefines& context, + int cullGroup, int flags, const ERenderMode renderMode, std::span submissions) { - if (m->submissions[cullGroup].empty()) + if (submissions.empty()) return; CMatrix3D worldToCam; @@ -403,7 +320,7 @@ void ModelRenderer::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 @@ -471,9 +388,8 @@ void ModelRenderer::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)}; @@ -727,7 +643,7 @@ void ModelRenderer::Render( if (newModeldef != currentModeldef) { currentModeldef = newModeldef; - m->vertexRenderer->PrepareModelDef(deviceCommandContext, *currentModeldef); + modelVertexRenderer.PrepareModelDef(deviceCommandContext, *currentModeldef); } // Bind all uniforms when any change @@ -790,9 +706,9 @@ void ModelRenderer::Render( modifier->PrepareModel(deviceCommandContext, model); CModelRData* rdata = static_cast(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); } } diff --git a/source/renderer/ModelRenderer.h b/source/renderer/ModelRenderer.h index f00fb4a262..d45b1d5779 100644 --- a/source/renderer/ModelRenderer.h +++ b/source/renderer/ModelRenderer.h @@ -30,6 +30,7 @@ #include "lib/types.h" #include +#include #include class CModel; @@ -80,7 +81,7 @@ private: /** - * ModelRenderer manages a per-frame list of models. It loads the appropriate + * 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). * @@ -93,46 +94,6 @@ private: class ModelRenderer { public: - ModelRenderer(ModelVertexRendererPtr vertexrenderer); - ~ModelRenderer(); - - /** - * 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(int cullGroup, CModel* model); - - /** - * 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); - - /** - * 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); - - /** - * EndFrame: Remove all models from the list of submitted - * models. - */ - void EndFrame(); - /** * Render: Render submitted models, using the given RenderModifier to setup * the fragment stage. @@ -147,8 +108,8 @@ public: */ void Render( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - const RenderModifierPtr& modifier, const CShaderDefines& context, - int cullGroup, int flags, const ERenderMode renderMode); + ModelVertexRenderer& modelVertexRenderer, const RenderModifierPtr& modifier, const CShaderDefines& context, + int cullGroup, int flags, const ERenderMode renderMode, std::span submissions); /** * CopyPositionAndNormals: Copy unanimated object-space vertices and @@ -233,10 +194,6 @@ public: * The new vertices cannot be used with existing face index and must be welded/reindexed. */ static void GenTangents(const CModelDefPtr& mdef, std::vector& newVertices, bool gpuSkinning); - -private: - struct ModelRendererInternals; - std::unique_ptr m; }; #endif // INCLUDED_MODELRENDERER diff --git a/source/renderer/SceneRenderer.cpp b/source/renderer/SceneRenderer.cpp index 380b91b60b..9bbaac375c 100644 --- a/source/renderer/SceneRenderer.cpp +++ b/source/renderer/SceneRenderer.cpp @@ -118,16 +118,6 @@ public: /// 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: // - 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 @@ -135,13 +125,21 @@ public: // model instance (except for skinned models), so non-skinned models // get different ModelVertexRenderers - std::unique_ptr OpaqueSkinned; - std::unique_ptr OpaqueUnskinned; - std::unique_ptr TransparentSkinned; - std::unique_ptr TransparentUnskinned; + struct Submissions + { + ModelVertexRenderer* modelVertexRenderer{nullptr}; + std::vector submissions[CSceneRenderer::CULL_MAX]; + }; + + Submissions OpaqueSkinned; + Submissions OpaqueUnskinned; + Submissions TransparentSkinned; + Submissions TransparentUnskinned; + + ModelRenderer modelRenderer; - ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; + ModelVertexRendererPtr VertexCPUSkinningShader; ModelVertexRendererPtr VertexGPUSkinningShader; LitRenderModifierPtr ModShader; @@ -149,6 +147,94 @@ public: 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"); + + for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup) + { + Model.OpaqueSkinned.modelVertexRenderer->UploadModelsData(deviceCommandContext, Model.OpaqueSkinned.submissions[cullGroup]); + Model.TransparentSkinned.modelVertexRenderer->UploadModelsData(deviceCommandContext, Model.TransparentSkinned.submissions[cullGroup]); + Model.OpaqueUnskinned.modelVertexRenderer->UploadModelsData(deviceCommandContext, Model.OpaqueUnskinned.submissions[cullGroup]); + Model.TransparentUnskinned.modelVertexRenderer->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"); + + for (int cullGroup{0}; cullGroup < CSceneRenderer::CULL_MAX; ++cullGroup) + { + PrepareModels(deviceCommandContext, *Model.OpaqueSkinned.modelVertexRenderer, Model.OpaqueSkinned.submissions[cullGroup]); + PrepareModels(deviceCommandContext, *Model.TransparentSkinned.modelVertexRenderer, Model.TransparentSkinned.submissions[cullGroup]); + PrepareModels(deviceCommandContext, *Model.OpaqueUnskinned.modelVertexRenderer, Model.OpaqueUnskinned.submissions[cullGroup]); + PrepareModels(deviceCommandContext, *Model.TransparentUnskinned.modelVertexRenderer, Model.TransparentUnskinned.submissions[cullGroup]); + } + } + + void PrepareModels( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, ModelVertexRenderer& modelVertexRenderer, std::span submissions) + { + for (CModel* model : submissions) + { + model->ValidatePosition(); + + CModelRData* rdata = static_cast(model->GetRenderData()); + ENSURE(rdata->GetKey() == &modelVertexRenderer); + } + + modelVertexRenderer.UpdateModelsData(deviceCommandContext, submissions); + + for (CModel* model : submissions) + { + CModelRData* rdata = static_cast(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(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. */ @@ -159,11 +245,11 @@ public: CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) contextSkinned.Add(str_USE_INSTANCING, str_1); - Model.OpaqueSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode); + Model.modelRenderer.Render(deviceCommandContext, *Model.OpaqueSkinned.modelVertexRenderer, Model.ModShader, contextSkinned, cullGroup, flags, renderMode, Model.OpaqueSkinned.submissions[cullGroup]); CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); - Model.OpaqueUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode); + Model.modelRenderer.Render(deviceCommandContext, *Model.OpaqueUnskinned.modelVertexRenderer, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode, Model.OpaqueUnskinned.submissions[cullGroup]); } /** @@ -176,11 +262,11 @@ public: CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) contextSkinned.Add(str_USE_INSTANCING, str_1); - Model.TransparentSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags, renderMode); + Model.modelRenderer.Render(deviceCommandContext, *Model.TransparentSkinned.modelVertexRenderer, Model.ModShader, contextSkinned, cullGroup, flags, renderMode, Model.TransparentSkinned.submissions[cullGroup]); CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); - Model.TransparentUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode); + Model.modelRenderer.Render(deviceCommandContext, *Model.TransparentUnskinned.modelVertexRenderer, Model.ModShader, contextUnskinned, cullGroup, flags, renderMode, Model.TransparentUnskinned.submissions[cullGroup]); } }; @@ -235,24 +321,24 @@ void CSceneRenderer::ReloadShaders([[maybe_unused]] Renderer::Backend::IDevice* m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); - m->Model.VertexRendererShader = ModelVertexRendererPtr(new CPUSkinnedModelVertexRenderer()); + m->Model.VertexCPUSkinningShader = ModelVertexRendererPtr(new CPUSkinnedModelVertexRenderer()); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer()); if (g_RenderingOptions.GetGPUSkinning()) { m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new GPUSkinnedModelModelRenderer()); - m->Model.OpaqueSkinned = std::make_unique(m->Model.VertexGPUSkinningShader); - m->Model.TransparentSkinned = std::make_unique(m->Model.VertexGPUSkinningShader); + m->Model.OpaqueSkinned.modelVertexRenderer = m->Model.VertexGPUSkinningShader.get(); + m->Model.TransparentSkinned.modelVertexRenderer = m->Model.VertexGPUSkinningShader.get(); } else { m->Model.VertexGPUSkinningShader.reset(); - m->Model.OpaqueSkinned = std::make_unique(m->Model.VertexRendererShader); - m->Model.TransparentSkinned = std::make_unique(m->Model.VertexRendererShader); + m->Model.OpaqueSkinned.modelVertexRenderer = m->Model.VertexCPUSkinningShader.get(); + m->Model.TransparentSkinned.modelVertexRenderer = m->Model.VertexCPUSkinningShader.get(); } - m->Model.OpaqueUnskinned = std::make_unique(m->Model.VertexInstancingShader); - m->Model.TransparentUnskinned = std::make_unique(m->Model.VertexInstancingShader); + m->Model.OpaqueUnskinned.modelVertexRenderer = m->Model.VertexInstancingShader.get(); + m->Model.TransparentUnskinned.modelVertexRenderer = m->Model.VertexInstancingShader.get(); } void CSceneRenderer::Initialize() @@ -764,13 +850,7 @@ void CSceneRenderer::PrepareSubmissions( CShaderDefines context = m->globalContext; // Prepare model renderers - { - PROFILE3("prepare models"); - m->Model.OpaqueSkinned->PrepareModels(deviceCommandContext); - m->Model.TransparentSkinned->PrepareModels(deviceCommandContext); - m->Model.OpaqueUnskinned->PrepareModels(deviceCommandContext); - m->Model.TransparentUnskinned->PrepareModels(deviceCommandContext); - } + m->PrepareModels(deviceCommandContext); m->terrainRenderer.PrepareForRendering(); @@ -778,13 +858,7 @@ void CSceneRenderer::PrepareSubmissions( m->particleRenderer.PrepareForRendering(context); - { - PROFILE3("upload models"); - m->Model.OpaqueSkinned->UploadModels(deviceCommandContext); - m->Model.TransparentSkinned->UploadModels(deviceCommandContext); - m->Model.OpaqueUnskinned->UploadModels(deviceCommandContext); - m->Model.TransparentUnskinned->UploadModels(deviceCommandContext); - } + m->UploadModels(deviceCommandContext); m->overlayRenderer.Upload(deviceCommandContext); @@ -898,11 +972,13 @@ void CSceneRenderer::EndFrame() m->particleRenderer.EndFrame(); m->silhouetteRenderer.EndFrame(); - // Finish model renderers - m->Model.OpaqueSkinned->EndFrame(); - m->Model.TransparentSkinned->EndFrame(); - m->Model.OpaqueUnskinned->EndFrame(); - m->Model.TransparentUnskinned->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) @@ -1017,21 +1093,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{ + g_RenderingOptions.GetGPUSkinning() ? *m->Model.VertexGPUSkinningShader : *m->Model.VertexCPUSkinningShader}; + ModelVertexRenderer& modelVertexRenderer{ + requiresSkinning ? modelVertexSkinningRenderer : *m->Model.VertexInstancingShader}; if (model->GetMaterial().UsesAlphaBlending()) { if (requiresSkinning) - m->Model.TransparentSkinned->Submit(m_CurrentCullGroup, model); + m->Submit(m_CurrentCullGroup, modelVertexRenderer, m->Model.TransparentSkinned, model); else - m->Model.TransparentUnskinned->Submit(m_CurrentCullGroup, model); + m->Submit(m_CurrentCullGroup, modelVertexRenderer, m->Model.TransparentUnskinned, model); } else { if (requiresSkinning) - m->Model.OpaqueSkinned->Submit(m_CurrentCullGroup, model); + m->Submit(m_CurrentCullGroup, modelVertexRenderer, m->Model.OpaqueSkinned, model); else - m->Model.OpaqueUnskinned->Submit(m_CurrentCullGroup, model); + m->Submit(m_CurrentCullGroup, modelVertexRenderer, m->Model.OpaqueUnskinned, model); } }