Skip to content

Commit ddf6a01

Browse files
authored
fix: Ensure cached data is up to date when models are mutated (#2936)
1 parent 24d3660 commit ddf6a01

File tree

3 files changed

+131
-38
lines changed

3 files changed

+131
-38
lines changed

sources/engine/Stride.Engine/Engine/ModelComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public SkeletonUpdater Skeleton
124124

125125
private void CheckSkeleton()
126126
{
127-
if (modelViewHierarchyDirty)
127+
if (modelViewHierarchyDirty || meshInfos.Count != model.Meshes.Count)
128128
{
129129
ModelUpdated();
130130
modelViewHierarchyDirty = false;

sources/engine/Stride.Engine/Rendering/ModelRenderProcessor.cs

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private void UpdateRenderModel(ModelComponent modelComponent, RenderModel render
118118
}
119119
}
120120

121-
private void UpdateMaterial(RenderMesh renderMesh, MaterialPass materialPass, MaterialInstance modelMaterialInstance, ModelComponent modelComponent)
121+
private void UpdateMaterial(RenderMesh renderMesh, MaterialPass materialPass, MaterialInstance modelMaterialInstance, ModelComponent modelComponent, Mesh sourceMesh)
122122
{
123123
var isShadowCaster = modelComponent.IsShadowCaster;
124124
if (modelMaterialInstance != null)
@@ -132,6 +132,8 @@ private void UpdateMaterial(RenderMesh renderMesh, MaterialPass materialPass, Ma
132132
}
133133

134134
renderMesh.MaterialPass = materialPass;
135+
// Account for any changes made to the individual meshes
136+
renderMesh.Mesh = sourceMesh;
135137
}
136138

137139
private Material FindMaterial(Material materialOverride, MaterialInstance modelMaterialInstance)
@@ -141,46 +143,46 @@ private Material FindMaterial(Material materialOverride, MaterialInstance modelM
141143

142144
private void CheckMeshes(ModelComponent modelComponent, RenderModel renderModel)
143145
{
144-
// Check if model changed
146+
// Check if model changed to update our cached data
145147
var model = modelComponent.Model;
146-
if (renderModel.Model == model)
147-
{
148-
// Check if any material pass count changed
149-
if (model != null)
150-
{
151-
// Number of meshes changed in the model?
152-
if (model.Meshes.Count != renderModel.UniqueMeshCount)
153-
goto RegenerateMeshes;
148+
if (renderModel.Model != model)
149+
goto RegenerateMeshes;
154150

155-
if (modelComponent.Enabled)
156-
{
157-
// Check materials
158-
var modelComponentMaterials = modelComponent.Materials;
159-
for (int sourceMeshIndex = 0; sourceMeshIndex < model.Meshes.Count; sourceMeshIndex++)
160-
{
161-
ref var material = ref renderModel.Materials[sourceMeshIndex];
162-
var materialIndex = model.Meshes[sourceMeshIndex].MaterialIndex;
163-
164-
var newMaterial = FindMaterial(modelComponentMaterials.SafeGet(materialIndex), model.Materials.GetItemOrNull(materialIndex));
165-
166-
// If material changed or its number of pass changed, trigger a full regeneration of RenderMeshes (note: we could do partial later)
167-
if ((newMaterial?.Passes.Count ?? 1) != material.MeshCount)
168-
goto RegenerateMeshes;
169-
170-
// Update materials
171-
material.Material = newMaterial;
172-
int meshIndex = material.MeshStartIndex;
173-
for (int pass = 0; pass < material.MeshCount; ++pass, ++meshIndex)
174-
{
175-
UpdateMaterial(renderModel.Meshes[meshIndex], newMaterial?.Passes[pass], model.Materials.GetItemOrNull(materialIndex), modelComponent);
176-
}
177-
}
178-
}
179-
}
151+
if (model == null)
152+
return;
153+
154+
// Number of meshes changed in the model?
155+
if (model.Meshes.Count != renderModel.UniqueMeshCount)
156+
goto RegenerateMeshes;
180157

158+
if (!modelComponent.Enabled)
181159
return;
160+
161+
// Check materials
162+
var modelComponentMaterials = modelComponent.Materials;
163+
for (int sourceMeshIndex = 0; sourceMeshIndex < model.Meshes.Count; sourceMeshIndex++)
164+
{
165+
ref var material = ref renderModel.Materials[sourceMeshIndex];
166+
var materialIndex = model.Meshes[sourceMeshIndex].MaterialIndex;
167+
168+
var baseMaterial = model.Materials.GetItemOrNull(materialIndex);
169+
var newMaterial = FindMaterial(modelComponentMaterials.SafeGet(materialIndex), baseMaterial);
170+
171+
// If material changed or its number of pass changed, trigger a full regeneration of RenderMeshes (note: we could do partial later)
172+
if ((newMaterial?.Passes.Count ?? 1) != material.MeshCount)
173+
goto RegenerateMeshes;
174+
175+
// Update materials
176+
material.Material = newMaterial;
177+
int meshIndex = material.MeshStartIndex;
178+
for (int pass = 0; pass < material.MeshCount; ++pass, ++meshIndex)
179+
{
180+
UpdateMaterial(renderModel.Meshes[meshIndex], newMaterial?.Passes[pass], baseMaterial, modelComponent, model.Meshes[sourceMeshIndex]);
181+
}
182182
}
183183

184+
return;
185+
184186
RegenerateMeshes:
185187
renderModel.Model = model;
186188

@@ -228,11 +230,10 @@ private void CheckMeshes(ModelComponent modelComponent, RenderModel renderModel)
228230
{
229231
Source = modelComponent,
230232
RenderModel = renderModel,
231-
Mesh = mesh,
232233
};
233234

234235
// Update material
235-
UpdateMaterial(renderMeshes[meshIndex], material.Material?.Passes[pass], model.Materials.GetItemOrNull(materialIndex), modelComponent);
236+
UpdateMaterial(renderMeshes[meshIndex], material.Material?.Passes[pass], model.Materials.GetItemOrNull(materialIndex), modelComponent, mesh);
236237
}
237238
}
238239

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
using System;
5+
using System.Linq;
6+
using Stride.Core.Mathematics;
7+
using Stride.Engine;
8+
using Stride.Rendering;
9+
using Stride.Rendering.Materials;
10+
using Stride.Rendering.Materials.ComputeColors;
11+
using Stride.Rendering.ProceduralModels;
12+
using Xunit;
13+
14+
namespace Stride.Graphics.Tests
15+
{
16+
public class TestModelComponent : GraphicTestGameBase
17+
{
18+
[Fact]
19+
public static void TestMutateModel()
20+
{
21+
var game = new TestModelComponent();
22+
game.Script.AddTask(async () =>
23+
{
24+
game.ScreenShotAutomationEnabled = false;
25+
26+
await game.Script.NextFrame();
27+
28+
var model = new CubeProceduralModel().Generate(game.Services);
29+
model.Materials.Add(Material.New(game.GraphicsDevice, new MaterialDescriptor
30+
{
31+
Attributes =
32+
{
33+
Diffuse = new MaterialDiffuseMapFeature(new ComputeColor(Color.White)),
34+
DiffuseModel = new MaterialDiffuseLambertModelFeature()
35+
}
36+
}));
37+
var entity = new Entity { new ModelComponent { Model = model } };
38+
39+
game.SceneSystem.SceneInstance.RootScene.Entities.Add(entity);
40+
41+
// Let the engine cache this model by waiting for the next frame
42+
await game.Script.NextFrame();
43+
44+
// Now let's mutate this model to ensure that the engine keeps up ...
45+
46+
// Adding a second mesh
47+
48+
model.Meshes.Add(new CubeProceduralModel().Generate(game.Services).Meshes[0]);
49+
model.Materials.Add(model.Materials[0]);
50+
51+
await game.Script.NextFrame();
52+
53+
// Removing a mesh
54+
55+
model.Meshes.RemoveAt(1);
56+
model.Materials.RemoveAt(1);
57+
58+
await game.Script.NextFrame();
59+
60+
// Changing a meshes' draw
61+
62+
MakeInvalid(model.Meshes[0].Draw);
63+
model.Meshes[0].Draw = new CubeProceduralModel().Generate(game.Services).Meshes[0].Draw;
64+
65+
await game.Script.NextFrame();
66+
67+
// Swapping the whole mesh
68+
69+
MakeInvalid(model.Meshes[0].Draw);
70+
model.Meshes[0].MaterialIndex = Int32.MaxValue;
71+
model.Meshes[0].NodeIndex = Int32.MaxValue;
72+
model.Meshes[0] = new CubeProceduralModel().Generate(game.Services).Meshes[0];
73+
74+
await game.Script.NextFrame();
75+
76+
game.Exit();
77+
});
78+
RunGameTest(game);
79+
80+
static void MakeInvalid(MeshDraw draw)
81+
{
82+
// Setting invalid data to ensure that anything that still uses a cached version of this draw would throw
83+
draw.VertexBuffers[0].Buffer.Dispose();
84+
draw.VertexBuffers = null;
85+
draw.IndexBuffer.Buffer.Dispose();
86+
draw.IndexBuffer = null;
87+
draw.DrawCount = Int32.MaxValue;
88+
draw.PrimitiveType = (PrimitiveType)Int32.MaxValue;
89+
}
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)