|
| 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