Skip to content

Commit 590dcbe

Browse files
committed
WIP: Implemented GPU Material Scene as the next stable GPUScene layer
- Added RenderMaterialID, GPUMaterialData, and MaterialScene CPU registry. - GPUSceneInstanceData.Metadata.z now carries a real render material row ID, not a submesh material slot. - SceneRenderer now uploads a GPUMaterials SSBO and remaps transient override/debug materials into the same uploaded material table. - Added shader-side material table access in MaterialScene.glslh. - Added material debug views: texture validity, alpha mode, roughness, metalness, and missing material. - Extended Renderer Debugger GPUScene stats with material row counts, dirty material counts, invalid material IDs, missing materials, and missing textures.
1 parent 1af18f9 commit 590dcbe

14 files changed

Lines changed: 1220 additions & 14 deletions

Core/Source/Lux/Renderer/GPUScene.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Lux/Core/Base.h"
44
#include "Lux/Core/UUID.h"
5+
#include "Lux/Renderer/MaterialScene.h"
56

67
#include <glm/glm.hpp>
78

@@ -74,7 +75,7 @@ namespace Lux {
7475
glm::vec4 TransformRows[3] = {};
7576
glm::vec4 PreviousTransformRows[3] = {};
7677
glm::vec4 BoundsSphere = glm::vec4(0.0f);
77-
glm::uvec4 Metadata = glm::uvec4(0); // x = primitive ID, y = submesh index, z = material slot, w = flags
78+
glm::uvec4 Metadata = glm::uvec4(0); // x = primitive ID, y = submesh index, z = RenderMaterialID, w = flags
7879
glm::uvec4 ObjectData = glm::uvec4(0); // x/y = entity UUID low/high, z = GPUScene instance ID, w = reserved
7980
};
8081

@@ -93,6 +94,11 @@ namespace Lux {
9394
return data.Metadata.z;
9495
}
9596

97+
inline RenderMaterialID GetGPUSceneRenderMaterialID(const GPUSceneInstanceData& data)
98+
{
99+
return data.Metadata.z;
100+
}
101+
96102
inline uint32_t GetGPUSceneInstanceFlags(const GPUSceneInstanceData& data)
97103
{
98104
return data.Metadata.w;
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#include "lpch.h"
2+
3+
#include "Lux/Renderer/MaterialScene.h"
4+
5+
#include "Lux/Asset/AssetManager.h"
6+
#include "Lux/Project/Project.h"
7+
8+
#include <algorithm>
9+
#include <cstring>
10+
11+
namespace Lux {
12+
13+
namespace
14+
{
15+
static const std::string s_AlbedoColorUniform = "u_MaterialUniforms.AlbedoColor";
16+
static const std::string s_UseNormalMapUniform = "u_MaterialUniforms.UseNormalMap";
17+
static const std::string s_MetalnessUniform = "u_MaterialUniforms.Metalness";
18+
static const std::string s_RoughnessUniform = "u_MaterialUniforms.Roughness";
19+
static const std::string s_EmissionUniform = "u_MaterialUniforms.Emission";
20+
static const std::string s_TransparencyUniform = "u_MaterialUniforms.Transparency";
21+
static const std::string s_MaterialComplexityScoreUniform = "u_MaterialUniforms.MaterialComplexityScore";
22+
23+
bool MaterialDataEquals(const GPUMaterialData& lhs, const GPUMaterialData& rhs)
24+
{
25+
return std::memcmp(&lhs, &rhs, sizeof(GPUMaterialData)) == 0;
26+
}
27+
28+
glm::vec3 ToLinearColor(glm::vec3 color)
29+
{
30+
color = glm::clamp(color, glm::vec3(0.0f), glm::vec3(1.0f));
31+
return glm::pow(color, glm::vec3(2.2f));
32+
}
33+
34+
float ReadMaterialFloat(Ref<Material> material, const std::string& name, float fallback)
35+
{
36+
if (!material || !material->FindUniformDeclaration(name))
37+
return fallback;
38+
39+
return material->GetFloat(name);
40+
}
41+
42+
bool ReadMaterialBool(Ref<Material> material, const std::string& name, bool fallback)
43+
{
44+
if (!material || !material->FindUniformDeclaration(name))
45+
return fallback;
46+
47+
return material->GetBool(name);
48+
}
49+
50+
glm::vec3 ReadMaterialVec3(Ref<Material> material, const std::string& name, const glm::vec3& fallback)
51+
{
52+
if (!material || !material->FindUniformDeclaration(name))
53+
return fallback;
54+
55+
return material->GetVector3(name);
56+
}
57+
58+
bool IsTextureHandleValid(AssetHandle textureHandle)
59+
{
60+
if (!textureHandle || !Project::GetAssetManager())
61+
return false;
62+
63+
return AssetManager::IsAssetHandleValid(textureHandle);
64+
}
65+
}
66+
67+
MaterialScene::MaterialScene()
68+
{
69+
EnsureFallbackMaterial();
70+
}
71+
72+
GPUMaterialData MaterialScene::GetFallbackMaterialData()
73+
{
74+
GPUMaterialData data;
75+
data.BaseColor = glm::vec4(1.0f);
76+
data.Scalars = glm::vec4(0.0f, 0.5f, 0.0f, 0.0f);
77+
data.TextureIndices = glm::uvec4(InvalidGPUTextureIndex);
78+
data.Metadata = glm::uvec4((uint32_t)(GPUMaterialFlags::Valid | GPUMaterialFlags::Missing), (uint32_t)GPUMaterialAlphaMode::Opaque, InvalidRenderMaterialID, 0);
79+
return data;
80+
}
81+
82+
GPUMaterialData MaterialScene::BuildGPUMaterialData(
83+
const GPUMaterialBuildInput& input,
84+
const std::function<GPUTextureIndex(AssetHandle)>& resolveTextureIndex)
85+
{
86+
GPUMaterialData data = GetFallbackMaterialData();
87+
88+
Ref<MaterialAsset> materialAsset = input.MaterialAsset;
89+
if (!materialAsset && input.MaterialHandle && Project::GetAssetManager())
90+
materialAsset = AssetManager::GetAsset<MaterialAsset>(input.MaterialHandle);
91+
92+
Ref<Material> material = input.OverrideMaterial;
93+
if (!material && materialAsset)
94+
material = materialAsset->GetMaterial();
95+
96+
if (!material)
97+
{
98+
data.Metadata.x = (uint32_t)GPUMaterialFlags::Missing;
99+
return data;
100+
}
101+
102+
const bool transparent = input.Transparent || (materialAsset && materialAsset->IsTransparent());
103+
const bool shadowCasting = materialAsset ? materialAsset->IsShadowCasting() : !material->GetFlag(MaterialFlag::DisableShadowCasting);
104+
const bool twoSided = material->GetFlag(MaterialFlag::TwoSided);
105+
106+
const glm::vec3 albedoColor = ReadMaterialVec3(material, s_AlbedoColorUniform, glm::vec3(1.0f));
107+
const float metalness = transparent ? 0.0f : glm::clamp(ReadMaterialFloat(material, s_MetalnessUniform, 0.0f), 0.0f, 1.0f);
108+
const float roughness = glm::clamp(ReadMaterialFloat(material, s_RoughnessUniform, transparent ? 0.5f : 0.4f), 0.0f, 1.0f);
109+
const float emission = glm::max(ReadMaterialFloat(material, s_EmissionUniform, 0.0f), 0.0f);
110+
const float opacity = transparent ? glm::clamp(ReadMaterialFloat(material, s_TransparencyUniform, 1.0f), 0.0f, 1.0f) : 1.0f;
111+
const float complexity = glm::max(ReadMaterialFloat(material, s_MaterialComplexityScoreUniform, transparent ? 5.0f : 3.0f), 0.0f);
112+
113+
data.BaseColor = glm::vec4(ToLinearColor(albedoColor), opacity);
114+
data.Scalars = glm::vec4(metalness, roughness, emission, complexity);
115+
data.TextureIndices = glm::uvec4(InvalidGPUTextureIndex);
116+
117+
GPUMaterialFlags flags = GPUMaterialFlags::Valid;
118+
if (input.OverrideMaterial)
119+
flags |= GPUMaterialFlags::OverrideMaterial;
120+
if (transparent)
121+
flags |= GPUMaterialFlags::Transparent;
122+
if (shadowCasting)
123+
flags |= GPUMaterialFlags::ShadowCasting;
124+
if (twoSided)
125+
flags |= GPUMaterialFlags::TwoSided;
126+
127+
auto assignTexture = [&](AssetHandle textureHandle, GPUMaterialFlags presentFlag, uint32_t textureSlot)
128+
{
129+
if (!textureHandle)
130+
return;
131+
132+
if (!IsTextureHandleValid(textureHandle))
133+
{
134+
flags |= GPUMaterialFlags::MissingTexture;
135+
return;
136+
}
137+
138+
flags |= presentFlag;
139+
data.TextureIndices[textureSlot] = resolveTextureIndex ? resolveTextureIndex(textureHandle) : InvalidGPUTextureIndex;
140+
};
141+
142+
if (materialAsset)
143+
{
144+
assignTexture(materialAsset->GetAlbedoMapHandle(), GPUMaterialFlags::HasAlbedoTexture, 0);
145+
146+
const bool useNormalMap = !transparent
147+
&& ReadMaterialBool(material, s_UseNormalMapUniform, false)
148+
&& materialAsset->GetNormalMapHandle();
149+
if (useNormalMap)
150+
flags |= GPUMaterialFlags::UseNormalMap;
151+
assignTexture(useNormalMap ? materialAsset->GetNormalMapHandle() : AssetHandle(0), GPUMaterialFlags::HasNormalTexture, 1);
152+
assignTexture(!transparent ? materialAsset->GetMetalnessMapHandle() : AssetHandle(0), GPUMaterialFlags::HasMetalnessTexture, 2);
153+
assignTexture(!transparent ? materialAsset->GetRoughnessMapHandle() : AssetHandle(0), GPUMaterialFlags::HasRoughnessTexture, 3);
154+
}
155+
156+
data.Metadata = glm::uvec4(
157+
(uint32_t)flags,
158+
(uint32_t)(transparent ? GPUMaterialAlphaMode::Blend : GPUMaterialAlphaMode::Opaque),
159+
InvalidRenderMaterialID,
160+
0);
161+
return data;
162+
}
163+
164+
void MaterialScene::EnsureFallbackMaterial()
165+
{
166+
if (!m_Materials.empty())
167+
return;
168+
169+
m_Materials.push_back(GetFallbackMaterialData());
170+
m_MaterialKeys.push_back({});
171+
m_LastTouchedFrames.push_back(m_FrameIndex);
172+
}
173+
174+
void MaterialScene::BeginSync(uint32_t frameIndex)
175+
{
176+
m_FrameIndex = frameIndex;
177+
m_DirtyMaterialCount = 0;
178+
m_DirtyMaterialIDs.clear();
179+
m_DirtyRanges.clear();
180+
EnsureFallbackMaterial();
181+
m_LastTouchedFrames[InvalidRenderMaterialID] = m_FrameIndex;
182+
}
183+
184+
RenderMaterialID MaterialScene::UpsertMaterial(AssetHandle materialHandle, bool forceDirty)
185+
{
186+
if (!materialHandle)
187+
return InvalidRenderMaterialID;
188+
189+
GPUMaterialBuildInput input;
190+
input.MaterialHandle = materialHandle;
191+
if (Project::GetAssetManager())
192+
input.MaterialAsset = AssetManager::GetAsset<MaterialAsset>(materialHandle);
193+
194+
GPUMaterialData data = BuildGPUMaterialData(input, [this](AssetHandle textureHandle) { return ResolveTextureIndex(textureHandle); });
195+
return UpsertMaterial({ (uint64_t)materialHandle, MaterialKeyType::Asset }, data, forceDirty);
196+
}
197+
198+
RenderMaterialID MaterialScene::UpsertOverrideMaterial(uint64_t overrideKey, const Ref<Material>& material, bool transparent, bool forceDirty)
199+
{
200+
if (!overrideKey || !material)
201+
return InvalidRenderMaterialID;
202+
203+
GPUMaterialBuildInput input;
204+
input.OverrideMaterial = material;
205+
input.Transparent = transparent;
206+
GPUMaterialData data = BuildGPUMaterialData(input, [this](AssetHandle textureHandle) { return ResolveTextureIndex(textureHandle); });
207+
return UpsertMaterial({ overrideKey, MaterialKeyType::Override }, data, forceDirty);
208+
}
209+
210+
RenderMaterialID MaterialScene::UpsertMaterial(MaterialKey key, GPUMaterialData data, bool forceDirty)
211+
{
212+
if (!key.SourceID)
213+
return InvalidRenderMaterialID;
214+
215+
auto idIt = m_MaterialIDByKey.find(key);
216+
RenderMaterialID materialID = InvalidRenderMaterialID;
217+
bool created = false;
218+
if (idIt == m_MaterialIDByKey.end())
219+
{
220+
created = true;
221+
if (!m_FreeMaterialIDs.empty())
222+
{
223+
materialID = m_FreeMaterialIDs.back();
224+
m_FreeMaterialIDs.pop_back();
225+
}
226+
else
227+
{
228+
materialID = (RenderMaterialID)m_Materials.size();
229+
m_Materials.emplace_back();
230+
m_MaterialKeys.emplace_back();
231+
m_LastTouchedFrames.emplace_back(0);
232+
}
233+
234+
m_MaterialIDByKey[key] = materialID;
235+
m_MaterialKeys[materialID] = key;
236+
}
237+
else
238+
{
239+
materialID = idIt->second;
240+
}
241+
242+
data.Metadata.z = materialID;
243+
m_LastTouchedFrames[materialID] = m_FrameIndex;
244+
245+
if (created || forceDirty || !MaterialDataEquals(m_Materials[materialID], data))
246+
{
247+
m_Materials[materialID] = data;
248+
MarkMaterialDirty(materialID);
249+
}
250+
251+
return materialID;
252+
}
253+
254+
GPUTextureIndex MaterialScene::ResolveTextureIndex(AssetHandle textureHandle)
255+
{
256+
if (!textureHandle)
257+
return InvalidGPUTextureIndex;
258+
259+
auto [it, inserted] = m_TextureIndexByHandle.try_emplace(textureHandle, InvalidGPUTextureIndex);
260+
if (inserted || it->second == InvalidGPUTextureIndex)
261+
it->second = m_NextTextureIndex++;
262+
263+
return it->second;
264+
}
265+
266+
void MaterialScene::EndSync()
267+
{
268+
for (RenderMaterialID materialID = 1; materialID < m_Materials.size(); materialID++)
269+
{
270+
if (m_LastTouchedFrames[materialID] == m_FrameIndex)
271+
continue;
272+
273+
const MaterialKey key = m_MaterialKeys[materialID];
274+
if (!key.SourceID)
275+
continue;
276+
277+
m_MaterialIDByKey.erase(key);
278+
m_MaterialKeys[materialID] = {};
279+
m_LastTouchedFrames[materialID] = 0;
280+
m_Materials[materialID] = GetFallbackMaterialData();
281+
m_Materials[materialID].Metadata.z = materialID;
282+
m_FreeMaterialIDs.push_back(materialID);
283+
MarkMaterialDirty(materialID);
284+
}
285+
286+
if (m_DirtyMaterialIDs.empty())
287+
return;
288+
289+
std::sort(m_DirtyMaterialIDs.begin(), m_DirtyMaterialIDs.end());
290+
m_DirtyMaterialIDs.erase(std::unique(m_DirtyMaterialIDs.begin(), m_DirtyMaterialIDs.end()), m_DirtyMaterialIDs.end());
291+
m_DirtyMaterialCount = (uint32_t)m_DirtyMaterialIDs.size();
292+
293+
MaterialSceneDirtyRange range;
294+
range.FirstMaterial = m_DirtyMaterialIDs.front();
295+
range.MaterialCount = 1;
296+
uint32_t previousMaterialID = range.FirstMaterial;
297+
298+
for (size_t index = 1; index < m_DirtyMaterialIDs.size(); index++)
299+
{
300+
const uint32_t materialID = m_DirtyMaterialIDs[index];
301+
if (materialID == previousMaterialID + 1)
302+
{
303+
range.MaterialCount++;
304+
}
305+
else
306+
{
307+
m_DirtyRanges.push_back(range);
308+
range.FirstMaterial = materialID;
309+
range.MaterialCount = 1;
310+
}
311+
312+
previousMaterialID = materialID;
313+
}
314+
315+
m_DirtyRanges.push_back(range);
316+
}
317+
318+
void MaterialScene::Clear()
319+
{
320+
m_FrameIndex = 0;
321+
m_DirtyMaterialCount = 0;
322+
m_NextTextureIndex = 1;
323+
m_Materials.clear();
324+
m_MaterialKeys.clear();
325+
m_LastTouchedFrames.clear();
326+
m_FreeMaterialIDs.clear();
327+
m_MaterialIDByKey.clear();
328+
m_TextureIndexByHandle.clear();
329+
m_DirtyMaterialIDs.clear();
330+
m_DirtyRanges.clear();
331+
EnsureFallbackMaterial();
332+
}
333+
334+
void MaterialScene::MarkMaterialDirty(RenderMaterialID materialID)
335+
{
336+
if (materialID >= m_Materials.size())
337+
return;
338+
339+
m_DirtyMaterialIDs.push_back(materialID);
340+
}
341+
342+
}

0 commit comments

Comments
 (0)