From de1257c8da14e09c79f0c14965c2c317784e5759 Mon Sep 17 00:00:00 2001 From: Vladislav Belov Date: Mon, 9 Dec 2024 22:47:17 +0100 Subject: [PATCH] Adds GPU skinning support via compute shaders. We use a shader with 64 bones by default to consume less uniform memory. But if we meet bigger skeletons we batch and handle them afterwards. In the future we need to sort the input models to have less changes for bound buffers. Also we might want to skin up to 4 models per a single dispatch. --- source/collada/PSAConvert.cpp | 6 +- source/graphics/Model.cpp | 5 +- source/ps/CStrInternStatic.h | 8 +- source/renderer/GPUSkinnedModelRenderer.cpp | 483 ++++++++++++++++++++ source/renderer/GPUSkinnedModelRenderer.h | 63 +++ source/renderer/InstancingModelRenderer.cpp | 106 +---- source/renderer/InstancingModelRenderer.h | 4 +- source/renderer/ModelRenderer.cpp | 2 +- source/renderer/ModelVertexRenderer.h | 2 +- source/renderer/Renderer.h | 1 + source/renderer/RenderingOptions.cpp | 10 +- source/renderer/SceneRenderer.cpp | 15 +- source/renderer/VertexArray.cpp | 27 +- source/renderer/VertexArray.h | 13 +- source/renderer/VertexBufferManager.cpp | 6 +- 15 files changed, 616 insertions(+), 135 deletions(-) create mode 100644 source/renderer/GPUSkinnedModelRenderer.cpp create mode 100644 source/renderer/GPUSkinnedModelRenderer.h diff --git a/source/collada/PSAConvert.cpp b/source/collada/PSAConvert.cpp index 70f0d6cc80..054694a501 100644 --- a/source/collada/PSAConvert.cpp +++ b/source/collada/PSAConvert.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -91,8 +91,8 @@ public: // (TODO: sort out the timing/looping problems) const size_t boneCount = skeleton.GetBoneCount(); - if (boneCount > 64) - Log(LOG_ERROR, "Skeleton has too many bones %zu/64", boneCount); + if (boneCount > 192) + Log(LOG_ERROR, "Skeleton has too many bones %zu/192", boneCount); std::vector boneTransforms; diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 982961bab9..cc7e390520 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -239,8 +239,7 @@ void CModel::ValidatePosition() // in the vertex shader instead. // Using g_RenderingOptions to detect CPU vs GPU is a bit hacky, // and this doesn't allow the setting to change at runtime, but there isn't - // an obvious cleaner way to determine what data needs to be computed, - // and GPU skinning is a rarely-used experimental feature anyway. + // an obvious cleaner way to determine what data needs to be computed. bool worldSpaceBoneMatrices = !g_RenderingOptions.GetGPUSkinning(); bool computeBlendMatrices = !g_RenderingOptions.GetGPUSkinning(); diff --git a/source/ps/CStrInternStatic.h b/source/ps/CStrInternStatic.h index 76e8f5326a..33f91ebcd0 100644 --- a/source/ps/CStrInternStatic.h +++ b/source/ps/CStrInternStatic.h @@ -67,7 +67,6 @@ X(USE_DESCRIPTOR_INDEXING) X(USE_FANCY_EFFECTS) X(USE_FP_SHADOW) X(USE_GPU_INSTANCING) -X(USE_GPU_SKINNING) X(USE_INSTANCING) X(USE_NORMALS) X(USE_OBJECTCOLOR) @@ -81,6 +80,10 @@ X(USE_FOG) X(WATERTYPE_CLAP) X(WATERTYPE_LAKE) X2(_emptystring, "") +X(OutputNormalsTangents) +X(OutputPositions) +X(SkinData) +X(InputVertices) X(ambient) X(baseTex) X(blendTex) @@ -96,6 +99,7 @@ X(color) X(colorAdd) X(colorMul) X(compute_rcas) +X(compute_skinning) X(compute_upscale_fsr) X(debug_line) X(debug_overlay) @@ -127,6 +131,7 @@ X(murkiness) X(normalMap) X(normalMap2) X(objectColor) +X(offset) X(overlay_line) X(overlay_solid) X(outTex) @@ -175,6 +180,7 @@ X(transform) X(translation) X(upscale_bilinear) X(upscale_nearest) +X(vertexCount) X(viewInvTransform) X(water_high) X(water_simple) diff --git a/source/renderer/GPUSkinnedModelRenderer.cpp b/source/renderer/GPUSkinnedModelRenderer.cpp new file mode 100644 index 0000000000..75bc145f1c --- /dev/null +++ b/source/renderer/GPUSkinnedModelRenderer.cpp @@ -0,0 +1,483 @@ +/* Copyright (C) 2024 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "precompiled.h" + +#include "renderer/GPUSkinnedModelRenderer.h" + +#include "graphics/Color.h" +#include "graphics/LightEnv.h" +#include "graphics/Model.h" +#include "graphics/ModelDef.h" +#include "graphics/ShaderManager.h" +#include "maths/MathUtil.h" +#include "maths/Vector3D.h" +#include "maths/Vector4D.h" +#include "ps/CLogger.h" +#include "ps/containers/StaticVector.h" +#include "ps/CStrInternStatic.h" +#include "renderer/Renderer.h" +#include "renderer/RenderModifiers.h" +#include "renderer/VertexArray.h" +#include "third_party/mikktspace/weldmesh.h" + +namespace +{ + +// We have the following structure for input and output vertices: +// +// Created on a model load and read by a compute shader for each frame where +// the model is visible. +// InputVertex(size/stride=64): +// vec4/CVector4D tangent (offset=0) +// vec3/CVector3D normal (offset=16) +// vec3/CVector3D position (offset=32) +// +// Written by a compute shader for each frame where the model is visible. +// OutputPosition(size/stride=16): +// vec3/CVector3D position (offset=0) +// +// Written by a compute shader for each frame where the model is visible. +// Each component is 16-bits float to consume less memory. +// OutputNormalTangent(size/stride=16) +// 16bits vec3 normal (offset=0) +// 16bits vec4 tangent (offset=8) + +constexpr uint32_t INPUT_VERTEX_ATTRIBUTE_ALIGNMENT{16}; +constexpr uint32_t INPUT_VERTEX_TANGENT_OFFSET{0}; +constexpr uint32_t INPUT_VERTEX_NORMAL_OFFSET{16}; +constexpr uint32_t INPUT_VERTEX_POSITION_OFFSET{32}; + +constexpr uint32_t OUTPUT_POSITION_STRIDE{16}; +constexpr uint32_t OUTPUT_NORMAL_TANGENT_STRIDE{16}; +constexpr uint32_t OUTPUT_NORMAL_OFFSET{0}; +constexpr uint32_t OUTPUT_TANGENT_OFFSET{8}; + +class ModelDefRData : public CModelDefRPrivate +{ +public: + // Static per-CModel vertex array + VertexArray m_Array; + + // Position and normals are static + VertexArray::Attribute m_Position; + VertexArray::Attribute m_Normal; + VertexArray::Attribute m_Tangent; + + VertexArray m_BlendArray; + VertexArray::Attribute m_BlendJoints; + VertexArray::Attribute m_BlendWeights; + + VertexArray m_UVArray; + + // The number of UVs is determined by the model + std::vector m_UVs; + + Renderer::Backend::IVertexInputLayout* m_VertexInputLayout{nullptr}; + + // Indices are the same for all models, so share them + VertexIndexArray m_IndexArray; + + ModelDefRData(const CModelDefPtr& mdef); +}; + +ModelDefRData::ModelDefRData(const CModelDefPtr& modelDef) + : m_IndexArray(Renderer::Backend::IBuffer::Usage::TRANSFER_DST), + m_Array(Renderer::Backend::IBuffer::Type::VERTEX, + Renderer::Backend::IBuffer::Usage::TRANSFER_DST | + Renderer::Backend::IBuffer::Usage::STORAGE), + m_BlendArray(Renderer::Backend::IBuffer::Type::VERTEX, + Renderer::Backend::IBuffer::Usage::TRANSFER_DST | + Renderer::Backend::IBuffer::Usage::STORAGE), + m_UVArray(Renderer::Backend::IBuffer::Type::VERTEX, + Renderer::Backend::IBuffer::Usage::TRANSFER_DST) +{ + m_Position.format = Renderer::Backend::Format::R32G32B32_SFLOAT; + m_Array.AddAttribute(&m_Position); + + m_Normal.format = Renderer::Backend::Format::R32G32B32_SFLOAT; + m_Array.AddAttribute(&m_Normal); + + // TODO: switch to 16-bits tangents when possible. + m_Tangent.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT; + m_Array.AddAttribute(&m_Tangent); + + m_UVs.resize(modelDef->GetNumUVsPerVertex()); + for (uint32_t index{0}; index < modelDef->GetNumUVsPerVertex(); ++index) + { + m_UVs[index].format = Renderer::Backend::Format::R32G32_SFLOAT; + m_UVArray.AddAttribute(&m_UVs[index]); + } + + // We can't use a lot of bones because it costs uniform memory. Recommended + // number of bones per model is 32. + // Add 1 to NumBones because of the special 'root' bone. + if (modelDef->GetNumBones() + 1 > 192) + LOGERROR("Model '%s' has too many bones %zu/192", modelDef->GetName().string8().c_str(), modelDef->GetNumBones() + 1); + ENSURE(modelDef->GetNumBones() + 1 <= 192); + + m_BlendJoints.format = Renderer::Backend::Format::R8G8B8A8_UINT; + m_BlendArray.AddAttribute(&m_BlendJoints); + + m_BlendWeights.format = Renderer::Backend::Format::R8G8B8A8_UNORM; + m_BlendArray.AddAttribute(&m_BlendWeights); + + // Generate tangents for the geometry: + + // floats per vertex; position + normal + tangent + UV*sets + GPUskinning (joint index and weight) + const uint32_t numberOfFloatsPerVertex{ + static_cast(3 + 3 + 4 + 2 * modelDef->GetNumUVsPerVertex() + 8)}; + + // the tangent generation can increase the number of vertices temporarily + // so reserve a bit more memory to avoid reallocations in GenTangents (in most cases) + std::vector newVertices; + newVertices.reserve(numberOfFloatsPerVertex * modelDef->GetNumVertices() * 2); + + // Generate the tangents. + ModelRenderer::GenTangents(modelDef, newVertices, true); + + // How many vertices do we have after generating tangents? + const uint32_t newNumberOfVertices{static_cast(newVertices.size()) / numberOfFloatsPerVertex}; + + std::vector remapTable(newNumberOfVertices); + std::vector vertexDataOut(newNumberOfVertices * numberOfFloatsPerVertex); + + // Re-weld the mesh to remove duplicated vertices. + const int finalNumberOfVertices{WeldMesh( + remapTable.data(), vertexDataOut.data(), newVertices.data(), newNumberOfVertices, numberOfFloatsPerVertex)}; + + // Copy the model data to graphics memory. + + m_Array.SetNumberOfVertices(finalNumberOfVertices); + m_Array.SetMinimumAttributeAlignment(INPUT_VERTEX_ATTRIBUTE_ALIGNMENT); + m_Array.Layout(); + + m_BlendArray.SetNumberOfVertices(finalNumberOfVertices); + m_BlendArray.Layout(); + + m_UVArray.SetNumberOfVertices(finalNumberOfVertices); + m_UVArray.Layout(); + + VertexArrayIterator positionIt{m_Position.GetIterator()}; + VertexArrayIterator normalIt{m_Normal.GetIterator()}; + VertexArrayIterator tangentIt{m_Tangent.GetIterator()}; + + VertexArrayIterator blendJointsIt{m_BlendJoints.GetIterator()}; + VertexArrayIterator blendWeightsIt{m_BlendWeights.GetIterator()}; + + // Copy everything into the vertex array. + for (int index{0}; index < finalNumberOfVertices; ++index) + { + uint32_t inputDataOffset{numberOfFloatsPerVertex * index}; + + positionIt[index] = CVector3D{ + vertexDataOut[inputDataOffset + 0], + vertexDataOut[inputDataOffset + 1], + vertexDataOut[inputDataOffset + 2]}; + inputDataOffset += 3; + + normalIt[index] = CVector3D{ + vertexDataOut[inputDataOffset + 0], + vertexDataOut[inputDataOffset + 1], + vertexDataOut[inputDataOffset + 2]}; + inputDataOffset += 3; + + tangentIt[index] = CVector4D{ + vertexDataOut[inputDataOffset + 0], + vertexDataOut[inputDataOffset + 1], + vertexDataOut[inputDataOffset + 2], + vertexDataOut[inputDataOffset + 3]}; + inputDataOffset += 4; + + for (uint32_t j{0}; j < 4; ++j) + { + blendJointsIt[index][j] = static_cast(vertexDataOut[inputDataOffset + 0 + 2 * j]); + blendWeightsIt[index][j] = static_cast(vertexDataOut[inputDataOffset + 1 + 2 * j]); + } + inputDataOffset += 8; + + for (uint32_t uvIndex{0}; uvIndex < modelDef->GetNumUVsPerVertex(); uvIndex++) + { + VertexArrayIterator UVit{m_UVs[uvIndex].GetIterator()}; + UVit[index][0] = vertexDataOut[inputDataOffset + 0 + 2 * uvIndex]; + UVit[index][1] = vertexDataOut[inputDataOffset + 1 + 2 * uvIndex]; + } + } + + // Upload vertex data. + m_Array.Upload(); + m_Array.FreeBackingStore(); + + m_BlendArray.Upload(); + m_BlendArray.FreeBackingStore(); + + if (m_UVArray.GetStride() > 0) + { + m_UVArray.Upload(); + m_UVArray.FreeBackingStore(); + } + + ENSURE(m_Array.GetStride() == INPUT_VERTEX_ATTRIBUTE_ALIGNMENT * 4); + ENSURE(m_Position.offset == INPUT_VERTEX_POSITION_OFFSET); + ENSURE(m_Normal.offset == INPUT_VERTEX_NORMAL_OFFSET); + ENSURE(m_Tangent.offset == INPUT_VERTEX_TANGENT_OFFSET); + + ENSURE(m_BlendArray.GetStride() == 8); + ENSURE(m_BlendJoints.offset % 4 == 0); + ENSURE(m_BlendWeights.offset % 4 == 0); + + m_IndexArray.SetNumberOfVertices(modelDef->GetNumFaces() * 3); + m_IndexArray.Layout(); + + // Re-index geometry and upload index. + VertexArrayIterator indices{m_IndexArray.GetIterator()}; + for (uint32_t index{0}; index < modelDef->GetNumFaces() * 3; ++index) + indices[index] = remapTable[index]; + m_IndexArray.Upload(); + m_IndexArray.FreeBackingStore(); + + constexpr size_t MAX_UV{2}; + + PS::StaticVector attributes{ + {Renderer::Backend::VertexAttributeStream::POSITION, + m_Position.format, 0, OUTPUT_POSITION_STRIDE, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}, + {Renderer::Backend::VertexAttributeStream::NORMAL, + Renderer::Backend::Format::R16G16B16_SFLOAT, OUTPUT_NORMAL_OFFSET, OUTPUT_NORMAL_TANGENT_STRIDE, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1}, + {Renderer::Backend::VertexAttributeStream::UV2, + Renderer::Backend::Format::R16G16B16A16_SFLOAT, OUTPUT_TANGENT_OFFSET, OUTPUT_NORMAL_TANGENT_STRIDE, + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 1} + }; + + for (size_t uv{0}; uv < std::min(MAX_UV, modelDef->GetNumUVsPerVertex()); ++uv) + { + const Renderer::Backend::VertexAttributeStream stream = + static_cast( + static_cast(Renderer::Backend::VertexAttributeStream::UV0) + uv); + attributes.push_back({ + stream, m_UVs[uv].format, m_UVs[uv].offset, m_UVArray.GetStride(), + Renderer::Backend::VertexAttributeRate::PER_VERTEX, 2}); + } + + m_VertexInputLayout = g_Renderer.GetVertexInputLayout({attributes.begin(), attributes.end()}); +} + +struct ModelRData : public CModelRData +{ + // We have a separate position array because we don't need other attributes + // for some passes (like shadows). + CVertexBufferManager::Handle m_PositionHandle; + CVertexBufferManager::Handle m_NormalTangentHandle; + + ModelRData(const void* key) + : CModelRData(key) + {} +}; + +} // anonymous namespace + +struct GPUSkinnedModelModelRenderer::Internals +{ + // Previously prepared modeldef + ModelDefRData* modelDefRData; + + // Shader technique for models with up to 64 bones. + CShaderTechniquePtr skinningShaderTechnique64; + // Shader technique for models with up to 192 bones. + CShaderTechniquePtr skinningShaderTechnique192; +}; + +GPUSkinnedModelModelRenderer::GPUSkinnedModelModelRenderer() + : m(std::make_unique()) +{ + m->modelDefRData = nullptr; + CShaderDefines shaderDefines64; + shaderDefines64.Add(CStrIntern{"MAX_BONES"}, CStrIntern{"64"}); + m->skinningShaderTechnique64 = g_Renderer.GetShaderManager().LoadEffect(str_compute_skinning, shaderDefines64); + CShaderDefines shaderDefines192; + shaderDefines192.Add(CStrIntern{"MAX_BONES"}, CStrIntern{"192"}); + m->skinningShaderTechnique192 = g_Renderer.GetShaderManager().LoadEffect(str_compute_skinning, shaderDefines192); +} + +GPUSkinnedModelModelRenderer::~GPUSkinnedModelModelRenderer() = default; + +CModelRData* GPUSkinnedModelModelRenderer::CreateModelData(const void* key, CModel* model) +{ + ENSURE(model->IsSkinned()); + CModelDefPtr modelDef{model->GetModelDef()}; + ModelDefRData* modelDefRData{static_cast(modelDef->GetRenderData(m.get()))}; + + if (!modelDefRData) + { + modelDefRData = new ModelDefRData(modelDef); + modelDef->SetRenderData(m.get(), modelDefRData); + } + + ModelRData* modelRData{new ModelRData(key)}; + + const size_t numberOfVertices{modelDefRData->m_Array.GetNumberOfVertices()}; + modelRData->m_PositionHandle = g_Renderer.GetVertexBufferManager().AllocateChunk( + OUTPUT_POSITION_STRIDE, numberOfVertices, Renderer::Backend::IBuffer::Type::VERTEX, + Renderer::Backend::IBuffer::Usage::STORAGE, + nullptr, CVertexBufferManager::Group::WATER); + modelRData->m_NormalTangentHandle = g_Renderer.GetVertexBufferManager().AllocateChunk( + OUTPUT_NORMAL_TANGENT_STRIDE, numberOfVertices, Renderer::Backend::IBuffer::Type::VERTEX, + Renderer::Backend::IBuffer::Usage::STORAGE, + nullptr, CVertexBufferManager::Group::WATER); + + return modelRData; +} + +void GPUSkinnedModelModelRenderer::UpdateModelsData( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + PS::span models) +{ + if (models.empty()) + return; + + GPU_SCOPED_LABEL(deviceCommandContext, "Compute Skinning"); + + // Models with up to 192 bones. + std::vector models192; + + deviceCommandContext->InsertMemoryBarrier( + Renderer::Backend::PipelineStage::VERTEX_INPUT, Renderer::Backend::PipelineStage::COMPUTE_SHADER, + Renderer::Backend::Access::VERTEX_ATTRIBUTE_READ | Renderer::Backend::Access::INDEX_READ, + Renderer::Backend::Access::SHADER_READ | Renderer::Backend::Access::SHADER_WRITE); + deviceCommandContext->BeginComputePass(); + deviceCommandContext->SetComputePipelineState( + m->skinningShaderTechnique64->GetComputePipelineState()); + + for (CModel* model : models) + { + ENSURE(model->IsSkinned()); + CModelDefPtr modelDef{model->GetModelDef()}; + if (modelDef->GetNumBones() + 1 > 64) + { + models192.emplace_back(model); + continue; + } + CModelRData* rdata{static_cast(model->GetRenderData())}; + UpdateModelData(deviceCommandContext, m->skinningShaderTechnique64->GetShader(), model, rdata, rdata->m_UpdateFlags); + } + + if (!models192.empty()) + { + deviceCommandContext->SetComputePipelineState( + m->skinningShaderTechnique192->GetComputePipelineState()); + for (CModel* model : models192) + { + CModelRData* rdata{static_cast(model->GetRenderData())}; + UpdateModelData(deviceCommandContext, m->skinningShaderTechnique192->GetShader(), model, rdata, rdata->m_UpdateFlags); + } + } + + deviceCommandContext->EndComputePass(); + deviceCommandContext->InsertMemoryBarrier( + Renderer::Backend::PipelineStage::COMPUTE_SHADER, Renderer::Backend::PipelineStage::VERTEX_INPUT, + Renderer::Backend::Access::SHADER_READ | Renderer::Backend::Access::SHADER_WRITE, + Renderer::Backend::Access::VERTEX_ATTRIBUTE_READ | Renderer::Backend::Access::INDEX_READ); +} + +void GPUSkinnedModelModelRenderer::UpdateModelData( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + Renderer::Backend::IShaderProgram* shaderProgram, + CModel* model, CModelRData* data, int updateflags) +{ + CModelDefPtr modelDef{model->GetModelDef()}; + ModelDefRData* modelDefRData{static_cast(modelDef->GetRenderData(m.get()))}; + ModelRData* modelRData{static_cast(data)}; + + if (updateflags & RENDERDATA_UPDATE_VERTICES) + { + constexpr uint32_t threadGroupWorkRegionDim{64}; + const uint32_t vertexCount{static_cast(modelRData->m_PositionHandle->m_Count)}; + const uint32_t dispatchGroupCountX{DivideRoundUp(vertexCount, threadGroupWorkRegionDim)}; + + // Bind matrices for current animation state. + // Add 1 to NumBones because of the special 'root' bone. + deviceCommandContext->SetUniform( + shaderProgram->GetBindingSlot(str_skinBlendMatrices), + PS::span( + model->GetAnimatedBoneMatrices()[0]._data, + model->GetAnimatedBoneMatrices()[0].AsFloatArray().size() * (modelDef->GetNumBones() + 1))); + + ENSURE(modelRData->m_PositionHandle->m_Count == modelRData->m_NormalTangentHandle->m_Count); + deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_vertexCount), + static_cast(vertexCount)); + deviceCommandContext->SetUniform(shaderProgram->GetBindingSlot(str_offset), + static_cast(modelDefRData->m_Array.GetOffset()), + static_cast(modelDefRData->m_BlendArray.GetOffset()), + static_cast(modelRData->m_PositionHandle->m_Index), + static_cast(modelRData->m_NormalTangentHandle->m_Index)); + deviceCommandContext->SetStorageBuffer(shaderProgram->GetBindingSlot(str_InputVertices), modelDefRData->m_Array.GetBuffer()); + deviceCommandContext->SetStorageBuffer(shaderProgram->GetBindingSlot(str_SkinData), modelDefRData->m_BlendArray.GetBuffer()); + deviceCommandContext->SetStorageBuffer(shaderProgram->GetBindingSlot(str_OutputPositions), modelRData->m_PositionHandle->m_Owner->GetBuffer()); + deviceCommandContext->SetStorageBuffer(shaderProgram->GetBindingSlot(str_OutputNormalsTangents), modelRData->m_NormalTangentHandle->m_Owner->GetBuffer()); + deviceCommandContext->Dispatch(dispatchGroupCountX, 1, 1); + } +} + +void GPUSkinnedModelModelRenderer::UploadModelsData( + Renderer::Backend::IDeviceCommandContext* UNUSED(deviceCommandContext), + PS::span UNUSED(models)) +{ +} + +void GPUSkinnedModelModelRenderer::PrepareModelDef( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + const CModelDef& def) +{ + m->modelDefRData = static_cast(def.GetRenderData(m.get())); + ENSURE(m->modelDefRData); + + deviceCommandContext->SetVertexInputLayout(m->modelDefRData->m_VertexInputLayout); + deviceCommandContext->SetIndexBuffer(m->modelDefRData->m_IndexArray.GetBuffer()); + + if (m->modelDefRData->m_UVArray.GetStride() > 0) + { + const uint32_t firstVertexOffset{ + m->modelDefRData->m_UVArray.GetOffset() * m->modelDefRData->m_UVArray.GetStride()}; + deviceCommandContext->SetVertexBuffer( + 2, m->modelDefRData->m_UVArray.GetBuffer(), firstVertexOffset); + } +} + +void GPUSkinnedModelModelRenderer::RenderModel( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + Renderer::Backend::IShaderProgram* UNUSED(shader), CModel* model, CModelRData* data) +{ + ModelRData* modelRData{static_cast(data)}; + + // Render the lot. + const size_t numberOfFaces{model->GetModelDef()->GetNumFaces()}; + + deviceCommandContext->SetVertexBuffer( + 0, modelRData->m_PositionHandle->m_Owner->GetBuffer(), + modelRData->m_PositionHandle->m_Index * OUTPUT_POSITION_STRIDE); + deviceCommandContext->SetVertexBuffer( + 1, modelRData->m_NormalTangentHandle->m_Owner->GetBuffer(), + modelRData->m_NormalTangentHandle->m_Index * OUTPUT_NORMAL_TANGENT_STRIDE); + + deviceCommandContext->DrawIndexed( + m->modelDefRData->m_IndexArray.GetOffset(), numberOfFaces * 3, 0); + + // Bump stats. + g_Renderer.m_Stats.m_DrawCalls++; + g_Renderer.m_Stats.m_ModelTris += numberOfFaces; +} diff --git a/source/renderer/GPUSkinnedModelRenderer.h b/source/renderer/GPUSkinnedModelRenderer.h new file mode 100644 index 0000000000..51fd4686a6 --- /dev/null +++ b/source/renderer/GPUSkinnedModelRenderer.h @@ -0,0 +1,63 @@ +/* Copyright (C) 2024 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_RENDERER_GPUSKINNEDMODELRENDERER +#define INCLUDED_RENDERER_GPUSKINNEDMODELRENDERER + +#include "renderer/ModelVertexRenderer.h" + +#include + +/** + * Render animated models using a ShaderRenderModifier. + * It calculates vertex data for models on the GPU side. + * This computes and binds per-vertex data; the modifier is responsible + * for setting any shader uniforms etc. + */ +class GPUSkinnedModelModelRenderer : public ModelVertexRenderer +{ +public: + GPUSkinnedModelModelRenderer(); + ~GPUSkinnedModelModelRenderer(); + + CModelRData* CreateModelData(const void* key, CModel* model) override; + + void UpdateModelsData( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + PS::span models) override; + + void UploadModelsData( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + PS::span models) override; + + void PrepareModelDef( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + const CModelDef& def) override; + void RenderModel(Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + Renderer::Backend::IShaderProgram* shader, CModel* model, CModelRData* data) override; + +private: + void UpdateModelData( + Renderer::Backend::IDeviceCommandContext* deviceCommandContext, + Renderer::Backend::IShaderProgram* shaderProgram, + CModel* model, CModelRData* data, int updateflags); + + struct Internals; + const std::unique_ptr m; +}; + +#endif // INCLUDED_RENDERER_GPUSKINNEDMODELRENDERER diff --git a/source/renderer/InstancingModelRenderer.cpp b/source/renderer/InstancingModelRenderer.cpp index 663a1077db..2bddcb2914 100644 --- a/source/renderer/InstancingModelRenderer.cpp +++ b/source/renderer/InstancingModelRenderer.cpp @@ -42,8 +42,6 @@ struct IModelDef : public CModelDefRPrivate VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; VertexArray::Attribute m_Tangent; - VertexArray::Attribute m_BlendJoints; // valid iff gpuSkinning == true - VertexArray::Attribute m_BlendWeights; // valid iff gpuSkinning == true /// The number of UVs is determined by the model std::vector m_UVs; @@ -53,11 +51,11 @@ struct IModelDef : public CModelDefRPrivate /// Indices are the same for all models, so share them VertexIndexArray m_IndexArray; - IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents); + IModelDef(const CModelDefPtr& mdef, bool calculateTangents); }; -IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateTangents) +IModelDef::IModelDef(const CModelDefPtr& mdef, bool calculateTangents) : m_IndexArray(Renderer::Backend::IBuffer::Usage::TRANSFER_DST), m_Array(Renderer::Backend::IBuffer::Type::VERTEX, Renderer::Backend::IBuffer::Usage::TRANSFER_DST) { @@ -76,22 +74,6 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT m_Array.AddAttribute(&m_UVs[i]); } - if (gpuSkinning) - { - // We can't use a lot of bones because it costs uniform memory. Recommended - // number of bones per model is 32. - // Add 1 to NumBones because of the special 'root' bone. - if (mdef->GetNumBones() + 1 > 64) - LOGERROR("Model '%s' has too many bones %zu/64", mdef->GetName().string8().c_str(), mdef->GetNumBones() + 1); - ENSURE(mdef->GetNumBones() + 1 <= 64); - - m_BlendJoints.format = Renderer::Backend::Format::R8G8B8A8_UINT; - m_Array.AddAttribute(&m_BlendJoints); - - m_BlendWeights.format = Renderer::Backend::Format::R8G8B8A8_UNORM; - m_Array.AddAttribute(&m_BlendWeights); - } - if (calculateTangents) { // Generate tangents for the geometry:- @@ -99,12 +81,8 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT m_Tangent.format = Renderer::Backend::Format::R32G32B32A32_SFLOAT; m_Array.AddAttribute(&m_Tangent); - // floats per vertex; position + normal + tangent + UV*sets [+ GPUskinning] + // floats per vertex; position + normal + tangent + UV*sets int numVertexAttrs = 3 + 3 + 4 + 2 * mdef->GetNumUVsPerVertex(); - if (gpuSkinning) - { - numVertexAttrs += 8; - } // the tangent generation can increase the number of vertices temporarily // so reserve a bit more memory to avoid reallocations in GenTangents (in most cases) @@ -112,7 +90,7 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT newVertices.reserve(numVertexAttrs * numVertices * 2); // Generate the tangents - ModelRenderer::GenTangents(mdef, newVertices, gpuSkinning); + ModelRenderer::GenTangents(mdef, newVertices, false); // how many vertices do we have after generating tangents? int newNumVert = newVertices.size() / numVertexAttrs; @@ -133,14 +111,6 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT VertexArrayIterator Normal = m_Normal.GetIterator(); VertexArrayIterator Tangent = m_Tangent.GetIterator(); - VertexArrayIterator BlendJoints; - VertexArrayIterator BlendWeights; - if (gpuSkinning) - { - BlendJoints = m_BlendJoints.GetIterator(); - BlendWeights = m_BlendWeights.GetIterator(); - } - // copy everything into the vertex array for (int i = 0; i < numVertices2; i++) { @@ -156,16 +126,6 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT vertexDataOut[q + 3]); q += 4; - if (gpuSkinning) - { - for (size_t j = 0; j < 4; ++j) - { - BlendJoints[i][j] = (u8)vertexDataOut[q + 0 + 2 * j]; - BlendWeights[i][j] = (u8)vertexDataOut[q + 1 + 2 * j]; - } - q += 8; - } - for (size_t j = 0; j < mdef->GetNumUVsPerVertex(); j++) { VertexArrayIterator UVit = m_UVs[j].GetIterator(); @@ -214,21 +174,6 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT ModelRenderer::BuildUV(mdef, UVit, i); } - if (gpuSkinning) - { - VertexArrayIterator BlendJoints = m_BlendJoints.GetIterator(); - VertexArrayIterator BlendWeights = m_BlendWeights.GetIterator(); - for (size_t i = 0; i < numVertices; ++i) - { - const SModelVertex& vtx = mdef->GetVertices()[i]; - for (size_t j = 0; j < 4; ++j) - { - BlendJoints[i][j] = vtx.m_Blend.m_Bone[j]; - BlendWeights[i][j] = (u8)(255.f * vtx.m_Blend.m_Weight[j]); - } - } - } - m_Array.Upload(); m_Array.FreeBackingStore(); @@ -261,23 +206,10 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}); } - // GPU skinning requires extra attributes to compute positions/normals. - if (gpuSkinning) - { - attributes.push_back({ - Renderer::Backend::VertexAttributeStream::UV2, - m_BlendJoints.format, m_BlendJoints.offset, stride, - Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}); - attributes.push_back({ - Renderer::Backend::VertexAttributeStream::UV3, - m_BlendWeights.format, m_BlendWeights.offset, stride, - Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}); - } - if (calculateTangents) { attributes.push_back({ - Renderer::Backend::VertexAttributeStream::UV4, + Renderer::Backend::VertexAttributeStream::UV2, m_Tangent.format, m_Tangent.offset, stride, Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}); } @@ -287,23 +219,17 @@ IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning, bool calculateT struct InstancingModelRendererInternals { - bool gpuSkinning; - bool calculateTangents; /// Previously prepared modeldef IModelDef* imodeldef; - - /// Index base for imodeldef - u8* imodeldefIndexBase; }; // Construction and Destruction -InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) +InstancingModelRenderer::InstancingModelRenderer(bool calculateTangents) { m = new InstancingModelRendererInternals; - m->gpuSkinning = gpuSkinning; m->calculateTangents = calculateTangents; m->imodeldef = 0; } @@ -320,14 +246,11 @@ CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* m CModelDefPtr mdef = model->GetModelDef(); IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); - if (m->gpuSkinning) - ENSURE(model->IsSkinned()); - else - ENSURE(!model->IsSkinned()); + ENSURE(!model->IsSkinned()); if (!imodeldef) { - imodeldef = new IModelDef(mdef, m->gpuSkinning, m->calculateTangents); + imodeldef = new IModelDef(mdef, m->calculateTangents); mdef->SetRenderData(m, imodeldef); } @@ -371,21 +294,10 @@ void InstancingModelRenderer::PrepareModelDef( // Render one model void InstancingModelRenderer::RenderModel( Renderer::Backend::IDeviceCommandContext* deviceCommandContext, - Renderer::Backend::IShaderProgram* shader, CModel* model, CModelRData* UNUSED(data)) + Renderer::Backend::IShaderProgram* UNUSED(shader), CModel* model, CModelRData* UNUSED(data)) { const CModelDefPtr& mdldef = model->GetModelDef(); - if (m->gpuSkinning) - { - // Bind matrices for current animation state. - // Add 1 to NumBones because of the special 'root' bone. - deviceCommandContext->SetUniform( - shader->GetBindingSlot(str_skinBlendMatrices), - PS::span( - model->GetAnimatedBoneMatrices()[0]._data, - model->GetAnimatedBoneMatrices()[0].AsFloatArray().size() * (mdldef->GetNumBones() + 1))); - } - // Render the lot. const size_t numberOfFaces = mdldef->GetNumFaces(); diff --git a/source/renderer/InstancingModelRenderer.h b/source/renderer/InstancingModelRenderer.h index 41e99fffb4..7b44a00f2e 100644 --- a/source/renderer/InstancingModelRenderer.h +++ b/source/renderer/InstancingModelRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,7 +35,7 @@ struct InstancingModelRendererInternals; class InstancingModelRenderer : public ModelVertexRenderer { public: - InstancingModelRenderer(bool gpuSkinning, bool calculateTangents); + InstancingModelRenderer(bool calculateTangents); ~InstancingModelRenderer(); // Implementations diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index b793aead57..03906065bd 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify diff --git a/source/renderer/ModelVertexRenderer.h b/source/renderer/ModelVertexRenderer.h index cb7e14183b..dcbfb62501 100644 --- a/source/renderer/ModelVertexRenderer.h +++ b/source/renderer/ModelVertexRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index 10d93d01c0..e0c482fe36 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -152,6 +152,7 @@ protected: friend class CPatchRData; friend class CPUSkinnedModelVertexRenderer; friend class CRenderingOptions; + friend class GPUSkinnedModelModelRenderer; friend class InstancingModelRenderer; bool ShouldRender() const; diff --git a/source/renderer/RenderingOptions.cpp b/source/renderer/RenderingOptions.cpp index aa9f521b33..701b31a2ed 100644 --- a/source/renderer/RenderingOptions.cpp +++ b/source/renderer/RenderingOptions.cpp @@ -254,14 +254,14 @@ void CRenderingOptions::ReadConfigAndSetupHooks() m_ConfigHooks->Setup("gpuskinning", [this]() { bool enabled; CFG_GET_VAL("gpuskinning", enabled); + const Renderer::Backend::IDevice::Capabilities& capabilities{ + g_VideoMode.GetBackendDevice()->GetCapabilities()}; if (enabled) { - if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB) - LOGWARNING("GPUSkinning has been disabled, because it is not supported with ARB shaders."); - else if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN) - LOGWARNING("GPUSkinning has been disabled, because it is not supported for Vulkan backend yet."); - else + if (capabilities.computeShaders && capabilities.storage) m_GPUSkinning = true; + else + LOGMESSAGE("GPU skinning isn't supported on the current hardware."); } }); diff --git a/source/renderer/SceneRenderer.cpp b/source/renderer/SceneRenderer.cpp index 932f0f3d3b..bd34c599e3 100644 --- a/source/renderer/SceneRenderer.cpp +++ b/source/renderer/SceneRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 Wildfire Games. +/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -46,6 +46,7 @@ #include "renderer/backend/IDevice.h" #include "renderer/CPUSkinnedModelRenderer.h" #include "renderer/DebugRenderer.h" +#include "renderer/GPUSkinnedModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" @@ -151,10 +152,7 @@ public: { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) - { contextSkinned.Add(str_USE_INSTANCING, str_1); - contextSkinned.Add(str_USE_GPU_SKINNING, str_1); - } Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags); if (Model.NormalUnskinned != Model.NormalSkinned) @@ -174,10 +172,7 @@ public: { CShaderDefines contextSkinned = context; if (g_RenderingOptions.GetGPUSkinning()) - { contextSkinned.Add(str_USE_INSTANCING, str_1); - contextSkinned.Add(str_USE_GPU_SKINNING, str_1); - } Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags); if (Model.TranspUnskinned != Model.TranspSkinned) @@ -246,11 +241,11 @@ void CSceneRenderer::ReloadShaders(Renderer::Backend::IDevice* device) ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new CPUSkinnedModelVertexRenderer()); - m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, device->GetBackend() != Renderer::Backend::Backend::GL_ARB)); + m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(device->GetBackend() != Renderer::Backend::Backend::GL_ARB)); - if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too + if (g_RenderingOptions.GetGPUSkinning()) { - m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, device->GetBackend() != Renderer::Backend::Backend::GL_ARB)); + m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new GPUSkinnedModelModelRenderer()); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); } diff --git a/source/renderer/VertexArray.cpp b/source/renderer/VertexArray.cpp index 0f52d3b720..273524fc27 100644 --- a/source/renderer/VertexArray.cpp +++ b/source/renderer/VertexArray.cpp @@ -18,6 +18,7 @@ #include "precompiled.h" #include "lib/alignment.h" +#include "lib/bits.h" #include "lib/sysdep/rtl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" @@ -43,12 +44,18 @@ uint32_t GetAttributeSize(const Renderer::Backend::Format format) return sizeof(u8); case Renderer::Backend::Format::R16_UNORM: FALLTHROUGH; case Renderer::Backend::Format::R16_UINT: FALLTHROUGH; - case Renderer::Backend::Format::R16_SINT: + case Renderer::Backend::Format::R16_SINT: FALLTHROUGH; + case Renderer::Backend::Format::R16_SFLOAT: return sizeof(u16); case Renderer::Backend::Format::R16G16_UNORM: FALLTHROUGH; case Renderer::Backend::Format::R16G16_UINT: FALLTHROUGH; - case Renderer::Backend::Format::R16G16_SINT: + case Renderer::Backend::Format::R16G16_SINT: FALLTHROUGH; + case Renderer::Backend::Format::R16G16_SFLOAT: return sizeof(u16) * 2; + case Renderer::Backend::Format::R16G16B16_SFLOAT: + return sizeof(u16) * 3; + case Renderer::Backend::Format::R16G16B16A16_SFLOAT: + return sizeof(u16) * 4; case Renderer::Backend::Format::R32_SFLOAT: return sizeof(float); case Renderer::Backend::Format::R32G32_SFLOAT: @@ -69,10 +76,6 @@ VertexArray::VertexArray( const Renderer::Backend::IBuffer::Type type, const uint32_t usage) : m_Type(type), m_Usage(usage) { - m_NumberOfVertices = 0; - - m_BackingStore = 0; - m_Stride = 0; } VertexArray::~VertexArray() @@ -99,6 +102,16 @@ void VertexArray::SetNumberOfVertices(const size_t numberOfVertices) m_NumberOfVertices = numberOfVertices; } +void VertexArray::SetMinimumAttributeAlignment(const uint32_t minimumAttributeAlignment) +{ + ENSURE(minimumAttributeAlignment >= 4 || is_pow2(minimumAttributeAlignment)); + if (minimumAttributeAlignment == m_MinimumAttributeAlignment) + return; + + Free(); + m_MinimumAttributeAlignment = minimumAttributeAlignment; +} + // Add vertex attributes like Position, Normal, UV void VertexArray::AddAttribute(Attribute* attr) { @@ -248,6 +261,8 @@ void VertexArray::Layout() if (m_Type == Renderer::Backend::IBuffer::Type::VERTEX) m_Stride = Align<4>(m_Stride); + if (m_MinimumAttributeAlignment > 0) + m_Stride = (m_Stride + m_MinimumAttributeAlignment - 1) & ~(m_MinimumAttributeAlignment - 1); } if (m_Type == Renderer::Backend::IBuffer::Type::VERTEX) diff --git a/source/renderer/VertexArray.h b/source/renderer/VertexArray.h index 47956aa04d..e023c0a24e 100644 --- a/source/renderer/VertexArray.h +++ b/source/renderer/VertexArray.h @@ -168,6 +168,10 @@ public: // Add vertex attributes void AddAttribute(Attribute* attr); + // Sets the minimum alignment for each attribute to have offsets multiples + // of `minimumAttributeAlignment`. + void SetMinimumAttributeAlignment(const uint32_t minimumAttributeAlignment); + size_t GetNumberOfVertices() const { return m_NumberOfVertices; } uint32_t GetStride() const { return m_Stride; } @@ -203,13 +207,14 @@ private: } Renderer::Backend::IBuffer::Type m_Type; - uint32_t m_Usage = 0; - size_t m_NumberOfVertices; + uint32_t m_Usage{0}; + uint32_t m_MinimumAttributeAlignment{0}; + size_t m_NumberOfVertices{0}; std::vector m_Attributes; CVertexBufferManager::Handle m_VB; - uint32_t m_Stride; - char* m_BackingStore; // 16-byte aligned, to allow fast SSE access + uint32_t m_Stride{0}; + char* m_BackingStore{nullptr}; // 16-byte aligned, to allow fast SSE access }; /** diff --git a/source/renderer/VertexBufferManager.cpp b/source/renderer/VertexBufferManager.cpp index 69b4e6aace..8015f8ccee 100644 --- a/source/renderer/VertexBufferManager.cpp +++ b/source/renderer/VertexBufferManager.cpp @@ -149,8 +149,10 @@ CVertexBufferManager::Handle CVertexBufferManager::AllocateChunk( char bufferName[64] = {0}; snprintf( - bufferName, std::size(bufferName), "%s (%s, %zu%s)", - GetBufferTypeName(type), GetGroupName(group), vertexSize, ((usage & Renderer::Backend::IBuffer::Usage::DYNAMIC) ? ", dynamic" : "")); + bufferName, std::size(bufferName), "%s (%s, %zu%s%s)", + GetBufferTypeName(type), GetGroupName(group), vertexSize, + ((usage & Renderer::Backend::IBuffer::Usage::DYNAMIC) ? ", dynamic" : ""), + ((usage & Renderer::Backend::IBuffer::Usage::STORAGE) ? ", storage" : "")); // got this far; need to allocate a new buffer buffers.emplace_back(