Skip to content

Commit 077f933

Browse files
Radient: move world transform propagation to commit phase
1 parent cff419e commit 077f933

3 files changed

Lines changed: 126 additions & 32 deletions

File tree

Radient/include/RadientSceneState.hpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#pragma once
2828

2929
#include "RadientScene.h"
30+
#include "FlagEnum.h"
3031

3132
#include "entt/entity/registry.hpp"
3233

@@ -69,6 +70,15 @@ class RadientSceneState
6970
RADIENT_STATUS CommitChanges();
7071

7172
private:
73+
enum DIRTY_FLAGS : Uint32
74+
{
75+
DIRTY_FLAG_NONE = 0u,
76+
DIRTY_FLAG_TRANSFORM = 1u << 0u,
77+
DIRTY_FLAG_VISIBILITY = 1u << 1u,
78+
DIRTY_FLAGS_REQUIRING_PROPAGATION = DIRTY_FLAG_TRANSFORM | DIRTY_FLAG_VISIBILITY
79+
};
80+
DECLARE_FRIEND_FLAG_ENUM_OPERATORS(DIRTY_FLAGS);
81+
7282
struct EntityComponent
7383
{
7484
RadientEntityID ID = InvalidRadientEntityID;
@@ -94,7 +104,11 @@ class RadientSceneState
94104
struct WorldTransformComponent
95105
{
96106
RadientMatrix4x4 Matrix;
97-
bool Dirty = true;
107+
};
108+
109+
struct DirtyStateComponent
110+
{
111+
DIRTY_FLAGS Flags = DIRTY_FLAG_NONE;
98112
};
99113

100114
struct CustomComponentStorage
@@ -117,14 +131,18 @@ class RadientSceneState
117131
bool IsDescendant(entt::entity Entity, entt::entity PotentialAncestor) const;
118132
void DetachFromParent(entt::entity Entity);
119133
void DestroyEntitySubtree(entt::entity Entity);
120-
void MarkWorldMatrixDirty(entt::entity Entity);
121-
void UpdateWorldMatrix(entt::entity Entity) const;
134+
DIRTY_FLAGS MarkDirty(entt::entity Entity, DIRTY_FLAGS Flags);
135+
void PropagateDirtyFlags();
136+
void PropagateDirtyFlags(entt::entity Entity, DIRTY_FLAGS Flags);
137+
void UpdateDirtyEntities();
138+
void UpdateTransform(entt::entity Entity);
122139
void Touch();
123140

124141
RadientSceneDesc m_Desc;
125142

126143
mutable entt::registry m_Registry;
127144
std::unordered_map<RadientEntityID, entt::entity> m_EntityMap;
145+
std::vector<RadientEntityID> m_DirtyEntities;
128146
RadientEntityID m_NextEntityID = 1;
129147
RadientRevision m_Revision = 0;
130148
};

Radient/src/RadientSceneState.cpp

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
namespace Diligent
3434
{
3535

36+
DEFINE_FLAG_ENUM_OPERATORS(RadientSceneState::DIRTY_FLAGS);
37+
3638
namespace
3739
{
3840

@@ -183,7 +185,6 @@ RADIENT_STATUS RadientSceneState::GetWorldMatrix(RadientEntityID Entity, Radient
183185
return RADIENT_STATUS_NOT_FOUND;
184186
}
185187

186-
UpdateWorldMatrix(E);
187188
Matrix = m_Registry.get<WorldTransformComponent>(E).Matrix;
188189
return RADIENT_STATUS_OK;
189190
}
@@ -258,8 +259,10 @@ RADIENT_STATUS RadientSceneState::CreateEntity(const RadientEntityDesc& Desc, Ra
258259
m_Registry.emplace<HierarchyComponent>(E);
259260
m_Registry.emplace<LocalTransformComponent>(E, LocalTransformComponent{Desc.Transform});
260261
m_Registry.emplace<WorldTransformComponent>(E);
262+
m_Registry.emplace<DirtyStateComponent>(E);
261263

262264
m_EntityMap.emplace(Entity, E);
265+
MarkDirty(E, DIRTY_FLAGS_REQUIRING_PROPAGATION);
263266

264267
if (Parent != entt::null)
265268
{
@@ -294,6 +297,7 @@ RADIENT_STATUS RadientSceneState::SetEntityFlags(RadientEntityID Entity, RADIENT
294297
return RADIENT_STATUS_NO_CHANGE;
295298

296299
State.Flags = Flags;
300+
MarkDirty(E, DIRTY_FLAG_VISIBILITY);
297301
Touch();
298302
return RADIENT_STATUS_OK;
299303
}
@@ -312,6 +316,7 @@ RADIENT_STATUS RadientSceneState::SetEntityOwnVisibility(RadientEntityID Entity,
312316
return RADIENT_STATUS_NO_CHANGE;
313317

314318
State.Flags = Flags;
319+
MarkDirty(E, DIRTY_FLAG_VISIBILITY);
315320
Touch();
316321
return RADIENT_STATUS_OK;
317322
}
@@ -340,13 +345,10 @@ RADIENT_STATUS RadientSceneState::SetParent(RadientEntityID Entity, RadientEntit
340345
RadientTransform LocalTransform = m_Registry.get<LocalTransformComponent>(E).Transform;
341346
if (KeepWorldTransform)
342347
{
343-
UpdateWorldMatrix(E);
344348
RadientMatrix4x4 LocalMatrix = m_Registry.get<WorldTransformComponent>(E).Matrix;
345349

346350
if (NewParent != entt::null)
347351
{
348-
UpdateWorldMatrix(NewParent);
349-
350352
RadientMatrix4x4 ParentWorldInverse;
351353
if (!RadientMath::TryInverseMatrix(m_Registry.get<WorldTransformComponent>(NewParent).Matrix, ParentWorldInverse))
352354
return RADIENT_STATUS_INVALID_OPERATION;
@@ -368,7 +370,7 @@ RADIENT_STATUS RadientSceneState::SetParent(RadientEntityID Entity, RadientEntit
368370
}
369371

370372
m_Registry.get<LocalTransformComponent>(E).Transform = LocalTransform;
371-
MarkWorldMatrixDirty(E);
373+
MarkDirty(E, DIRTY_FLAGS_REQUIRING_PROPAGATION);
372374
Touch();
373375
return RADIENT_STATUS_OK;
374376
}
@@ -380,7 +382,7 @@ RADIENT_STATUS RadientSceneState::SetLocalTransform(RadientEntityID Entity, cons
380382
return RADIENT_STATUS_NOT_FOUND;
381383

382384
m_Registry.get<LocalTransformComponent>(E).Transform = Transform;
383-
MarkWorldMatrixDirty(E);
385+
MarkDirty(E, DIRTY_FLAG_TRANSFORM);
384386
Touch();
385387
return RADIENT_STATUS_OK;
386388
}
@@ -491,6 +493,9 @@ RADIENT_STATUS RadientSceneState::RemoveComponent(RadientEntityID Entity, Radien
491493

492494
RADIENT_STATUS RadientSceneState::CommitChanges()
493495
{
496+
PropagateDirtyFlags();
497+
UpdateDirtyEntities();
498+
m_DirtyEntities.clear();
494499
return RADIENT_STATUS_OK;
495500
}
496501

@@ -549,7 +554,7 @@ void RadientSceneState::DetachFromParent(entt::entity Entity)
549554

550555
void RadientSceneState::DestroyEntitySubtree(entt::entity Entity)
551556
{
552-
const std::vector<RadientEntityID>& Children = m_Registry.get<HierarchyComponent>(Entity).Children;
557+
const std::vector<RadientEntityID> Children = m_Registry.get<HierarchyComponent>(Entity).Children;
553558
for (const RadientEntityID Child : Children)
554559
{
555560
const entt::entity ChildEntity = FindEntity(Child);
@@ -561,48 +566,97 @@ void RadientSceneState::DestroyEntitySubtree(entt::entity Entity)
561566
m_Registry.destroy(Entity);
562567
}
563568

564-
void RadientSceneState::MarkWorldMatrixDirty(entt::entity Entity)
569+
RadientSceneState::DIRTY_FLAGS RadientSceneState::MarkDirty(entt::entity Entity, DIRTY_FLAGS Flags)
565570
{
566-
m_Registry.get<WorldTransformComponent>(Entity).Dirty = true;
571+
if (Flags == DIRTY_FLAG_NONE)
572+
return DIRTY_FLAG_NONE;
573+
574+
DirtyStateComponent& DirtyState = m_Registry.get<DirtyStateComponent>(Entity);
575+
const DIRTY_FLAGS AddedFlags = Flags & ~DirtyState.Flags;
576+
if (DirtyState.Flags == DIRTY_FLAG_NONE)
577+
m_DirtyEntities.push_back(m_Registry.get<EntityComponent>(Entity).ID);
578+
579+
DirtyState.Flags |= Flags;
580+
return AddedFlags;
581+
}
582+
583+
void RadientSceneState::PropagateDirtyFlags()
584+
{
585+
const size_t DirtyEntityCount = m_DirtyEntities.size();
586+
for (size_t Index = 0; Index < DirtyEntityCount; ++Index)
587+
{
588+
const entt::entity Entity = FindEntity(m_DirtyEntities[Index]);
589+
if (Entity == entt::null)
590+
continue;
567591

592+
const DirtyStateComponent& DirtyState = m_Registry.get<DirtyStateComponent>(Entity);
593+
const DIRTY_FLAGS FlagsToPropagate = DirtyState.Flags & DIRTY_FLAGS_REQUIRING_PROPAGATION;
594+
if (FlagsToPropagate == DIRTY_FLAG_NONE)
595+
continue;
596+
597+
PropagateDirtyFlags(Entity, FlagsToPropagate);
598+
}
599+
}
600+
601+
void RadientSceneState::PropagateDirtyFlags(entt::entity Entity, DIRTY_FLAGS Flags)
602+
{
568603
const std::vector<RadientEntityID>& Children = m_Registry.get<HierarchyComponent>(Entity).Children;
569604
for (const RadientEntityID Child : Children)
570605
{
571606
const entt::entity ChildEntity = FindEntity(Child);
572-
if (ChildEntity != entt::null)
573-
MarkWorldMatrixDirty(ChildEntity);
607+
if (ChildEntity == entt::null)
608+
continue;
609+
610+
const DIRTY_FLAGS AddedFlags = MarkDirty(ChildEntity, Flags);
611+
if (AddedFlags != DIRTY_FLAG_NONE)
612+
PropagateDirtyFlags(ChildEntity, AddedFlags);
574613
}
575614
}
576615

577-
void RadientSceneState::UpdateWorldMatrix(entt::entity Entity) const
616+
void RadientSceneState::UpdateDirtyEntities()
578617
{
579-
WorldTransformComponent& WorldTransform = m_Registry.get<WorldTransformComponent>(Entity);
580-
if (!WorldTransform.Dirty)
581-
return;
618+
for (const RadientEntityID EntityID : m_DirtyEntities)
619+
{
620+
const entt::entity Entity = FindEntity(EntityID);
621+
if (Entity == entt::null)
622+
continue;
582623

583-
const LocalTransformComponent& LocalTransform = m_Registry.get<LocalTransformComponent>(Entity);
584-
const HierarchyComponent& Hierarchy = m_Registry.get<HierarchyComponent>(Entity);
624+
DirtyStateComponent& DirtyState = m_Registry.get<DirtyStateComponent>(Entity);
625+
if ((DirtyState.Flags & DIRTY_FLAG_TRANSFORM) != DIRTY_FLAG_NONE)
626+
UpdateTransform(Entity);
585627

586-
const RadientMatrix4x4 LocalMatrix = RadientMath::TransformToMatrix(LocalTransform.Transform);
587-
if (Hierarchy.Parent == InvalidRadientEntityID)
588-
{
589-
WorldTransform.Matrix = LocalMatrix;
628+
DirtyState.Flags = DIRTY_FLAG_NONE;
590629
}
591-
else
630+
}
631+
632+
void RadientSceneState::UpdateTransform(entt::entity Entity)
633+
{
634+
DirtyStateComponent& DirtyState = m_Registry.get<DirtyStateComponent>(Entity);
635+
if ((DirtyState.Flags & DIRTY_FLAG_TRANSFORM) == DIRTY_FLAG_NONE)
636+
return;
637+
638+
const HierarchyComponent& Hierarchy = m_Registry.get<HierarchyComponent>(Entity);
639+
640+
const RadientMatrix4x4* pParentWorldMatrix = nullptr;
641+
if (Hierarchy.Parent != InvalidRadientEntityID)
592642
{
593643
const entt::entity Parent = FindEntity(Hierarchy.Parent);
594644
if (Parent != entt::null)
595645
{
596-
UpdateWorldMatrix(Parent);
597-
WorldTransform.Matrix = RadientMath::MultiplyMatrices(LocalMatrix, m_Registry.get<WorldTransformComponent>(Parent).Matrix);
598-
}
599-
else
600-
{
601-
WorldTransform.Matrix = LocalMatrix;
646+
UpdateTransform(Parent);
647+
pParentWorldMatrix = &m_Registry.get<WorldTransformComponent>(Parent).Matrix;
602648
}
603649
}
604650

605-
WorldTransform.Dirty = false;
651+
const LocalTransformComponent& LocalTransform = m_Registry.get<LocalTransformComponent>(Entity);
652+
const RadientMatrix4x4 LocalMatrix = RadientMath::TransformToMatrix(LocalTransform.Transform);
653+
654+
WorldTransformComponent& WorldTransform = m_Registry.get<WorldTransformComponent>(Entity);
655+
WorldTransform.Matrix = pParentWorldMatrix != nullptr ?
656+
RadientMath::MultiplyMatrices(LocalMatrix, *pParentWorldMatrix) :
657+
LocalMatrix;
658+
659+
DirtyState.Flags &= ~DIRTY_FLAG_TRANSFORM;
606660
}
607661

608662
void RadientSceneState::Touch()

Tests/RadientTest/src/RadientSceneStateTest.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ TEST(RadientSceneStateTest, GetWorldMatrix)
403403
RadientEntityID Root = InvalidRadientEntityID;
404404
EXPECT_EQ(State.CreateEntity(RootDesc, Root), RADIENT_STATUS_OK);
405405
EXPECT_EQ(State.GetWorldMatrix(Root, Matrix), RADIENT_STATUS_OK);
406+
ExpectMatrixNear(Matrix, RadientMatrix4x4{});
407+
408+
EXPECT_EQ(State.CommitChanges(), RADIENT_STATUS_OK);
409+
EXPECT_EQ(State.GetWorldMatrix(Root, Matrix), RADIENT_STATUS_OK);
406410
ExpectMatrixNear(Matrix, RadientMath::TransformToMatrix(RootTransform));
407411

408412
RadientTransform ChildTransform;
@@ -420,29 +424,47 @@ TEST(RadientSceneStateTest, GetWorldMatrix)
420424
RadientMath::TransformToMatrix(ChildTransform),
421425
RadientMath::TransformToMatrix(RootTransform));
422426
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
427+
ExpectMatrixNear(Matrix, RadientMatrix4x4{});
428+
429+
EXPECT_EQ(State.CommitChanges(), RADIENT_STATUS_OK);
430+
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
423431
ExpectMatrixNear(Matrix, ExpectedChildWorld);
424432

433+
RadientMatrix4x4 CommittedChildWorld = ExpectedChildWorld;
434+
425435
RootTransform.Position = {10.f, 20.f, 30.f};
426436
EXPECT_EQ(State.SetLocalTransform(Root, RootTransform), RADIENT_STATUS_OK);
437+
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
438+
ExpectMatrixNear(Matrix, CommittedChildWorld);
427439

428440
ExpectedChildWorld = RadientMath::MultiplyMatrices(
429441
RadientMath::TransformToMatrix(ChildTransform),
430442
RadientMath::TransformToMatrix(RootTransform));
443+
EXPECT_EQ(State.CommitChanges(), RADIENT_STATUS_OK);
431444
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
432445
ExpectMatrixNear(Matrix, ExpectedChildWorld);
446+
CommittedChildWorld = ExpectedChildWorld;
433447

434448
ChildTransform.Position = {8.f, 9.f, 10.f};
435449
ChildTransform.Scale = {4.f, 5.f, 6.f};
436450
EXPECT_EQ(State.SetLocalTransform(Child, ChildTransform), RADIENT_STATUS_OK);
451+
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
452+
ExpectMatrixNear(Matrix, CommittedChildWorld);
437453

438454
ExpectedChildWorld = RadientMath::MultiplyMatrices(
439455
RadientMath::TransformToMatrix(ChildTransform),
440456
RadientMath::TransformToMatrix(RootTransform));
457+
EXPECT_EQ(State.CommitChanges(), RADIENT_STATUS_OK);
441458
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
442459
ExpectMatrixNear(Matrix, ExpectedChildWorld);
460+
CommittedChildWorld = ExpectedChildWorld;
443461

444462
EXPECT_EQ(State.SetParent(Child, InvalidRadientEntityID, True), RADIENT_STATUS_OK);
445463
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
464+
ExpectMatrixNear(Matrix, CommittedChildWorld);
465+
466+
EXPECT_EQ(State.CommitChanges(), RADIENT_STATUS_OK);
467+
EXPECT_EQ(State.GetWorldMatrix(Child, Matrix), RADIENT_STATUS_OK);
446468
ExpectMatrixNear(Matrix, ExpectedChildWorld);
447469

448470
EXPECT_EQ(State.DestroyEntity(Child), RADIENT_STATUS_OK);

0 commit comments

Comments
 (0)