From bc361683180bae67b3d469b023dbd87fde3bb681 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Jun 2026 17:59:41 +0200 Subject: [PATCH] Optimize Animated Model rendering with hardware instancing All models are using the same global buffer for skinned bones which allows to share shader binding for instancing. Refactor draw call for batching skinned mesh draws. Remove `SkinnedMeshDrawData` and merge it into `AnimatedModel` internals. --- .../Editor/MaterialTemplates/Surface.shader | 57 +-- Flax.flaxproj | 2 +- .../Utilities/ViewportIconsRenderer.cpp | 8 - Source/Engine/Animations/Animations.cpp | 3 - Source/Engine/Foliage/Foliage.cpp | 17 +- .../Materials/DeferredMaterialShader.cpp | 65 ++-- .../Materials/ForwardMaterialShader.cpp | 9 +- .../Graphics/Materials/MaterialShader.h | 2 +- Source/Engine/Graphics/Models/Config.h | 2 +- Source/Engine/Graphics/Models/Mesh.cpp | 8 +- Source/Engine/Graphics/Models/MeshBase.h | 17 +- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 16 +- .../Graphics/Models/SkinnedMeshDrawData.cpp | 59 ---- .../Graphics/Models/SkinnedMeshDrawData.h | 80 ----- Source/Engine/Level/Actors/AnimatedModel.cpp | 329 +++++++++++++----- Source/Engine/Level/Actors/AnimatedModel.h | 23 +- Source/Engine/Level/Actors/Camera.cpp | 8 - Source/Engine/Renderer/DrawCall.h | 16 +- .../Renderer/Editor/LightmapUVsDensity.cpp | 2 +- Source/Engine/Renderer/RenderList.cpp | 31 +- Source/Engine/Renderer/RenderList.h | 10 +- Source/Engine/Renderer/ShadowsPass.cpp | 5 + Source/Engine/Terrain/TerrainChunk.cpp | 10 +- .../MaterialGenerator/MaterialGenerator.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 2 +- Source/Shaders/MaterialCommon.hlsl | 14 +- 26 files changed, 430 insertions(+), 367 deletions(-) delete mode 100644 Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp delete mode 100644 Source/Engine/Graphics/Models/SkinnedMeshDrawData.h diff --git a/Content/Editor/MaterialTemplates/Surface.shader b/Content/Editor/MaterialTemplates/Surface.shader index 847c7a4de..ee4a5cada 100644 --- a/Content/Editor/MaterialTemplates/Surface.shader +++ b/Content/Editor/MaterialTemplates/Surface.shader @@ -20,10 +20,6 @@ Buffer ObjectsBuffer : register(t0); #if USE_SKINNING // The skeletal bones matrix buffer (stored as 4x3, 3 float4 behind each other) Buffer BoneMatrices : register(t1); -#if PER_BONE_MOTION_BLUR -// The skeletal bones matrix buffer from the previous frame -Buffer PrevBoneMatrices : register(t2); -#endif #endif // Geometry data passed though the graphics rendering stages up to the pixel shader @@ -418,32 +414,8 @@ float4 VS_Depth(ModelInput_PosOnly input) : SV_Position #if USE_SKINNING -#if PER_BONE_MOTION_BLUR - -float3x4 GetPrevBoneMatrix(int index) -{ - float4 a = PrevBoneMatrices[index * 3]; - float4 b = PrevBoneMatrices[index * 3 + 1]; - float4 c = PrevBoneMatrices[index * 3 + 2]; - return float3x4(a, b, c); -} - -float3 SkinPrevPosition(ModelInput_Skinned input) -{ - float4 position = float4(input.Position.xyz, 1); - float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w; - float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros - float3x4 boneMatrix = mainWeight * GetPrevBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetPrevBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetPrevBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetPrevBoneMatrix(input.BlendIndices.w); - return mul(boneMatrix, position); -} - -#endif - // Calculates the transposed transform matrix for the given bone index -float3x4 GetBoneMatrix(int index) +float3x4 GetBoneMatrix(uint index) { float4 a = BoneMatrices[index * 3]; float4 b = BoneMatrices[index * 3 + 1]; @@ -452,14 +424,14 @@ float3x4 GetBoneMatrix(int index) } // Calculates the transposed transform matrix for the given vertex (uses blending) -float3x4 GetBoneMatrix(ModelInput_Skinned input) +float3x4 GetBoneMatrix(ModelInput_Skinned input, uint skinningOffset) { float weightsSum = input.BlendWeights.x + input.BlendWeights.y + input.BlendWeights.z + input.BlendWeights.w; float mainWeight = input.BlendWeights.x + (1.0f - weightsSum); // Re-normalize to account for 16-bit weights encoding erros - float3x4 boneMatrix = mainWeight * GetBoneMatrix(input.BlendIndices.x); - boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y); - boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z); - boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w); + float3x4 boneMatrix = mainWeight * GetBoneMatrix(input.BlendIndices.x + skinningOffset); + boneMatrix += input.BlendWeights.y * GetBoneMatrix(input.BlendIndices.y + skinningOffset); + boneMatrix += input.BlendWeights.z * GetBoneMatrix(input.BlendIndices.z + skinningOffset); + boneMatrix += input.BlendWeights.w * GetBoneMatrix(input.BlendIndices.w + skinningOffset); return boneMatrix; } @@ -487,31 +459,40 @@ float3x3 SkinTangents(ModelInput_Skinned input, float3x4 boneMatrix) // Vertex Shader function for GBuffers/Depth Pass (skinned mesh rendering) META_VS(true, FEATURE_LEVEL_ES2) -META_PERMUTATION_1(USE_SKINNING=1) -META_PERMUTATION_2(USE_SKINNING=1, PER_BONE_MOTION_BLUR=1) +META_PERMUTATION_2(USE_SKINNING=1, USE_INSTANCING=0) +META_PERMUTATION_3(USE_SKINNING=1, USE_INSTANCING=0, PER_BONE_MOTION_BLUR=1) +META_PERMUTATION_2(USE_SKINNING=1, USE_INSTANCING=1) +META_PERMUTATION_3(USE_SKINNING=1, USE_INSTANCING=1, PER_BONE_MOTION_BLUR=1) META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TEXCOORD, 0, R16G16_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(NORMAL, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(TANGENT, 0, R10G10B10A2_UNORM, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(BLENDINDICES, 0, R8G8B8A8_UINT, 0, ALIGN, PER_VERTEX, 0, true) META_VS_IN_ELEMENT(BLENDWEIGHTS, 0, R16G16B16A16_FLOAT,0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(ATTRIBUTE, 0, R32_UINT, 3, 0, PER_INSTANCE, 1, USE_INSTANCING) VertexOutput VS_Skinned(ModelInput_Skinned input) { VertexOutput output; // Load object data +#if USE_INSTANCING + output.Geometry.ObjectIndex = input.ObjectIndex; +#else output.Geometry.ObjectIndex = DrawObjectIndex; +#endif ObjectData object = LoadObject(ObjectsBuffer, output.Geometry.ObjectIndex); // Perform skinning - float3x4 boneMatrix = GetBoneMatrix(input); + float3x4 boneMatrix = GetBoneMatrix(input, object.SkinningOffset); float3 position = SkinPosition(input, boneMatrix); float3x3 tangentToLocal = SkinTangents(input, boneMatrix); // Compute world space vertex position output.Geometry.WorldPosition = mul(float4(position, 1), object.WorldMatrix).xyz; #if PER_BONE_MOTION_BLUR - float3 prevPosition = SkinPrevPosition(input); + int prevBonesOffset = (int)object.SkinningOffset + object.PrevBonesOffset; // Offset can be negative + float3x4 prevBoneMatrix = GetBoneMatrix(input, (uint)prevBonesOffset); + float3 prevPosition = SkinPosition(input, prevBoneMatrix); output.Geometry.PrevWorldPosition = mul(float4(prevPosition, 1), object.PrevWorldMatrix).xyz; #else output.Geometry.PrevWorldPosition = mul(float4(position, 1), object.PrevWorldMatrix).xyz; diff --git a/Flax.flaxproj b/Flax.flaxproj index 4abc7bca6..d128ab6e1 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 13, "Revision": 0, - "Build": 7005 + "Build": 7006 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index 26a705970..01e4ae8fc 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -84,16 +84,8 @@ void ViewportIconsRenderer::DrawIcons(RenderContext& renderContext, Actor* actor return; Mesh::DrawInfo draw; - draw.Lightmap = nullptr; - draw.LightmapUVs = nullptr; draw.Flags = StaticFlags::Transform; draw.DrawModes = DrawPass::Forward; - draw.PerInstanceRandom = 0; - draw.StencilValue = 0; - draw.LODBias = 0; - draw.ForcedLOD = -1; - draw.SortOrder = 0; - draw.VertexColors = nullptr; if (const auto scene = SceneObject::Cast(actor)) { diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index f20f41a58..ba1c8e338 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -95,9 +95,6 @@ void AnimationsSystem::Job(int32 index) ZoneName(*graphName, graphName.Length()); #endif - // Prepare skinning data - animatedModel->SetupSkinningData(); - // Animation delta time can be based on a time since last update or the current delta float dt = animatedModel->UseTimeScale ? DeltaTime : UnscaledDeltaTime; float t = animatedModel->UseTimeScale ? Time : UnscaledTime; diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index 9b97e0e95..b862651e6 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -163,7 +163,7 @@ void Foliage::DrawInstance(DrawContext& context, FoliageInstance& instance, int3 Matrix::Transformation(transform.Scale, transform.Orientation, translation, instance.CachedDrawWorld); instance.CachedDrawWorldValid = true; } - instanceData.Store(instance.CachedDrawWorld, instance.CachedDrawWorld, instance.LightmapUVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor); + instanceData.Store(instance.CachedDrawWorld, instance.CachedDrawWorld, instance.LightmapUVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, (byte)(lodDitherFactor * 255)); } } @@ -461,10 +461,6 @@ void Foliage::DrawFoliageJob(int32 i) Mesh::DrawInfo draw; draw.Flags = GetStaticFlags(); draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass); - draw.LODBias = 0; - draw.ForcedLOD = -1; - draw.VertexColors = nullptr; - draw.Deformation = nullptr; DrawType(renderContext, type, draw); #endif } @@ -564,7 +560,7 @@ void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Me firstInstance.Load(batch.DrawCall); #if USE_EDITOR if (renderContext.View.Mode == ViewMode::LightmapUVsDensity) - batch.DrawCall.Surface.LODDitherFactor = type.ScaleInLightmap; // See LightmapUVsDensityMaterialShader + batch.DrawCall.Surface.SkinningBonesOffset = *(uint32*)&type.ScaleInLightmap; // See LightmapUVsDensityMaterialShader #endif if (EnumHasAnyFlags(drawModes, DrawPass::Forward)) @@ -1298,16 +1294,11 @@ void Foliage::Draw(RenderContext& renderContext) drawState.PrevWorld = world; Mesh::DrawInfo draw; draw.Flags = GetStaticFlags(); - draw.LODBias = 0; - draw.ForcedLOD = -1; - draw.SortOrder = 0; - draw.VertexColors = nullptr; draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(instance.LightmapTextureIndex) : nullptr; draw.LightmapUVs = &instance.LightmapUVsArea; draw.Buffer = &type.Entries; draw.World = &world; draw.DrawState = &drawState; - draw.Deformation = nullptr; draw.Bounds = instance.Bounds; draw.PerInstanceRandom = instance.Random; draw.DrawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode); @@ -1321,10 +1312,6 @@ void Foliage::Draw(RenderContext& renderContext) Mesh::DrawInfo draw; draw.Flags = GetStaticFlags(); draw.DrawModes = (DrawPass)(DrawPass::Default & view.Pass); - draw.LODBias = 0; - draw.ForcedLOD = -1; - draw.VertexColors = nullptr; - draw.Deformation = nullptr; #endif #if FOLIAGE_USE_SINGLE_QUAD_TREE if (Root) diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index a409c4a89..125974d50 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -10,7 +10,6 @@ #include "Engine/Level/Scene/Lightmap.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" -#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/GPULimits.h" @@ -56,19 +55,12 @@ void DeferredMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); context->BindSR(0, params.ObjectBuffer); - // Check if using mesh skinning - const bool useSkinning = drawCall.Surface.Skinning != nullptr; - bool perBoneMotionBlur = false; + // Bind skinning buffer + const bool useSkinning = drawCall.Surface.Skinning != DrawCall::SkinningMode::None; + const bool usePerBoneMotionBlur = drawCall.Surface.Skinning == DrawCall::SkinningMode::WithPrevBones; if (useSkinning) { - // Bind skinning buffer - ASSERT(drawCall.Surface.Skinning->IsReady()); - context->BindSR(1, drawCall.Surface.Skinning->BoneMatrices->View()); - if (drawCall.Surface.Skinning->PrevBoneMatrices && drawCall.Surface.Skinning->PrevBoneMatrices->IsAllocated()) - { - context->BindSR(2, drawCall.Surface.Skinning->PrevBoneMatrices->View()); - perBoneMotionBlur = true; - } + context->BindSR(1, drawCall.Surface.SkinningBones->View()); } // Bind constants @@ -90,9 +82,8 @@ void DeferredMaterialShader::Bind(BindParameters& params) // Invert culling when scale is negative cullMode = cullMode == CullMode::Normal ? CullMode::Inverted : CullMode::Normal; } - ASSERT_LOW_LAYER(!(useSkinning && params.Instanced)); // No support for instancing skinned meshes const auto cache = params.Instanced ? &_cacheInstanced : &_cache; - PipelineStateCache* psCache = cache->GetPS(view.Pass, useLightmap, useSkinning, perBoneMotionBlur); + PipelineStateCache* psCache = cache->GetPS(view.Pass, useLightmap, useSkinning, usePerBoneMotionBlur); ASSERT(psCache); GPUPipelineState* state = psCache->GetPS(cullMode, wireframe); @@ -139,28 +130,35 @@ bool DeferredMaterialShader::Load() psDesc.StencilReadMask = 0; psDesc.StencilPassOp = StencilOperation::Replace; + auto vs = _shader->GetVS("VS"); + auto vsInstanced = _shader->GetVS("VS", 1); + auto vsSkinned = _shader->GetVS("VS_Skinned"); + auto vsSkinnedInstanced = _shader->GetVS("VS_Skinned", 2); + // GBuffer Pass - psDesc.VS = _shader->GetVS("VS"); + psDesc.VS = vs; failed |= psDesc.VS == nullptr; psDesc.PS = _shader->GetPS("PS_GBuffer"); _cache.Default.Init(psDesc); - psDesc.VS = _shader->GetVS("VS", 1); + psDesc.VS = vsInstanced; failed |= psDesc.VS == nullptr; _cacheInstanced.Default.Init(psDesc); - // GBuffer Pass with lightmap (pixel shader permutation for USE_LIGHTMAP=1) - psDesc.VS = _shader->GetVS("VS"); + // GBuffer Pass with lightmap (USE_LIGHTMAP=1) + psDesc.VS = vs; failed |= psDesc.VS == nullptr; psDesc.PS = _shader->GetPS("PS_GBuffer", 1); _cache.DefaultLightmap.Init(psDesc); - psDesc.VS = _shader->GetVS("VS", 1); + psDesc.VS = vsInstanced; failed |= psDesc.VS == nullptr; _cacheInstanced.DefaultLightmap.Init(psDesc); - // GBuffer Pass with skinning - psDesc.VS = _shader->GetVS("VS_Skinned"); + // GBuffer Pass with skinning (USE_SKINNING=1) + psDesc.VS = vsSkinned; psDesc.PS = _shader->GetPS("PS_GBuffer"); _cache.DefaultSkinned.Init(psDesc); + psDesc.VS = vsSkinnedInstanced; + _cacheInstanced.DefaultSkinned.Init(psDesc); psDesc.StencilEnable = false; psDesc.StencilPassOp = StencilOperation::Keep; @@ -169,13 +167,15 @@ bool DeferredMaterialShader::Load() if (_shader->HasShader("PS_QuadOverdraw")) { // Quad Overdraw - psDesc.VS = _shader->GetVS("VS"); + psDesc.VS = vs; psDesc.PS = _shader->GetPS("PS_QuadOverdraw"); _cache.QuadOverdraw.Init(psDesc); - psDesc.VS = _shader->GetVS("VS", 1); + psDesc.VS = vsInstanced; _cacheInstanced.Depth.Init(psDesc); - psDesc.VS = _shader->GetVS("VS_Skinned"); + psDesc.VS = vsSkinned; _cache.QuadOverdrawSkinned.Init(psDesc); + psDesc.VS = vsSkinnedInstanced; + _cacheInstanced.QuadOverdrawSkinned.Init(psDesc); } #endif @@ -183,17 +183,22 @@ bool DeferredMaterialShader::Load() psDesc.DepthWriteEnable = false; psDesc.DepthEnable = true; psDesc.DepthFunc = ComparisonFunc::DefaultEqual; - psDesc.VS = _shader->GetVS("VS"); + psDesc.VS = vs; psDesc.PS = _shader->GetPS("PS_MotionVectors"); _cache.MotionVectors.Init(psDesc); + _cacheInstanced.MotionVectors.Init(psDesc); // Motion Vectors pass with skinning - psDesc.VS = _shader->GetVS("VS_Skinned"); + psDesc.VS = vsSkinned; _cache.MotionVectorsSkinned.Init(psDesc); + psDesc.VS = vsSkinnedInstanced; + _cacheInstanced.MotionVectorsSkinned.Init(psDesc); // Motion Vectors pass with skinning (with per-bone motion blur) psDesc.VS = _shader->GetVS("VS_Skinned", 1); _cache.MotionVectorsSkinnedPerBone.Init(psDesc); + psDesc.VS = _shader->GetVS("VS_Skinned", 3); + _cacheInstanced.MotionVectorsSkinnedPerBone.Init(psDesc); // Depth Pass psDesc.CullMode = CullMode::TwoSided; @@ -209,8 +214,8 @@ bool DeferredMaterialShader::Load() { // Materials with masking need full vertex buffer to get texcoord used to sample textures for per pixel masking. // Materials with world pos offset need full VB to apply offset using texcoord etc. - psDesc.VS = _shader->GetVS("VS"); - instancedDepthPassVS = _shader->GetVS("VS", 1); + psDesc.VS = vs; + instancedDepthPassVS = vsInstanced; psDesc.PS = _shader->GetPS("PS_Depth"); } else @@ -224,8 +229,10 @@ bool DeferredMaterialShader::Load() _cacheInstanced.Depth.Init(psDesc); // Depth Pass with skinning - psDesc.VS = _shader->GetVS("VS_Skinned"); + psDesc.VS = vsSkinned; _cache.DepthSkinned.Init(psDesc); + psDesc.VS = vsSkinnedInstanced; + _cacheInstanced.DepthSkinned.Init(psDesc); return failed; } diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index f22b7d065..943134b6f 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -8,7 +8,6 @@ #include "Engine/Graphics/GPULimits.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderTask.h" -#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" #include "Engine/Graphics/Shaders/GPUConstantBuffer.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Renderer/DrawCall.h" @@ -58,13 +57,11 @@ void ForwardMaterialShader::Bind(BindParameters& params) MaterialParams::Bind(params.ParamsLink, bindMeta); context->BindSR(0, params.ObjectBuffer); - // Check if using mesh skinning - const bool useSkinning = drawCall.Surface.Skinning != nullptr; + // Bind skinning buffer + const bool useSkinning = drawCall.Surface.Skinning != DrawCall::SkinningMode::None; if (useSkinning) { - // Bind skinning buffer - ASSERT(drawCall.Surface.Skinning->IsReady()); - context->BindSR(1, drawCall.Surface.Skinning->BoneMatrices->View()); + context->BindSR(1, drawCall.Surface.SkinningBones->View()); } // Bind constants diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 849f272f2..5f78be257 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 183 +#define MATERIAL_GRAPH_VERSION 184 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Models/Config.h b/Source/Engine/Graphics/Models/Config.h index 9f6905c97..8758d77a8 100644 --- a/Source/Engine/Graphics/Models/Config.h +++ b/Source/Engine/Graphics/Models/Config.h @@ -32,6 +32,6 @@ #define MAX_BONES_PER_VERTEX MODEL_MAX_BONES_PER_VERTEX // Defines the maximum allowed amount of skeleton bones to be used with skinned model -#define MODEL_MAX_BONES_PER_MODEL 0xffff +#define MODEL_MAX_BONES_PER_MODEL MAX_int16 // [Deprecated in v1.10] Use MODEL_MAX_BONES_PER_MODEL #define MAX_BONES_PER_MODEL MODEL_MAX_BONES_PER_MODEL diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 88fdf65b9..12305e189 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -306,7 +306,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Half4::Zero; - drawCall.Surface.LODDitherFactor = lodDitherFactor; + drawCall.Surface.LODDitherFactor = (byte)(lodDitherFactor * 255); drawCall.PerInstanceRandom = info.PerInstanceRandom; drawCall.StencilValue = info.StencilValue; #if USE_EDITOR @@ -314,7 +314,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); if (viewMode == ViewMode::LightmapUVsDensity) - drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader + drawCall.Surface.SkinningBonesOffset = *(uint32*)&info.LightmapScale; // See LightmapUVsDensityMaterialShader #endif // Push draw call to the render list @@ -369,7 +369,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; drawCall.Surface.Lightmap = (info.Flags & StaticFlags::Lightmap) != StaticFlags::None ? info.Lightmap : nullptr; drawCall.Surface.LightmapUVsArea = info.LightmapUVs ? *info.LightmapUVs : Half4::Zero; - drawCall.Surface.LODDitherFactor = lodDitherFactor; + drawCall.Surface.LODDitherFactor = (byte)(lodDitherFactor * 255); drawCall.PerInstanceRandom = info.PerInstanceRandom; drawCall.StencilValue = info.StencilValue; #if USE_EDITOR @@ -377,7 +377,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in if (viewMode == ViewMode::LightmapUVsDensity || viewMode == ViewMode::LODPreview) GBufferPass::AddIndexBufferToModelLOD(_indexBuffer, &((Model*)_model)->LODs[_lodIndex]); if (viewMode == ViewMode::LightmapUVsDensity) - drawCall.Surface.LODDitherFactor = info.LightmapScale; // See LightmapUVsDensityMaterialShader + drawCall.Surface.SkinningBonesOffset = *(uint32*)&info.LightmapScale; // See LightmapUVsDensityMaterialShader #endif // Push draw call to the render lists diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 39f10893f..48d076206 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -362,7 +362,10 @@ public: /// /// The skinning. /// - SkinnedMeshDrawData* Skinning; + GPUBuffer* SkinningBones; + uint32 SkinningBonesOffset; // In Matrix3x4s + int16 PrevBonesOffset; // In Matrix3x4s, can be negative + bool WithPrevBones; }; struct @@ -425,9 +428,19 @@ public: int8 SortOrder; #if USE_EDITOR - float LightmapScale = -1.0f; + float LightmapScale; #endif + // Zero-init. + FORCE_INLINE DrawInfo() + { + Platform::MemoryClear(this, sizeof(DrawInfo)); + ForcedLOD = -1; +#if USE_EDITOR + LightmapScale = -1; +#endif + } + // Packs object layer into the stencil bits. FORCE_INLINE void SetStencilValue(int32 layer) { diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index da1f90569..44ed82eee 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -334,6 +334,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, return; // Setup draw call + ASSERT(info.SkinningBones); DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -348,8 +349,11 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; - drawCall.Surface.Skinning = info.Skinning; - drawCall.Surface.LODDitherFactor = lodDitherFactor; + drawCall.Surface.Skinning = info.WithPrevBones ? DrawCall::SkinningMode::WithPrevBones : DrawCall::SkinningMode::Active; + drawCall.Surface.SkinningBones = info.SkinningBones; + drawCall.Surface.SkinningBonesOffset = info.SkinningBonesOffset; + drawCall.Surface.PrevBonesOffset = info.PrevBonesOffset; + drawCall.Surface.LODDitherFactor = (byte)(lodDitherFactor * 255); drawCall.PerInstanceRandom = info.PerInstanceRandom; drawCall.StencilValue = info.StencilValue; @@ -376,6 +380,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI return; // Setup draw call + ASSERT(info.SkinningBones); DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -389,8 +394,11 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI drawCall.ObjectRadius = (float)info.Bounds.Radius; // TODO: should it be kept in sync with ObjectPosition? drawCall.Surface.GeometrySize = _box.GetSize(); drawCall.Surface.PrevWorld = info.DrawState->PrevWorld; - drawCall.Surface.Skinning = info.Skinning; - drawCall.Surface.LODDitherFactor = lodDitherFactor; + drawCall.Surface.Skinning = info.WithPrevBones ? DrawCall::SkinningMode::WithPrevBones : DrawCall::SkinningMode::Active; + drawCall.Surface.SkinningBones = info.SkinningBones; + drawCall.Surface.SkinningBonesOffset = info.SkinningBonesOffset; + drawCall.Surface.PrevBonesOffset = info.PrevBonesOffset; + drawCall.Surface.LODDitherFactor = (byte)(lodDitherFactor * 255); drawCall.PerInstanceRandom = info.PerInstanceRandom; drawCall.StencilValue = info.StencilValue; diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp deleted file mode 100644 index eb2ea0145..000000000 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Wojciech Figat. All rights reserved. - -#include "SkinnedMeshDrawData.h" -#include "Engine/Graphics/GPUDevice.h" -#include "Engine/Animations/Config.h" -#include "Engine/Core/Log.h" -#include "Engine/Core/Math/Matrix.h" - -SkinnedMeshDrawData::~SkinnedMeshDrawData() -{ - SAFE_DELETE_GPU_RESOURCE(BoneMatrices); - SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices); -} - -void SkinnedMeshDrawData::Setup(int32 bonesCount) -{ - if (BoneMatrices == nullptr) - { - BoneMatrices = GPUDevice::Instance->CreateBuffer(TEXT("BoneMatrices")); - } - - const int32 elementsCount = bonesCount * 3; // 3 * float4 per bone - if (BoneMatrices->Init(GPUBufferDescription::Typed(elementsCount, PixelFormat::R32G32B32A32_Float, false, GPUResourceUsage::Dynamic))) - { - LOG(Error, "Failed to initialize the skinned mesh bones buffer"); - return; - } - - BonesCount = bonesCount; - _hasValidData = false; - _isDirty = true; - Data.Resize(BoneMatrices->GetSize()); - SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices); -} - -void SkinnedMeshDrawData::OnDataChanged(bool dropHistory) -{ - // Setup previous frame bone matrices if needed - if (_hasValidData && !dropHistory) - { - ASSERT(BoneMatrices); - if (PrevBoneMatrices == nullptr) - { - PrevBoneMatrices = GPUDevice::Instance->CreateBuffer(TEXT("BoneMatrices")); - if (PrevBoneMatrices->Init(BoneMatrices->GetDescription())) - { - LOG(Fatal, "Failed to initialize the skinned mesh bones buffer"); - } - } - Swap(PrevBoneMatrices, BoneMatrices); - } - else - { - SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices); - } - - _isDirty = true; - _hasValidData = true; -} diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h deleted file mode 100644 index dc780a26d..000000000 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Wojciech Figat. All rights reserved. - -#pragma once - -#include "Engine/Core/Collections/Array.h" -#include "Engine/Graphics/GPUBuffer.h" - -/// -/// Data storage for the skinned meshes rendering -/// -class FLAXENGINE_API SkinnedMeshDrawData -{ -private: - bool _hasValidData = false; - bool _isDirty = false; - -public: - /// - /// The bones count. - /// - int32 BonesCount = 0; - - /// - /// The bone matrices buffer. Contains prepared skeletal bones transformations (stored as 4x3, 3 Vector4 behind each other). - /// - GPUBuffer* BoneMatrices = nullptr; - - /// - /// The bone matrices buffer used during the previous update. Used by per-bone motion blur. - /// - GPUBuffer* PrevBoneMatrices = nullptr; - - /// - /// The CPU data buffer with the bones transformations (ready to be flushed with the GPU). - /// - Array Data; - -public: - /// - /// Finalizes an instance of the class. - /// - ~SkinnedMeshDrawData(); - -public: - /// - /// Determines whether this instance is ready for rendering. - /// - FORCE_INLINE bool IsReady() const - { - return BoneMatrices != nullptr && BoneMatrices->IsAllocated(); - } - - /// - /// Determines whether this instance has been modified and needs to be flushed with GPU buffer. - /// - FORCE_INLINE bool IsDirty() const - { - return _isDirty; - } - - /// - /// Setups the data container for the specified bones amount. - /// - /// The bones count. - void Setup(int32 bonesCount); - - /// - /// After bones Data has been modified externally. Updates the bone matrices data for the GPU buffer. Ensure to call Flush before rendering. - /// - /// True if drop previous update bones used for motion blur, otherwise will keep them and do the update. - void OnDataChanged(bool dropHistory); - - /// - /// After bones Data has been sent to the GPU buffer. - /// - void OnFlush() - { - _isDirty = false; - } -}; diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ba3a2025d..db8ec5323 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -22,71 +22,165 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Profiler/Profiler.h" +#include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Serialization/Serialization.h" -// Implements efficient skinning data update within a shared GPUMemoryPass with manual resource transitions batched for all animated models. +// Implements efficient skinning data update within a shared GPUBuffer with memory sharing for all animated models. class AnimatedModelRenderListExtension : public RenderList::IExtension { public: - struct Item + // Allocation within the global buffer (offset and size are in bytes) + struct Allocation { - GPUBuffer* BoneMatrices; - void* Data; - int32 Size; + uint32 Size; + uint32 Offset; }; - RenderListBuffer Items; + GPUBuffer* GlobalBuffer = nullptr; + RenderListBuffer Updates; + CriticalSection Locker; + Array FreeList; + Array Data; + uint32 CurrentOffset = 0; + uint32 CurrentSize = 0; + uint32 ReallocateSize = 0; // Lower bound for the new buffer size to copy back from old buffer (in bytes) + volatile int64 UpdateSize = 0; + ReadWriteLock DataLocker; // Ensure to lock data writers when performing reallocation of the global buffer - void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override + // Allocates a new skinned bones data block from the global buffer and returns its offset and size (in bytes). + Allocation Allocate(uint32 size) { - Items.Clear(); + Allocation result; + PROFILE_MEM(Animations); + ScopeLock lock(Updates.Locker()); + + // Check free items list to reuse allocation + auto* freeItems = FreeList.Get(); + for (int32 i = 0; i < FreeList.Count(); i++) + { + if (freeItems[i].Size == size) + { + result = freeItems[i]; + FreeList.RemoveAt(i); + return result; + } + } + + // Check if need to create/resize the global buffer + if (CurrentOffset + size > CurrentSize) + { + DataLocker.WriteLock(); // Ensure none if writing to this buffer during resize + + // First allocation sets it (in case multiple reallocs before draw) + if (ReallocateSize == 0) + ReallocateSize = CurrentSize; + + // Grow buffer + CurrentSize = CurrentSize == 0 ? 16 * 1024 : CurrentSize * 2; + ASSERT(CurrentOffset + size <= CurrentSize); + Data.Resize(CurrentSize, true); + + DataLocker.WriteUnlock(); + } + + // Allocate new block + result = { size, CurrentOffset }; + CurrentOffset += size; + return result; } + // Frees allocated memory back to the global buffer. + void Free(Allocation alloc) + { + PROFILE_MEM(Animations); + ScopeLock lock(Updates.Locker()); + FreeList.Add(alloc); + + // TODO: track active allocations count and roll back to offset 0 without free list when all allocations are freed to reduce fragmentation (eg. on scene changing) + } + +private: + GPUBuffer* InitBuffer() const + { + GPUBuffer* buffer = GPUDevice::Instance->CreateBuffer(TEXT("BoneMatrices")); + if (buffer->Init(GPUBufferDescription::Typed((int32)(CurrentSize / sizeof(Float4)), PixelFormat::R32G32B32A32_Float, false, GPUResourceUsage::Dynamic))) + { + LOG(Error, "Failed to initialize the skinned mesh bones buffer"); + SAFE_DELETE_GPU_RESOURCE(buffer); + } + return buffer; + } + +public: + // [RenderList::IExtension] + void Dispose() override + { + // Free memory + Updates.Clear(); + FreeList.Clear(); + CurrentOffset = 0; + CurrentSize = 0; + ReallocateSize = 0; + SAFE_DELETE_GPU_RESOURCE(GlobalBuffer); + } + void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override + { + // Free pending updates to collect the during drawing + Updates.Clear(); + UpdateSize = 0; + + // Setup global buffer on GPU + if (!CurrentSize) + return; + if (!GlobalBuffer) + { + GlobalBuffer = InitBuffer(); + ReallocateSize = 0; + } + else if (ReallocateSize) + { + auto newGlobalBuffer = InitBuffer(); + context->CopyBuffer(newGlobalBuffer, GlobalBuffer, ReallocateSize); + GlobalBuffer->DeleteObject(1.0f); // Delay destruction + GlobalBuffer = newGlobalBuffer; + ReallocateSize = 0; + } + } void PostDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override { - const int32 count = Items.Count(); + const int32 count = Updates.Count(); if (count == 0) return; PROFILE_GPU_CPU_NAMED("Update Bones"); GPUMemoryPass pass(context); - Item* items = Items.Get(); + ScopeWriteLock lock(DataLocker); - // Special case for D3D11 backend that doesn't need transitions - if (context->GetDevice()->GetRendererType() <= RendererType::DirectX11) + auto* updates = Updates.Get(); + auto globalBuffer = GlobalBuffer; + auto globalData = Data.Get(); + if (context->GetDevice()->GetRendererType() <= RendererType::DirectX11 || // Dynamic buffer cannot be updated partially on D3D11 (hence D3D11_MAP_WRITE_DISCARD), so update the whole buffer + count >= 1000 || // When updates count is large, it is more efficient to update the whole buffer at once + UpdateSize >= (uint32)(0.7f * CurrentOffset)) // When modified size is large, it is more efficient to update the whole buffer at once { - for (int32 i = 0; i < count; i++) - { - Item& item = items[i]; - context->UpdateBuffer(item.BoneMatrices, item.Data, item.Size); - } + // Update whole buffer at once + context->UpdateBuffer(globalBuffer, globalData, CurrentOffset); } else { - // Batch resource barriers for buffer update - for (int32 i = 0; i < count; i++) - pass.Transition(items[i].BoneMatrices, GPUResourceAccess::CopyWrite); - - // Update all buffers within Memory Pass (no barriers between) + // Update all modified chunks of the buffer for (int32 i = 0; i < count; i++) { - Item& item = items[i]; - context->UpdateBuffer(item.BoneMatrices, item.Data, item.Size); + auto& item = updates[i]; + context->UpdateBuffer(globalBuffer, globalData + item.Offset, item.Size, item.Offset); } - - // Batch resource barriers for reading in Vertex Shader - for (int32 i = 0; i < count; i++) - pass.Transition(items[i].BoneMatrices, GPUResourceAccess::ShaderReadGraphics); + pass.Transition(globalBuffer, GPUResourceAccess::ShaderReadGraphics); } #if COMPILE_WITH_PROFILER - // Insert amount of kilobytes of data updated into profiler trace - uint32 dataSize = 0; - for (int32 i = 0; i < count; i++) - dataSize += items[i].Size; - ZoneValue(dataSize / 1024); + ZoneValue(UpdateSize / 1024); // Trace amount of kilobytes of data updated #endif - - Items.Clear(); + Updates.Clear(); + UpdateSize = 0; } }; @@ -107,6 +201,94 @@ AnimatedModel::AnimatedModel(const SpawnParams& params) _sphere = BoundingSphere(Vector3::Zero, 0.0f); } +AnimatedModel::SkinnedBones::SkinnedBones() +{ + static_assert(sizeof(*this) == sizeof(uint64), "Update size/alignment."); + *(uint64*)this = 0; +} + +AnimatedModel::SkinnedBones::~SkinnedBones() +{ + if (IsAllocated) + { + uint32 dataSize = BonesCount * sizeof(Matrix3x4) * (HasPrevBones ? 2 : 1); + RenderListExtension.Free({ dataSize, GlobalBufferOffset }); + } +} + +void AnimatedModel::SkinnedBones::Update(const SkeletonData& skeleton, const Array& nodesPose, bool perBoneMotionBlur, bool reset) +{ + const int32 bonesCount = skeleton.Bones.Count(); + + // Swap between two halves of the buffer for current/previous frame bones + if (HasPrevBones) + { + IsPrevBones = !IsPrevBones; + } + + // Lazy-allocate from global buffer (double the size when using prev frame bones for per-bone motion vectors) + if (!IsAllocated || BonesCount != bonesCount || HasPrevBones != perBoneMotionBlur) + { + if (IsAllocated) + { + uint32 dataSize = BonesCount * sizeof(Matrix3x4) * (HasPrevBones ? 2 : 1); + RenderListExtension.Free({ dataSize, GlobalBufferOffset }); + } + uint32 dataSize = bonesCount * sizeof(Matrix3x4) * (perBoneMotionBlur ? 2 : 1); + auto alloc = RenderListExtension.Allocate(dataSize); + GlobalBufferOffset = alloc.Offset; + BonesCount = bonesCount; + IsAllocated = true; + IsPrevBones = false; + IsPrevFlushed = false; + HasPrevBones = perBoneMotionBlur; + } + else if (reset) + { + IsPrevBones = false; + IsPrevFlushed = false; + } + + // Copy bones transformations to the CPU buffer (including bone offset matrix) and mark it as dirty to be flushed with GPU buffer later + RenderListExtension.DataLocker.ReadLock(); + const SkeletonBone* bones = skeleton.Bones.Get(); + const Matrix* nodes = nodesPose.Get(); + Matrix3x4* output = (Matrix3x4*)(RenderListExtension.Data.Get() + GlobalBufferOffset); // DataLocker ensures it's safe to access (resizing happens within exclusive write-lock) + if (IsPrevBones) + output += BonesCount; // Write to the second half of the allocation + ASSERT(nodesPose.Count() == skeleton.Nodes.Count()); + for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) + { + const SkeletonBone& bone = bones[boneIndex]; + Matrix matrix; + Matrix::Multiply(bone.OffsetMatrix, nodes[bone.NodeIndex], matrix); + output[boneIndex].SetMatrixTranspose(matrix); + } + RenderListExtension.DataLocker.ReadUnlock(); + IsDirty = true; +} + +void AnimatedModel::SkinnedBones::Flush() +{ + uint32 size = BonesCount * sizeof(Matrix3x4); + uint32 offset = GlobalBufferOffset; + if (IsPrevBones) + { + // Write to the second half of the allocation (1st half will contain previous frame bones) + offset += size; + + // Mark initial flush of the previous frame bones + IsPrevFlushed = true; + } + + // Add pending buffer update + RenderListExtension.Updates.Add({ size, offset }); + Platform::InterlockedAdd(&RenderListExtension.UpdateSize, size); + + // Clear dirty flag + IsDirty = false; +} + AnimatedModel::~AnimatedModel() { if (_deformation) @@ -139,15 +321,6 @@ void AnimatedModel::UpdateAnimation() void AnimatedModel::SetupSkinningData() { - ASSERT(SkinnedModel && SkinnedModel->IsLoaded()); - - const int32 targetBonesCount = SkinnedModel->Skeleton.Bones.Count(); - const int32 currentBonesCount = _skinningData.BonesCount; - - if (targetBonesCount != currentBonesCount) - { - _skinningData.Setup(targetBonesCount); - } } void AnimatedModel::PreInitSkinningData() @@ -158,7 +331,6 @@ void AnimatedModel::PreInitSkinningData() PROFILE_MEM(Animations); ScopeLock lock(SkinnedModel->Locker); - SetupSkinningData(); auto& skeleton = SkinnedModel->Skeleton; const int32 bonesCount = skeleton.Bones.Count(); const int32 nodesCount = skeleton.Nodes.Count(); @@ -180,15 +352,7 @@ void AnimatedModel::PreInitSkinningData() GraphInstance.RootTransform = nodesCount > 0 ? skeleton.Nodes[0].LocalTransform : Transform::Identity; // Setup bones transformations including bone offset matrix - Matrix3x4* output = (Matrix3x4*)_skinningData.Data.Get(); - const SkeletonBone* bones = skeleton.Bones.Get(); - for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) - { - auto& bone = bones[boneIndex]; - Matrix identityMatrix = bone.OffsetMatrix * nodesPose[bone.NodeIndex]; - output[boneIndex].SetMatrixTranspose(identityMatrix); - } - _skinningData.OnDataChanged(true); + _bones.Update(skeleton, GraphInstance.NodesPose, PerBoneMotionBlur, true); UpdateBounds(); UpdateSockets(); @@ -926,18 +1090,7 @@ void AnimatedModel::OnAnimationUpdated_Async() // Calculate the final bones transformations and update skinning { ANIM_GRAPH_PROFILE_EVENT("Final Pose"); - const int32 bonesCount = skeleton.Bones.Count(); - Matrix3x4* output = (Matrix3x4*)_skinningData.Data.Get(); - ASSERT(GraphInstance.NodesPose.Count() == skeleton.Nodes.Count()); - ASSERT(_skinningData.Data.Count() == bonesCount * sizeof(Matrix3x4)); - for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++) - { - const SkeletonBone& bone = skeleton.Bones[boneIndex]; - Matrix matrix; - Matrix::Multiply(bone.OffsetMatrix, GraphInstance.NodesPose.Get()[bone.NodeIndex], matrix); - output[boneIndex].SetMatrixTranspose(matrix); - } - _skinningData.OnDataChanged(!PerBoneMotionBlur); + _bones.Update(skeleton, GraphInstance.NodesPose, PerBoneMotionBlur); } //if (UpdateWhenOffscreen) @@ -1077,18 +1230,26 @@ void AnimatedModel::Draw(RenderContext& renderContext) GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); - if (_skinningData.IsReady()) + if (_bones.IsAllocated) { // Flush skinning data with GPU - if (_skinningData.IsDirty()) - { - RenderListExtension.Items.Add({ _skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count() }); - _skinningData.OnFlush(); - } + if (_bones.IsDirty) + _bones.Flush(); SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; - draw.Skinning = &_skinningData; + draw.SkinningBones = RenderListExtension.GlobalBuffer; + draw.SkinningBonesOffset = _bones.GlobalBufferOffset / sizeof(Matrix3x4); + draw.WithPrevBones = _bones.HasPrevBones && _bones.IsPrevFlushed; + if (draw.WithPrevBones) + { + draw.PrevBonesOffset = _bones.BonesCount; + if (_bones.IsPrevBones) + { + draw.SkinningBonesOffset += draw.PrevBonesOffset; + draw.PrevBonesOffset = -draw.PrevBonesOffset; + } + } draw.World = &world; draw.DrawState = &_drawState; draw.Deformation = _deformation; @@ -1120,18 +1281,26 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch) GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); - if (_skinningData.IsReady()) + if (_bones.IsAllocated) { // Flush skinning data with GPU - if (_skinningData.IsDirty()) - { - RenderListExtension.Items.Add({ _skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count() }); - _skinningData.OnFlush(); - } + if (_bones.IsDirty) + _bones.Flush(); SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; - draw.Skinning = &_skinningData; + draw.SkinningBones = RenderListExtension.GlobalBuffer; + draw.SkinningBonesOffset = _bones.GlobalBufferOffset / sizeof(Matrix3x4); + draw.WithPrevBones = _bones.HasPrevBones && _bones.IsPrevFlushed; + if (draw.WithPrevBones) + { + draw.PrevBonesOffset = _bones.BonesCount; + if (_bones.IsPrevBones) + { + draw.SkinningBonesOffset += draw.PrevBonesOffset; + draw.PrevBonesOffset = -draw.PrevBonesOffset; + } + } draw.World = &world; draw.DrawState = &_drawState; draw.Deformation = _deformation; diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index bbfa6f0ea..a13634568 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -5,7 +5,6 @@ #include "ModelInstanceActor.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Content/Assets/AnimationGraph.h" -#include "Engine/Graphics/Models/SkinnedMeshDrawData.h" #include "Engine/Renderer/DrawCall.h" #include "Engine/Core/Delegate.h" @@ -80,9 +79,26 @@ private: uint32 Usages; }; + struct SkinnedBones + { + uint32 GlobalBufferOffset; // In bytes + uint16 BonesCount; + uint16 IsAllocated : 1; + uint16 IsDirty : 1; + uint16 IsPrevBones : 1; + uint16 IsPrevFlushed : 1; + uint16 HasPrevBones : 1; + + SkinnedBones(); + ~SkinnedBones(); + + void Update(const SkeletonData& skeleton, const Array& nodesPose, bool perBoneMotionBlur, bool reset = false); + void Flush(); + }; + GeometryDrawStateData _drawState; - SkinnedMeshDrawData _skinningData; AnimationUpdateMode _actualMode; + SkinnedBones _bones; uint32 _counter; Real _lastMinDstSqr; bool _isDuringUpdateEvent = false; @@ -216,8 +232,9 @@ public: /// /// Validates and creates a proper skinning data. + /// [Deprecated in v1.13] /// - API_FUNCTION() void SetupSkinningData(); + API_FUNCTION() DEPRECATED("Not used anymore. Does nothing.") void SetupSkinningData(); /// /// Creates and setups the skinning data (writes the identity bones transformations). diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index fa4c2abfb..2c49444b8 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -366,19 +366,11 @@ void Camera::Draw(RenderContext& renderContext) draw.Buffer = &_previewModelBuffer; draw.World = &world; draw.DrawState = &drawState; - draw.Deformation = nullptr; - draw.Lightmap = nullptr; - draw.LightmapUVs = nullptr; draw.Flags = StaticFlags::Transform; draw.DrawModes = (DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward) & renderContext.View.Pass; BoundingSphere::FromBox(_previewModelBox, draw.Bounds); draw.Bounds.Center -= renderContext.View.Origin; draw.PerInstanceRandom = GetPerInstanceRandom(); - draw.StencilValue = 0; - draw.LODBias = 0; - draw.ForcedLOD = -1; - draw.SortOrder = 0; - draw.VertexColors = nullptr; if (draw.DrawModes != DrawPass::None) { _previewModel->Draw(renderContext, draw); diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 5acdf6ecd..420f2c613 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -148,6 +148,13 @@ struct DrawCall /// int32 InstanceCount; + enum class SkinningMode + { + None = 0, + Active, + WithPrevBones, + }; + union { struct @@ -190,9 +197,12 @@ struct DrawCall { const Lightmap* Lightmap; Half4 LightmapUVsArea; - SkinnedMeshDrawData* Skinning; + SkinningMode Skinning; + int16 PrevBonesOffset; // In Matrix3x4s, can be negative + byte LODDitherFactor; // The model LOD transition dither progress. + GPUBuffer* SkinningBones; Float3 GeometrySize; // Object geometry size in the world (unscaled). - float LODDitherFactor; // The model LOD transition dither progress. + uint32 SkinningBonesOffset; // In Matrix3x4s Matrix PrevWorld; } Surface; @@ -276,7 +286,7 @@ struct DrawCall uint8 StencilValue; /// - /// The world matrix determinant sign (used for geometry that is two sided or has inverse scale - needs to flip normal vectors and change triangles culling). + /// The world matrix determinant sign (used for geometry that is two-sided or has inverse scale - needs to flip normal vectors and change triangles culling). /// 0 - sign is positive /// 1 - sign is negative (flips object surfaces) /// diff --git a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp index 0c75ce10d..9e69dd716 100644 --- a/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp +++ b/Source/Engine/Renderer/Editor/LightmapUVsDensity.cpp @@ -104,7 +104,7 @@ void LightmapUVsDensityMaterialShader::Bind(BindParameters& params) data.LightmapSize = 1024.0f; data.LightmapArea = drawCall.Surface.LightmapUVsArea.ToFloat4(); const ModelLOD* drawCallModelLod; - float scaleInLightmap = drawCall.Surface.LODDitherFactor; // Reuse field + float scaleInLightmap = *(float*)&drawCall.Surface.SkinningBonesOffset; // Reuse field (the same bit-depth) if (scaleInLightmap < 0.0f) data.LightmapSize = -1.0f; // Not using lightmap else if (GBufferPass::IndexBufferToModelLOD.TryGet(drawCall.Geometry.IndexBuffer, drawCallModelLod)) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 7d00a1a6b..ae2bc18d3 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -47,7 +47,7 @@ namespace } } -void ShaderObjectData::Store(const Matrix& worldMatrix, const Matrix& prevWorldMatrix, const Half4& lightmapUVsArea, const Float3& geometrySize, float perInstanceRandom, float worldDeterminantSign, float lodDitherFactor) +void ShaderObjectData::Store(const Matrix& worldMatrix, const Matrix& prevWorldMatrix, const Half4& lightmapUVsArea, const Float3& geometrySize, float perInstanceRandom, float worldDeterminantSign, byte lodDitherFactor, uint32 skinningOffset, int16 skinningPrevOffset) { Float2 lightmapUVsAreaPackedAliased = *(Float2*)&lightmapUVsArea; Raw[0] = Float4(worldMatrix.M11, worldMatrix.M12, worldMatrix.M13, worldMatrix.M41); @@ -57,11 +57,15 @@ void ShaderObjectData::Store(const Matrix& worldMatrix, const Matrix& prevWorldM Raw[4] = Float4(prevWorldMatrix.M21, prevWorldMatrix.M22, prevWorldMatrix.M23, prevWorldMatrix.M42); Raw[5] = Float4(prevWorldMatrix.M31, prevWorldMatrix.M32, prevWorldMatrix.M33, prevWorldMatrix.M43); Raw[6] = Float4(geometrySize, perInstanceRandom); - Raw[7] = Float4(worldDeterminantSign, lodDitherFactor, lightmapUVsAreaPackedAliased.X, lightmapUVsAreaPackedAliased.Y); - // TODO: pack WorldDeterminantSign and LODDitherFactor + // 0-3 bits: LOD Dither Factor (0-1 range mapped to 0-255) + // 4 bit: World Determinant Sign (0 for normal or 1 for inversed) + // 5-15 bits: unused + // 16-31 bits: Offset in Skinning Bones buffer for previous frame bones (can be negative) + uint32 packed7x = (uint32)lodDitherFactor + (worldDeterminantSign < 0 ? 256 : 0) + ((skinningPrevOffset + 32760) << 16); + Raw[7] = Float4(*(float*)&packed7x, *(float*)&skinningOffset, lightmapUVsAreaPackedAliased.X, lightmapUVsAreaPackedAliased.Y); } -void ShaderObjectData::Load(Matrix& worldMatrix, Matrix& prevWorldMatrix, Half4& lightmapUVsArea, Float3& geometrySize, float& perInstanceRandom, float& worldDeterminantSign, float& lodDitherFactor) const +void ShaderObjectData::Load(Matrix& worldMatrix, Matrix& prevWorldMatrix, Half4& lightmapUVsArea, Float3& geometrySize, float& perInstanceRandom, float& worldDeterminantSign, byte& lodDitherFactor, uint32& skinningOffset, int16& skinningPrevOffset) const { worldMatrix.SetRow1(Float4(Float3(Raw[0]), 0.0f)); worldMatrix.SetRow2(Float4(Float3(Raw[1]), 0.0f)); @@ -73,8 +77,11 @@ void ShaderObjectData::Load(Matrix& worldMatrix, Matrix& prevWorldMatrix, Half4& prevWorldMatrix.SetRow4(Float4(Raw[3].W, Raw[4].W, Raw[5].W, 1.0f)); geometrySize = Float3(Raw[6]); perInstanceRandom = Raw[6].W; - worldDeterminantSign = Raw[7].X; - lodDitherFactor = Raw[7].Y; + uint32 packed7x = *(uint32*)&Raw[7].X; + lodDitherFactor = packed7x & 255; + worldDeterminantSign = (packed7x & 256) == 256 ? -1.0f : 1.0f; + skinningOffset = *(uint32*)&Raw[7].Y; + skinningPrevOffset = (packed7x >> 16) - 32760; Float2 lightmapUVsAreaPackedAliased(Raw[7].Z, Raw[7].W); lightmapUVsArea = *(Half4*)&lightmapUVsAreaPackedAliased; } @@ -289,6 +296,11 @@ void RenderList::CleanupCache() // Don't call it during rendering (data may be already in use) ASSERT(GPUDevice::Instance == nullptr || GPUDevice::Instance->CurrentTask == nullptr); + // Free extensions + for (IExtension* e : GetExtensions()) + e->Dispose(); + + // Free pooled memory MemPoolLocker.Lock(); FreeRenderList.ClearDelete(); for (auto& e : MemPool) @@ -935,7 +947,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD FORCE_INLINE bool CanUseInstancing(DrawPass pass) { - return pass == DrawPass::GBuffer || pass == DrawPass::Depth; + return pass == DrawPass::GBuffer || pass == DrawPass::Depth || pass == DrawPass::MotionVectors; } FORCE_INLINE bool DrawsEqual(const DrawCall* a, const DrawCall* b) @@ -1217,15 +1229,14 @@ void SurfaceDrawCallHandler::GetHash(const DrawCall& drawCall, uint32& batchKey) { if (drawCall.Surface.Lightmap) CombineHash(batchKey, 1313); - if (drawCall.Surface.Skinning) - CombineHash(batchKey, 11); + CombineHash(batchKey, (byte)drawCall.Surface.Skinning); } bool SurfaceDrawCallHandler::CanBatch(const DrawCall& a, const DrawCall& b, DrawPass pass) { // TODO: find reason why batching static meshes with lightmap causes problems with sampling in shader (flickering when meshes in batch order gets changes due to async draw calls collection) if (a.Surface.Lightmap == nullptr && b.Surface.Lightmap == nullptr && - a.Surface.Skinning == nullptr && b.Surface.Skinning == nullptr) + a.Surface.Skinning == b.Surface.Skinning) { auto& materialInfo = a.Material->GetInfo(); if (a.Material != b.Material) diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 3fb203dfe..9294c569c 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -359,6 +359,8 @@ API_CLASS(Sealed) class FLAXENGINE_API RenderList : public ScriptingObject IExtension(); virtual ~IExtension(); + // Event called when GPU Device is shutting down and the extension is being disposed. Used to clean up GPU resources (before scripting might destroy extension). + virtual void Dispose() {} // Event called before collecting draw calls. Can be used for initialization. virtual void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch) {} // Event called after collecting draw calls. Can be used for cleanup or to perform additional drawing using collected draw calls data such as batched data processing. @@ -690,18 +692,18 @@ GPU_CB_STRUCT(ShaderObjectData { Float4 Raw[8]; - void FLAXENGINE_API Store(const Matrix& worldMatrix, const Matrix& prevWorldMatrix, const Half4& lightmapUVsAreaPacked, const Float3& geometrySize, float perInstanceRandom = 0.0f, float worldDeterminantSign = 1.0f, float lodDitherFactor = 0.0f); - void FLAXENGINE_API Load(Matrix& worldMatrix, Matrix& prevWorldMatrix, Half4& lightmapUVsArea, Float3& geometrySize, float& perInstanceRandom, float& worldDeterminantSign, float& lodDitherFactor) const; + void FLAXENGINE_API Store(const Matrix& worldMatrix, const Matrix& prevWorldMatrix, const Half4& lightmapUVsAreaPacked, const Float3& geometrySize, float perInstanceRandom = 0.0f, float worldDeterminantSign = 1.0f, byte lodDitherFactor = 0, uint32 skinningOffset = 0, int16 skinningPrevOffset = 0); + void FLAXENGINE_API Load(Matrix& worldMatrix, Matrix& prevWorldMatrix, Half4& lightmapUVsArea, Float3& geometrySize, float& perInstanceRandom, float& worldDeterminantSign, byte& lodDitherFactor, uint32& skinningOffset, int16& prevBonesOffset) const; FORCE_INLINE void Store(const DrawCall& drawCall) { - Store(drawCall.World, drawCall.Surface.PrevWorld, drawCall.Surface.LightmapUVsArea, drawCall.Surface.GeometrySize, drawCall.PerInstanceRandom, drawCall.WorldDeterminant ? -1.0f : 1.0f, drawCall.Surface.LODDitherFactor); + Store(drawCall.World, drawCall.Surface.PrevWorld, drawCall.Surface.LightmapUVsArea, drawCall.Surface.GeometrySize, drawCall.PerInstanceRandom, drawCall.WorldDeterminant ? -1.0f : 1.0f, drawCall.Surface.LODDitherFactor, drawCall.Surface.SkinningBonesOffset, drawCall.Surface.PrevBonesOffset); } FORCE_INLINE void Load(DrawCall& drawCall) const { float worldDeterminantSign; - Load(drawCall.World, drawCall.Surface.PrevWorld, drawCall.Surface.LightmapUVsArea, drawCall.Surface.GeometrySize, drawCall.PerInstanceRandom, worldDeterminantSign, drawCall.Surface.LODDitherFactor); + Load(drawCall.World, drawCall.Surface.PrevWorld, drawCall.Surface.LightmapUVsArea, drawCall.Surface.GeometrySize, drawCall.PerInstanceRandom, worldDeterminantSign, drawCall.Surface.LODDitherFactor, drawCall.Surface.SkinningBonesOffset, drawCall.Surface.PrevBonesOffset); drawCall.ObjectPosition = drawCall.World.GetTranslation(); drawCall.WorldDeterminant = worldDeterminantSign < 0 ? 1 : 0; } diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index c2ccc3d67..7065e33dd 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -19,6 +19,7 @@ #include "Engine/Renderer/Lightmaps.h" #endif +#define SHADOWS_USE_CACHE 1 #define SHADOWS_POSITION_ERROR METERS_TO_UNITS(0.1f) #define SHADOWS_ROTATION_ERROR 0.9999f #define SHADOWS_MAX_TILES 6 @@ -237,6 +238,7 @@ struct ShadowAtlasLight void ValidateCache(const RenderView& view, const RenderLightData& light) { +#if SHADOWS_USE_CACHE if (!Cache.StaticValid || !Cache.DynamicValid) return; if (!Math::NearEqual(Cache.Distance, light.ShadowsDistance) || @@ -286,6 +288,9 @@ struct ShadowAtlasLight Cache.DynamicValid = false; } } +#else + Cache.StaticValid = Cache.DynamicValid = false; +#endif } }; diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 3ffedbe6f..b873173d6 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -126,7 +126,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const drawCall.SetStencilValue(_patch->_terrain->GetLayer()); #if USE_EDITOR if (renderContext.View.Mode == ViewMode::LightmapUVsDensity) - drawCall.Surface.LODDitherFactor = 1.0f; // See LightmapUVsDensityMaterialShader + { + float lightmapScale = 1.0f; + drawCall.Surface.SkinningBonesOffset = *(uint32*)&lightmapScale; // See LightmapUVsDensityMaterialShader + } #endif // Add half-texel offset for heightmap sampling in vertex shader @@ -187,7 +190,10 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi drawCall.SetStencilValue(_patch->_terrain->GetLayer()); #if USE_EDITOR if (renderContext.View.Mode == ViewMode::LightmapUVsDensity) - drawCall.Surface.LODDitherFactor = 1.0f; // See LightmapUVsDensityMaterialShader + { + float lightmapScale = 1.0f; + drawCall.Surface.SkinningBonesOffset = *(uint32*)&lightmapScale; // See LightmapUVsDensityMaterialShader + } #endif // Add half-texel offset for heightmap sampling in vertex shader diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp index 707c24416..f23ee3e43 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.cpp @@ -487,7 +487,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo switch (baseLayer->Domain) { case MaterialDomain::Surface: - srv = 3; // Objects + Skinning Bones + Prev Bones + srv = 2; // Objects + Skinning Bones break; case MaterialDomain::Decal: srv = 2; // Depth buffer + Stencil buffer diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 6bb50ae96..fc86d74e5 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1089,7 +1089,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option // Special case if imported model has no bones but has valid skeleton and meshes. // We assume that every mesh uses a single bone. Copy nodes to bones. - if (data.Skeleton.Bones.IsEmpty() && Math::IsInRange(data.Skeleton.Nodes.Count(), 1, MODEL_MAX_BONES_PER_MODEL)) + if (data.Skeleton.Bones.IsEmpty() && Math::IsInRange(data.Skeleton.Nodes.Count(), 1, (int32)MODEL_MAX_BONES_PER_MODEL)) { data.Skeleton.Bones.Resize(data.Skeleton.Nodes.Count()); for (int32 i = 0; i < data.Skeleton.Nodes.Count(); i++) diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl index f3f705f52..6ca9d8216 100644 --- a/Source/Shaders/MaterialCommon.hlsl +++ b/Source/Shaders/MaterialCommon.hlsl @@ -81,9 +81,11 @@ struct ObjectData float4x4 PrevWorldMatrix; float3 GeometrySize; float WorldDeterminantSign; + float4 LightmapArea; float LODDitherFactor; float PerInstanceRandom; - float4 LightmapArea; + uint SkinningOffset; + int PrevBonesOffset; }; float2 UnpackHalf2(uint xy) @@ -115,8 +117,11 @@ ObjectData LoadObject(Buffer objectsBuffer, uint objectIndex) object.PrevWorldMatrix[3] = float4(vector3.w, vector4.w, vector5.w, 1.0f); object.GeometrySize = vector6.xyz; object.PerInstanceRandom = vector6.w; - object.WorldDeterminantSign = vector7.x; - object.LODDitherFactor = vector7.y; + uint packed7x = asuint(vector7.x); + object.WorldDeterminantSign = (packed7x & 256) == 256 ? -1.0f : 1.0f; + object.LODDitherFactor = (packed7x & 255) / 255.0f; + object.SkinningOffset = asuint(vector7.y); + object.PrevBonesOffset = (int)(packed7x >> 16) - 32760; object.LightmapArea.xy = UnpackHalf2(asuint(vector7.z)); object.LightmapArea.zw = UnpackHalf2(asuint(vector7.w)); return object; @@ -247,6 +252,9 @@ struct ModelInput_Skinned #endif uint4 BlendIndices : BLENDINDICES; float4 BlendWeights : BLENDWEIGHTS; +#if USE_INSTANCING + uint ObjectIndex : ATTRIBUTE0; +#endif }; struct Model_VS2PS