Skip to content

Commit ba88ac3

Browse files
Radient: move morph target streams to GPU-backed geometry data
- split transient morph sources from immutable layout metadata - cache and upload morph streams as per-geometry data assets - import glTF morph targets in each primitive's vertex domain - expose morph buffer offsets to drawable geometry - add source validation, caching, and GPU upload tests
1 parent 272ecfd commit ba88ac3

18 files changed

Lines changed: 1476 additions & 287 deletions

‎Radient/CMakeLists.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set(SOURCE
1818
src/Assets/RadientMaterialAssetImplBase.hpp
1919
src/Assets/RadientMeshIndexSource.cpp
2020
src/Assets/RadientMeshAssetManager.cpp
21+
src/Assets/RadientMorphTargetSource.cpp
2122
src/Assets/RadientMorphTargets.cpp
2223
src/Assets/RadientMeshPrimitives.cpp
2324
src/Assets/RadientMeshVertexSource.cpp
@@ -81,6 +82,7 @@ set(INCLUDE
8182
include/Assets/RadientMeshIndexSource.hpp
8283
include/Assets/RadientMeshAssetManager.hpp
8384
include/Assets/RadientMorphTargetData.hpp
85+
include/Assets/RadientMorphTargetSource.hpp
8486
include/Assets/RadientMeshVertexSource.hpp
8587
include/Assets/RadientMeshViewSource.hpp
8688
include/Assets/RadientTextureAssetManager.hpp

‎Radient/include/Assets/RadientMeshAssetManager.hpp‎

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
#pragma once
2828

29-
#include "Assets/RadientMorphTargetData.hpp"
3029
#include "Render/RadientDrawableMesh.hpp"
3130
#include "RadientAssetCache.hpp"
3231
#include "RadientAssets.h"
@@ -48,8 +47,10 @@ class ResourceManager;
4847

4948
class RadientMeshIndexSource;
5049
class RadientMeshVertexSource;
50+
class RadientMorphTargetSource;
5151
class MeshIndexDataPayloadImpl;
5252
class MeshVertexDataPayloadImpl;
53+
class MeshMorphTargetDataPayloadImpl;
5354
class MeshPayloadImpl;
5455
class RadientMeshAssetManager;
5556
struct RadientMeshViewCreateInfo;
@@ -68,11 +69,19 @@ class IRadientMeshVertexData : public IRadientAsset
6869
{
6970
};
7071

72+
// Opaque handle to shared mesh morph-target data. The implementation details
73+
// are private to RadientMeshAssetManager.
74+
class IRadientMeshMorphTargetData : public IRadientAsset
75+
{
76+
};
77+
7178
struct RadientMeshGeometryData
7279
{
73-
/// Vertex and index data that together form one drawable geometry.
74-
IRadientMeshVertexData* pVertexData = nullptr;
75-
IRadientMeshIndexData* pIndexData = nullptr;
80+
/// Vertex, index, and optional morph-target data that together form one
81+
/// drawable geometry. Morph-target deltas use the vertex data's domain.
82+
IRadientMeshVertexData* pVertexData = nullptr;
83+
IRadientMeshIndexData* pIndexData = nullptr;
84+
IRadientMeshMorphTargetData* pMorphTargetData = nullptr;
7685
};
7786

7887
class RadientMeshAssetManager final : public std::enable_shared_from_this<RadientMeshAssetManager>
@@ -101,15 +110,18 @@ class RadientMeshAssetManager final : public std::enable_shared_from_this<Radien
101110
std::unique_ptr<RadientMeshVertexSource> pVertexSource,
102111
IRadientMeshVertexData** ppVertexData);
103112

113+
RADIENT_STATUS CreateMeshMorphTargetData(IThreadPool& ThreadPool,
114+
std::unique_ptr<RadientMorphTargetSource> pMorphTargetSource,
115+
IRadientMeshMorphTargetData** ppMorphTargetData);
116+
104117
// Creates a mesh handle and schedules mesh-view payload creation. When
105118
// vertex or index data is still loading, the view task depends on the
106119
// corresponding data tasks if they are still available.
107-
RADIENT_STATUS CreateMeshView(IThreadPool& ThreadPool,
108-
const RadientMeshGeometryData* pGeometryData,
109-
Uint32 GeometryCount,
110-
const RadientMeshViewCreateInfo& ViewCI,
111-
IRadientMeshAsset** ppMesh,
112-
std::unique_ptr<RadientMorphTargetData> pMorphTargetData = nullptr);
120+
RADIENT_STATUS CreateMeshView(IThreadPool& ThreadPool,
121+
const RadientMeshGeometryData* pGeometryData,
122+
Uint32 GeometryCount,
123+
const RadientMeshViewCreateInfo& ViewCI,
124+
IRadientMeshAsset** ppMesh);
113125

114126
// Returns drawable mesh data when the mesh asset is ready. A pending status
115127
// means that any mesh dependency may still be unresolved: source/view
@@ -127,18 +139,19 @@ class RadientMeshAssetManager final : public std::enable_shared_from_this<Radien
127139
static RADIENT_STATUS GetLoadStatus(IRadientMeshIndexData* pMeshIndexData);
128140
// Reports CPU-side mesh data readiness for shared mesh vertex data handles.
129141
static RADIENT_STATUS GetLoadStatus(IRadientMeshVertexData* pMeshVertexData);
142+
// Reports CPU-side mesh data readiness for shared morph-target data handles.
143+
static RADIENT_STATUS GetLoadStatus(IRadientMeshMorphTargetData* pMorphTargetData);
130144

131145
// Reports render-resource readiness. This follows GetLoadStatus(), then
132146
// checks geometry GPU resources and material/texture GPU resources.
133-
static RADIENT_STATUS GetGPUResourceStatus(IRadientAsset* pMeshAsset);
134-
static const MeshPayloadImpl* GetMeshPayload(IRadientMeshAsset* pMeshAsset);
135-
static Uint32 GetMeshGeometryCount(IRadientMeshAsset* pMeshAsset);
136-
static const MeshIndexDataPayloadImpl* GetMeshIndexDataPayload(IRadientMeshAsset* pMeshAsset, Uint32 GeometryIndex);
137-
static const MeshVertexDataPayloadImpl* GetMeshVertexDataPayload(IRadientMeshAsset* pMeshAsset, Uint32 GeometryIndex);
138-
static const IRadientMeshIndexData* GetMeshIndexData(IRadientMeshAsset* pMeshAsset);
139-
static const IRadientMeshVertexData* GetMeshVertexData(IRadientMeshAsset* pMeshAsset);
140-
// Returns packed morph deltas for a ready mesh asset.
141-
static const RadientMorphTargetData* GetMorphTargetData(IRadientMeshAsset* pMeshAsset);
147+
static RADIENT_STATUS GetGPUResourceStatus(IRadientAsset* pMeshAsset);
148+
static const MeshPayloadImpl* GetMeshPayload(IRadientMeshAsset* pMeshAsset);
149+
static Uint32 GetMeshGeometryCount(IRadientMeshAsset* pMeshAsset);
150+
static const MeshIndexDataPayloadImpl* GetMeshIndexDataPayload(IRadientMeshAsset* pMeshAsset, Uint32 GeometryIndex);
151+
static const MeshVertexDataPayloadImpl* GetMeshVertexDataPayload(IRadientMeshAsset* pMeshAsset, Uint32 GeometryIndex);
152+
static const MeshMorphTargetDataPayloadImpl* GetMeshMorphTargetDataPayload(IRadientMeshAsset* pMeshAsset, Uint32 GeometryIndex);
153+
static const IRadientMeshIndexData* GetMeshIndexData(IRadientMeshAsset* pMeshAsset);
154+
static const IRadientMeshVertexData* GetMeshVertexData(IRadientMeshAsset* pMeshAsset);
142155

143156
private:
144157
explicit RadientMeshAssetManager(const CreateInfo& CI);
@@ -147,9 +160,10 @@ class RadientMeshAssetManager final : public std::enable_shared_from_this<Radien
147160
RefCntWeakPtr<GLTF::ResourceManager> m_WeakResourceManager;
148161
RefCntWeakPtr<IGPUUploadManager> m_WeakUploadManager;
149162

150-
RadientAssetCache<MeshPayloadImpl> m_MeshCache;
151-
RadientAssetCache<MeshIndexDataPayloadImpl> m_MeshIndexDataCache;
152-
RadientAssetCache<MeshVertexDataPayloadImpl> m_MeshVertexDataCache;
163+
RadientAssetCache<MeshPayloadImpl> m_MeshCache;
164+
RadientAssetCache<MeshIndexDataPayloadImpl> m_MeshIndexDataCache;
165+
RadientAssetCache<MeshVertexDataPayloadImpl> m_MeshVertexDataCache;
166+
RadientAssetCache<MeshMorphTargetDataPayloadImpl> m_MeshMorphTargetDataCache;
153167
};
154168

155169
} // namespace Diligent

‎Radient/include/Assets/RadientMorphTargetData.hpp‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@
3434
namespace Diligent
3535
{
3636

37-
/// Packed immutable morph-target data copied from RadientMeshCreateInfo.
37+
class RadientMorphTargetSource;
38+
39+
/// Packed immutable morph-target metadata stored in a mesh-data payload.
3840
class RadientMorphTargetData final
3941
{
4042
public:
41-
RadientMorphTargetData(const RadientMorphTargetCreateInfo* pTargets,
42-
Uint32 TargetCount,
43-
Uint32 VertexCount);
43+
explicit RadientMorphTargetData(const RadientMorphTargetSource& Source);
4444

4545
RadientMorphTargetData(const RadientMorphTargetData&) = delete;
4646
RadientMorphTargetData& operator=(const RadientMorphTargetData&) = delete;
@@ -50,21 +50,34 @@ class RadientMorphTargetData final
5050
return m_Desc;
5151
}
5252

53-
/// Returns the copied delta stream for one target attribute.
54-
const Float32* GetDeltas(Uint32 TargetIndex, Uint32 AttributeIndex) const noexcept;
53+
Uint32 GetVertexCount() const noexcept
54+
{
55+
return m_VertexCount;
56+
}
57+
58+
Uint32 GetDataSize() const noexcept
59+
{
60+
return m_DataSize;
61+
}
62+
63+
/// Returns the byte offset of one attribute stream in the GPU allocation.
64+
Uint32 GetAttributeDataOffset(Uint32 TargetIndex, Uint32 AttributeIndex) const noexcept;
5565

5666
private:
57-
struct AttributeData
67+
struct AttributeLayout
5868
{
59-
const Float32* pDeltas = nullptr;
69+
Uint32 DataOffset = 0;
6070
};
6171

6272
using PackedMemory = std::unique_ptr<void, STDDeleterRawMem<void>>;
6373

6474
PackedMemory m_Memory;
6575
RadientMeshAssetDesc m_Desc;
66-
const RadientMorphTargetAttributeDesc* m_pAttributes = nullptr;
67-
const AttributeData* m_pAttributeData = nullptr;
76+
const RadientMorphTargetAttributeDesc* m_pAttributes = nullptr;
77+
const AttributeLayout* m_pAttributeLayouts = nullptr;
78+
79+
Uint32 m_VertexCount = 0;
80+
Uint32 m_DataSize = 0;
6881
};
6982

7083
RADIENT_STATUS CreateRadientMorphTargetWeights(IRadientMeshAsset* pMesh,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2026 Diligent Graphics LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* In no event and under no legal theory, whether in tort (including negligence),
17+
* contract, or otherwise, unless required by applicable law (such as deliberate
18+
* and grossly negligent acts) or agreed to in writing, shall any Contributor be
19+
* liable for any damages, including any direct, indirect, special, incidental,
20+
* or consequential damages of any character arising as a result of this License or
21+
* out of the use or inability to use the software (including but not limited to damages
22+
* for loss of goodwill, work stoppage, computer failure or malfunction, or any and
23+
* all other commercial damages or losses), even if such Contributor has been advised
24+
* of the possibility of such damages.
25+
*/
26+
27+
#pragma once
28+
29+
#include "RadientMorphTargets.h"
30+
31+
#include <string>
32+
#include <vector>
33+
34+
namespace Diligent
35+
{
36+
37+
/// Owns CPU-side morph-target source data that is packed into a GPU buffer.
38+
class RadientMorphTargetSource final
39+
{
40+
public:
41+
struct Attribute
42+
{
43+
std::string Semantic;
44+
Uint32 ComponentCount = 0;
45+
Uint32 DataOffset = 0;
46+
};
47+
48+
struct Target
49+
{
50+
std::string Name;
51+
Float32 DefaultWeight = 0;
52+
std::vector<Attribute> Attributes;
53+
};
54+
55+
struct PackDestination
56+
{
57+
void* pData = nullptr;
58+
Uint32 DataSize = 0;
59+
};
60+
61+
RadientMorphTargetSource(const RadientMorphTargetCreateInfo* pTargets,
62+
Uint32 TargetCount,
63+
Uint32 VertexCount);
64+
65+
// clang-format off
66+
RadientMorphTargetSource(const RadientMorphTargetSource&) = delete;
67+
RadientMorphTargetSource(RadientMorphTargetSource&&) = delete;
68+
RadientMorphTargetSource& operator=(const RadientMorphTargetSource&) = delete;
69+
RadientMorphTargetSource& operator=(RadientMorphTargetSource&&) = delete;
70+
// clang-format on
71+
72+
RADIENT_STATUS GetStatus() const noexcept
73+
{
74+
return m_Status;
75+
}
76+
77+
Uint32 GetVertexCount() const noexcept
78+
{
79+
return m_VertexCount;
80+
}
81+
82+
Uint32 GetTargetCount() const noexcept
83+
{
84+
return static_cast<Uint32>(m_Targets.size());
85+
}
86+
87+
const Target& GetTarget(Uint32 TargetIndex) const noexcept
88+
{
89+
return m_Targets[TargetIndex];
90+
}
91+
92+
Uint32 GetDataSize() const noexcept
93+
{
94+
return static_cast<Uint32>(m_Deltas.size() * sizeof(Float32));
95+
}
96+
97+
/// Returns a key for the packed morph-target description and GPU data.
98+
std::string MakeCacheKey() const;
99+
100+
RADIENT_STATUS PackData(PackDestination Destination) const noexcept;
101+
102+
private:
103+
RADIENT_STATUS m_Status = RADIENT_STATUS_INVALID_ARGUMENT;
104+
Uint32 m_VertexCount = 0;
105+
std::vector<Target> m_Targets;
106+
std::vector<Float32> m_Deltas;
107+
};
108+
109+
} // namespace Diligent

‎Radient/include/Render/RadientDrawableMesh.hpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
namespace Diligent
3636
{
3737

38+
class RadientMorphTargetData;
3839
struct IRadientMaterialAsset;
3940

4041
struct RadientDrawableMeshPrimitive
@@ -53,10 +54,18 @@ struct RadientDrawableMeshGeometry
5354
{
5455
IVertexPool* pVertexPool = nullptr;
5556

57+
/// Immutable layout of this geometry's morph-target streams, or null when
58+
/// the geometry has no morph targets.
59+
const RadientMorphTargetData* pMorphTargetData = nullptr;
60+
5661
PBR_Renderer::PSO_FLAGS VertexAttribFlags = PBR_Renderer::PSO_FLAG_NONE;
5762

5863
Uint32 FirstIndexLocation = 0;
5964
Uint32 BaseVertex = 0;
65+
66+
/// Byte offset of the geometry's packed deltas in the shared morph-target
67+
/// buffer. Valid when pMorphTargetData is non-null and contains GPU data.
68+
Uint32 MorphTargetDataOffset = 0;
6069
};
6170

6271
/// Resolved mesh data needed to expand one scene renderable into drawable primitive slots.

‎Radient/interface/RadientAssets.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,9 @@ DILIGENT_END_INTERFACE
518518
/// Immutable mesh asset and its morph-target schema.
519519
DILIGENT_BEGIN_INTERFACE(IRadientMeshAsset, IRadientAsset)
520520
{
521-
/// Returns the immutable mesh description. The returned reference and all
522-
/// data it references remain valid while the mesh asset is retained.
521+
/// Returns the immutable mesh description. The description is empty until
522+
/// mesh loading completes successfully. The returned reference and all data
523+
/// it references remain valid while the mesh asset is retained.
523524
VIRTUAL const RadientMeshAssetDesc REF METHOD(GetDesc)(THIS) CONST PURE;
524525

525526
/// Creates mutable weights initialized to the mesh morph-target defaults.

‎Radient/interface/RadientMorphTargets.h‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ struct RadientMorphTargetAttributeCreateInfo
6767
{
6868
/// Tightly packed canonical floating-point deltas. The array contains
6969
/// RadientMeshCreateInfo::VertexCount times the corresponding attribute
70-
/// description's ComponentCount values. Radient copies the data during
71-
/// mesh creation.
70+
/// description's ComponentCount values. Radient copies the data into
71+
/// transient upload storage during mesh creation and does not retain the
72+
/// deltas in CPU memory after the upload has been scheduled.
7273
const Float32* pDeltas DEFAULT_INITIALIZER(nullptr);
7374
};
7475
typedef struct RadientMorphTargetAttributeCreateInfo RadientMorphTargetAttributeCreateInfo;
@@ -100,8 +101,9 @@ typedef struct RadientMorphTargetDesc RadientMorphTargetDesc;
100101

101102
/// Morph-target creation attributes.
102103
///
103-
/// Radient copies Desc, all metadata it references, and the attribute creation
104-
/// data during mesh creation.
104+
/// Radient retains a copy of Desc and all metadata it references. Attribute
105+
/// creation data is copied only into transient upload storage and is not kept
106+
/// in CPU memory after the GPU upload has been scheduled.
105107
struct RadientMorphTargetCreateInfo
106108
{
107109
/// Complete immutable description of the target to create.

‎Radient/src/Assets/RadientAssetManagerImpl.cpp‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ namespace Diligent
5555
namespace
5656
{
5757

58-
constexpr Uint64 RadientDefaultIndexBufferSize = 16ull * 1024ull * 1024ull;
59-
constexpr Uint64 RadientDefaultMaxIndexBufferSize = 256ull * 1024ull * 1024ull;
60-
constexpr Uint32 RadientDefaultVertexPoolSize = 1024u * 1024u;
61-
constexpr Uint32 RadientDefaultTextureAtlasSize = 2048u;
62-
constexpr Uint32 RadientDefaultTextureAtlasSlices = 1u;
63-
constexpr Uint32 RadientDefaultTextureAtlasMaxSlices = 2048u;
58+
constexpr Uint64 RadientDefaultIndexBufferSize = 16ull * 1024ull * 1024ull;
59+
constexpr Uint64 RadientDefaultMaxIndexBufferSize = 256ull * 1024ull * 1024ull;
60+
constexpr Uint64 RadientDefaultMorphTargetBufferSize = 1024ull * 1024ull;
61+
constexpr Uint64 RadientDefaultMaxMorphTargetBufferSize = 256ull * 1024ull * 1024ull;
62+
constexpr Uint32 RadientDefaultVertexPoolSize = 1024u * 1024u;
63+
constexpr Uint32 RadientDefaultTextureAtlasSize = 2048u;
64+
constexpr Uint32 RadientDefaultTextureAtlasSlices = 1u;
65+
constexpr Uint32 RadientDefaultTextureAtlasMaxSlices = 2048u;
6466

6567
static constexpr INTERFACE_ID IID_SceneAssetImpl = {0xb59806f1, 0xa08a, 0x4dff, {0xb0, 0x37, 0x84, 0x75, 0xd6, 0xfd, 0x7f, 0x1b}};
6668

@@ -207,6 +209,15 @@ GLTF::ResourceManager::CreateInfo CreateResourceManagerInfo()
207209
CreateInfo.IndexAllocatorCI.ExpansionSize = static_cast<Uint32>(RadientDefaultIndexBufferSize);
208210
CreateInfo.IndexAllocatorCI.MaxSize = RadientDefaultMaxIndexBufferSize;
209211

212+
CreateInfo.MorphTargetAllocatorCI.Desc.Name = "Radient morph target buffer";
213+
CreateInfo.MorphTargetAllocatorCI.Desc.Size = RadientDefaultMorphTargetBufferSize;
214+
CreateInfo.MorphTargetAllocatorCI.Desc.Usage = USAGE_DEFAULT;
215+
CreateInfo.MorphTargetAllocatorCI.Desc.BindFlags = BIND_SHADER_RESOURCE;
216+
CreateInfo.MorphTargetAllocatorCI.Desc.Mode = BUFFER_MODE_STRUCTURED;
217+
CreateInfo.MorphTargetAllocatorCI.Desc.ElementByteStride = sizeof(Float32);
218+
CreateInfo.MorphTargetAllocatorCI.ExpansionSize = static_cast<Uint32>(RadientDefaultMorphTargetBufferSize);
219+
CreateInfo.MorphTargetAllocatorCI.MaxSize = RadientDefaultMaxMorphTargetBufferSize;
220+
210221
CreateInfo.DefaultPoolDesc.Name = "Radient vertex pool";
211222
CreateInfo.DefaultPoolDesc.VertexCount = RadientDefaultVertexPoolSize;
212223
CreateInfo.DefaultPoolDesc.Usage = USAGE_DEFAULT;

0 commit comments

Comments
 (0)