From d51fc375d242952c45161eb57dfc54745cac5ef7 Mon Sep 17 00:00:00 2001 From: Anish Bhobe Date: Wed, 2 Jul 2025 03:28:19 +0200 Subject: [PATCH] Punctual PBR lighting. --- Assets/Shaders/Material.slang | 63 +++ Assets/Shaders/Mesh.slang | 217 +++++++--- Assets/Shaders/PBR.slang | 107 +++++ Blaze.sln.DotSettings | 4 +- Blaze.vcxproj | 19 +- Blaze.vcxproj.filters | 6 + Blaze/Blaze.cpp | 16 +- Blaze/EntityManager.cpp | 5 +- Blaze/MiscData.cpp | 14 +- Blaze/ModelLoader.cpp | 749 +++++++++++++++++++--------------- Blaze/ModelLoader.h | 19 +- Blaze/TextureManager.cpp | 4 +- Blaze/TextureManager.h | 3 +- 13 files changed, 816 insertions(+), 410 deletions(-) create mode 100644 Assets/Shaders/Material.slang create mode 100644 Assets/Shaders/PBR.slang diff --git a/Assets/Shaders/Material.slang b/Assets/Shaders/Material.slang new file mode 100644 index 0000000..3a4d7f7 --- /dev/null +++ b/Assets/Shaders/Material.slang @@ -0,0 +1,63 @@ +import Bindless; + +public struct Material { + float4 baseColor; + float4 emissiveColor; + Sampler2D.RID albedoTextureID; + Sampler2D.RID normalTextureID; + Sampler2D.RID metalRoughTextureID; + Sampler2D.RID emissiveTextureID; + float metallic; + float roughness; + + public float4 getAlbedo(float2 uv, float4 inColor) { + if (let albedoTex = albedoTextureID) { + return baseColor * albedoTex.Sample(uv).rgba; + } + return baseColor; + } + + public float3 getEmissive(float2 uv) { + if (let emissionTex = emissiveTextureID) { + return emissionTex.Sample(uv).rgb * emissiveColor.rgb * emissiveColor.a; + } + return emissiveColor.rgb * emissiveColor.a; + } + + public float3 getNormal(float3 position, float3 normal, float4 tangent, float2 uv) { + float3 N = normalize(normal.xyz); + + if (let normalTex = normalTextureID) { + let vNt = normalize(2.0f * normalTex.Sample(uv).rgb - 1.0f); + + float3 T; + float3 B; + + if (tangent.w == 0.0f) { + float3 q1 = ddx(position); + float3 q2 = ddy(position); + float2 st1 = ddx(uv); + float2 st2 = ddy(uv); + + float det = (st1.x * st2.y - st2.x * st1.y); + + T = -(q1 * st2.y - q2 * st1.y) / det; + T = T - N * dot(N, T); + B = normalize(cross(N, T)); + } else { + T = normalize(tangent.xyz); + B = tangent.w * cross(N, T); + } + N = normalize(T * vNt.x + B * vNt.y + N * vNt.z); + } + + return N; + } + + public float2 getMetalRough(float2 uv) { + if (let metalRoughTex = metalRoughTextureID) { + return metalRoughTex.Sample(uv).bg * float2(metallic, roughness); + } + return float2(metallic, roughness); + } +} \ No newline at end of file diff --git a/Assets/Shaders/Mesh.slang b/Assets/Shaders/Mesh.slang index 427147b..e3ac5b6 100644 --- a/Assets/Shaders/Mesh.slang +++ b/Assets/Shaders/Mesh.slang @@ -1,12 +1,15 @@ import Bindless; +import Material; +import PBR; struct VertexOut { + float4 worldPosition : POSITION; + float3 normal : NORMAL; + float4 tangent : TANGENT; + float2 texCoord0 : TEXCOORD0; + float2 texCoord1 : TEXCOORD1; + float4 vertexColor0 : COLOR0; float4 outPosition : SV_Position; - float4 worldPosition : WorldPosition; - float4 normal : WorldNormal; - float2 texCoord0 : TexCoord0; - float2 texCoord1 : TexCoord1; - float4 vertexColor0 : VertexColor; }; struct CameraData { @@ -15,13 +18,6 @@ struct CameraData { float4 position; }; -struct PointLight { - float3 position; - float range; - float3 color; - float attenuation; -}; - struct DirectionalLight { float3 direction; float _padding0; @@ -55,11 +51,8 @@ uniform ParameterBlock pfd; struct PerInstanceData { float4x4 transform; - Sampler2D.RID textureID; - uint _padding; - float metallic; - float roughness; - float4 baseColor; + float4x4 invTransform; + Material material; } [[vk::push_constant]] @@ -70,6 +63,7 @@ VertexOut VertexMain( uint vertexId: SV_VertexID, float3 position, float3 normal, + float4 tangent, float2 texCoord0, float2 texCoord1, float4 vertexColor0, @@ -79,49 +73,164 @@ VertexOut VertexMain( VertexOut output; output.outPosition = mul(pfd.camera.proj, mul(pfd.camera.view, worldPosition)); output.worldPosition = worldPosition; - output.normal = mul(pcb.transform, float4(normalize(normal.rgb), 0.0f)); + output.normal = normalize(mul(float4(normal.xyz, 0.0f), pcb.invTransform).xyz); + if (tangent.w == 0.0f) { + output.tangent = 0.0f.xxxx; + } else { + output.tangent = float4(normalize(mul(float4(tangent.xyz, 0.0f), pcb.invTransform).xyz), tangent.w); + } output.texCoord0 = texCoord0; output.texCoord1 = texCoord1; output.vertexColor0 = vertexColor0; return output; } -[shader("fragment")] -float4 FragmentMain( - float4 worldPosition : WorldPosition, - float4 normal : WorldNormal, - float2 uv0 : TexCoord0, - float2 uv1 : TexCoord1, - float4 color : VertexColor, -) : SV_Target0 { - float3 diffuse = 0.0f.xxx; - float3 specular = 0.0f.xxx; - - for (uint i = 0; i < pfd.lightData.pointLightCount; ++i) { - PointLight pointlight = pfd.lightData.pointLights[i]; - - let lightPosition = pointlight.position; - let lightDisplace = worldPosition.xyz - lightPosition; - let lightDistance = length(lightDisplace); - let lightDirection = normalize(lightDisplace); - let viewDirection = normalize(worldPosition.xyz - pfd.camera.position.xyz); - let halfWayVector = normalize(-lightDirection + viewDirection); - - let attenuation = (1.0f / lightDistance); - - let diffuseFactor = pcb.roughness * dot(-lightDirection, normalize(normal.xyz)); - diffuse += pointlight.color * diffuseFactor; - - let specularFactor = (1.0f - pcb.roughness) * pow(max(dot(halfWayVector, viewDirection), 0.0f), 32.0f) * attenuation; - - specular += pointlight.color * specularFactor; - } - - if (let texture = pcb.textureID) { - return float4(texture.Sample(uv0).rgb, 1.0f) * pcb.baseColor * color * float4((diffuse + specular), 0.0f); - } else { - return pcb.baseColor * color * float4((diffuse + specular), 0.0f); - } +float TrowbridgeReitzGGX(float3 Normal, float3 Halfway, float Roughness) +{ + float Coeff = Roughness * Roughness; + float Alpha2 = Coeff * Coeff; + float NdotH = max(dot(Normal, Halfway), 0.0f); + float NdotH2 = NdotH * NdotH; + + float Numerator = Alpha2; + float Denominator = NdotH2 * (Alpha2 - 1.0f) + 1.0f; + Denominator = PI * Denominator * Denominator; + + return Numerator / Denominator; +} + +float GeometrySchlickGGX(float NdotV, float Roughness) +{ + float R = Roughness + 1.0f; + float K = (R * R) / 8.0f; + + float Numerator = NdotV; + float Denominator = NdotV * (1.0f - K) + K; + + return Numerator / Denominator; +} + +float GeometrySmith(float NdotV, float NdotL, float Roughness) +{ + float GGX1 = GeometrySchlickGGX(NdotV, Roughness); + float GGX2 = GeometrySchlickGGX(NdotL, Roughness); + + return GGX1 * GGX2; +} + +// https://en.wikipedia.org/wiki/Schlick%27s_approximation +float3 FresnelSchlick(float cosine, float3 F_0) +{ + return F_0 + (1.0f - F_0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +// Sebastian Lagarde +float3 FresnelSchlickRoughness(float cosine, float3 F_0, float Roughness) +{ + return F_0 + (max((1.0f - Roughness).xxx, F_0) - F_0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +float3 GetPBRContrib(float3 Albedo, float3 LightColor, float3 ViewDir, float3 Normal, float Metallic, float Roughness, float3 F_0, float3 LightDir, float LightDistance) +{ + float Attenuation = 1.0f / (LightDistance * LightDistance); // TODO: Controlled Attenuation + float3 Halfway = normalize(ViewDir + LightDir); + + float CosineFactor = max(dot(Halfway, ViewDir), 0.0f); + float NdotV = max(dot(Normal, ViewDir), 0.0f); + float NdotL = max(dot(Normal, LightDir), 0.0f); + + float3 Radiance = LightColor * Attenuation; + + float NormalDistribution = TrowbridgeReitzGGX(Normal, Halfway, Roughness); + float Geometry = GeometrySmith(NdotV, NdotL, Roughness); + float3 Fresnel = FresnelSchlickRoughness(CosineFactor, F_0, Roughness); + + float3 Numerator = (NormalDistribution * Geometry) * Fresnel; + float Denominator = 4.0f * NdotV * NdotL; + float3 Specular = Numerator / (Denominator + 0.00001f); + + float3 K_Specular = Fresnel; + float3 K_Diffuse = 1.0f.xxx - K_Specular; + + K_Diffuse *= 1.0f - Metallic; + + return NdotL * Radiance * (K_Diffuse * Albedo / PI + Specular); +} + +float3 GetPointLightInfluence(float3 Albedo, float2 MetalRough, float3 Position, float3 Normal) +{ + if (pfd.lightData.pointLightCount == 0) + return 0.0f.xxx; + + float3 ViewDir = normalize(pfd.camera.position.xyz - Position); + + float Metallic = MetalRough.r; + float Roughness = MetalRough.g; + + // Dielectric F_0 based on LearnOpenGL. + // TODO: Cite + float3 F_0 = 0.04f.xxx; + F_0 = lerp(F_0, Albedo, Metallic); + + float3 Contrib = 0.0f; + for (uint i = 0; i < pfd.lightData.pointLightCount; ++i) + { + PointLight Light = pfd.lightData.pointLights[i]; + + if (Light.range < 0.0f) + continue; + + float3 LightDir = float3(Light.position) - Position; + float LightDistance = length(LightDir); + + if (LightDistance > Light.range) + continue; + + LightDir /= LightDistance; // Normalization + + // Color Unpack + //float R = (Light.Color & 0xFF000000) >> 24; + //float G = (Light.Color & 0x00FF0000) >> 16; + //float B = (Light.Color & 0x0000FF00) >> 8; + + //float3 LightColor = Light.Intensity * float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 + + Contrib += GetPBRContrib(Albedo, Light.color, ViewDir, Normal, Metallic, Roughness, F_0, LightDir, LightDistance); + } + + return Contrib; +} + +[shader("fragment")] +float4 FragmentMain( + float4 position : POSITION, + float3 normal : NORMAL, + float4 tangent : TANGENT, + float2 texCoord0 : TEXCOORD0, + float2 texCoord1 : TEXCOORD1, + float4 vertexColor0 : COLOR0, +) : SV_Target0 { + + float3 N = pcb.material.getNormal(position.xyz, normal.xyz, tangent, texCoord0); + float2 metalRough = pcb.material.getMetalRough(texCoord0); + + let albedo = pcb.material.getAlbedo(texCoord0, vertexColor0); + let viewDir = normalize(position.xyz - pfd.camera.position.xyz); + + //float3 f_0 = 0.04f.xxx; + //f_0 = lerp(f_0, albedo.rgb, metalRough.x); + + + //float3 contrib = 0.0f.xxx; + //for (uint i = 0; i < pfd.lightData.pointLightCount; ++i) { + // PointLight pointlight = pfd.lightData.pointLights[i]; + + // contrib += pointlight.getInfluence(albedo.rgb, metalRough, viewDir, position.xyz, N, f_0); + //} + + let contrib = GetPointLightInfluence(albedo.rgb, metalRough, position.xyz, N); + + return float4(pcb.material.getEmissive(texCoord0) + contrib, 1.0f); } diff --git a/Assets/Shaders/PBR.slang b/Assets/Shaders/PBR.slang new file mode 100644 index 0000000..daea05f --- /dev/null +++ b/Assets/Shaders/PBR.slang @@ -0,0 +1,107 @@ +public static const float PI = 3.14159265f; + +float TrowbridgeReitzGGX(float3 normal, float3 halfway, float roughness) +{ + float alpha = roughness * roughness; + float alpha2 = alpha * alpha; + float nDotH = max(dot(normal, halfway), 0.0f); + float nDotH2 = nDotH * nDotH; + + float numerator = alpha2; + float denominator = nDotH2 * (alpha2 - 1.0f) + 1.0f; + denominator = PI * denominator * denominator; + + return numerator / denominator; +} + +float GeometrySchlickGGX(float nDotV, float roughness) +{ + float r = roughness + 1.0f; + float k = (r * r) / 8.0f; + + float numerator = nDotV; + float denominator = nDotV * (1.0f - k) + k; + + return numerator / denominator; +} + +float GeometrySmith(float nDotV, float nDotL, float roughness) +{ + float ggx1 = GeometrySchlickGGX(nDotV, roughness); + float ggx2 = GeometrySchlickGGX(nDotL, roughness); + + return ggx1 * ggx2; +} + +// https://en.wikipedia.org/wiki/Schlick%27s_approximation +float3 FresnelSchlick(float cosine, float3 f_0) +{ + return f_0 + (1.0f - f_0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +// Sebastian Lagarde +float3 FresnelSchlickRoughness(float cosine, float3 f_0, float roughness) +{ + return f_0 + (max((1.0f - roughness).xxx, f_0) - f_0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +float3 GetPBRContrib( + float3 albedo, + float3 radiance, + float3 viewDir, + float3 normal, + float3 lightDir, + float2 metalRough, + float3 f_0) +{ + float3 halfway = normalize(viewDir + lightDir); + + float cosineFactor = max(dot(halfway, viewDir), 0.0f); + float nDotV = max(dot(normal, viewDir), 0.0f); + float nDotL = max(dot(normal, lightDir), 0.0f); + + float normalDistribution = TrowbridgeReitzGGX(normal, halfway, metalRough.y); + float geometry = GeometrySmith(nDotV, nDotL, metalRough.y); + float3 fresnel = FresnelSchlickRoughness(cosineFactor, f_0, metalRough.y); + + float3 numerator = (normalDistribution * geometry) * fresnel; + float denominator = 4.0f * nDotV * nDotL; + float3 specular = numerator / (denominator + 0.00001f); + + float3 kSpecular = fresnel; + float3 kDiffuse = 1.0f - kSpecular; + + kDiffuse *= 1.0f - metalRough.x; + + return nDotL * radiance * (kDiffuse * albedo / PI + specular); +} + +public struct PointLight { + public float3 position; + public float range; + public float3 color; + float attenuation; + + public float3 getInfluence(float3 albedo, float2 metalRough, float3 viewDir, float3 worldPosition, float3 normal, float3 f_0) { + + float3 lightDir = float3(position) - worldPosition; + float lightDistance = length(lightDir); + + if (lightDistance > range) + return 0.0f.xxx; + + lightDir /= lightDistance; // Normalization + + // Color Unpack + //float R = (Light.Color & 0xFF000000) >> 24; + //float G = (Light.Color & 0x00FF0000) >> 16; + //float B = (Light.Color & 0x0000FF00) >> 8; + + //float3 LightColor = Light.Intensity * float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 + + let attenuation_ = attenuation / (lightDistance * lightDistance); + let radiance = color * attenuation_; + + return GetPBRContrib(albedo, radiance, viewDir, normal, lightDir, metalRough, f_0); + } +}; diff --git a/Blaze.sln.DotSettings b/Blaze.sln.DotSettings index 1c66e89..6b6b4b5 100644 --- a/Blaze.sln.DotSettings +++ b/Blaze.sln.DotSettings @@ -1,2 +1,4 @@  - <NamingElement Priority="10"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="aa_bb" /></Policy></NamingElement> \ No newline at end of file + <NamingElement Priority="10"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="aa_bb" /></Policy></NamingElement> + <NamingElement Priority="7"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></NamingElement> + <NamingElement Priority="6"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="function parameter" /><type Name="lambda parameter" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></NamingElement> \ No newline at end of file diff --git a/Blaze.vcxproj b/Blaze.vcxproj index e3f73f9..3d8e7cd 100644 --- a/Blaze.vcxproj +++ b/Blaze.vcxproj @@ -125,7 +125,7 @@ slangc %(FullPath) -profile sm_6_6 -target spirv -o %(Filename).spv - Compiling %(Filename).slang + Compiling %(Filename) %(Filename).spv @@ -155,6 +155,15 @@ true %(AdditionalLibraryDirectories) + + slangc %(FullPath) -profile sm_6_6 -target spirv -o %(Filename).spv + + + Compiling %(Filename) + + + %(Filename).spv + @@ -167,7 +176,13 @@ Compiling %(Filename).slang %(Filename).spv - + + Document + slangc %(FullPath) -profile sm_6_6 -target module -o %(Filename).slang-module + %(Filename).slang-module + + + diff --git a/Blaze.vcxproj.filters b/Blaze.vcxproj.filters index 995b499..9846623 100644 --- a/Blaze.vcxproj.filters +++ b/Blaze.vcxproj.filters @@ -48,6 +48,12 @@ Resource Files\Shader Files + + Resource Files\Shader Files + + + Resource Files\Shader Files + diff --git a/Blaze/Blaze.cpp b/Blaze/Blaze.cpp index ac106dd..ac0cb89 100644 --- a/Blaze/Blaze.cpp +++ b/Blaze/Blaze.cpp @@ -57,9 +57,9 @@ SDL_AppResult SDL_AppInit( void** appstate, int, char** ) .attenuation = 1.0f, }, MiscData::PointLight{ - .position = { 0.0f, 12.0f, 0.0f }, + .position = { 0.0f, 3.0f, 0.0f }, .range = 12, - .color = { 0.0f, 1.0f, 0.0f }, + .color = { 12.0f, 12.0f, 12.0f }, .attenuation = 1.0f, }, MiscData::PointLight{ @@ -262,6 +262,16 @@ SDL_AppResult SDL_AppIterate( void* appstate ) vkCmdPushConstants( cmd, misc.pipelineLayout, VK_SHADER_STAGE_ALL_GRAPHICS, 0, sizeof worldTransform, &worldTransform ); + DirectX::XMMATRIX const inverseTransform = XMMatrixInverse( nullptr, worldTransform ); + + vkCmdPushConstants( + cmd, + misc.pipelineLayout, + VK_SHADER_STAGE_ALL_GRAPHICS, + sizeof worldTransform, + sizeof inverseTransform, + &inverseTransform ); + if ( not entity.modelMesh.isNull() ) { ASSERT( current ); @@ -285,7 +295,7 @@ SDL_AppResult SDL_AppIterate( void* appstate ) cmd, misc.pipelineLayout, VK_SHADER_STAGE_ALL_GRAPHICS, - sizeof worldTransform, + 2 * sizeof worldTransform, Material::GPU_DATA_SIZE, materialData ); diff --git a/Blaze/EntityManager.cpp b/Blaze/EntityManager.cpp index d48a4c8..a0498ad 100644 --- a/Blaze/EntityManager.cpp +++ b/Blaze/EntityManager.cpp @@ -132,7 +132,10 @@ void EntityManager::destroyEntity( Entity* entity ) for ( auto& material : entity->model.materials ) { vkDestroySampler( device, Take( material.sampler ), nullptr ); - pRenderDevice->textureManager->freeTexture( std::move( material.texture ) ); + pRenderDevice->textureManager->freeTexture( std::move( material.albedoTextureID ) ); + pRenderDevice->textureManager->freeTexture( std::move( material.normalTextureID ) ); + pRenderDevice->textureManager->freeTexture( std::move( material.metalRoughTextureID ) ); + pRenderDevice->textureManager->freeTexture( std::move( material.emissiveTextureID ) ); } pRenderDevice->bufferManager->freeBuffer( std::move( entity->model.vertexBuffer ) ); diff --git a/Blaze/MiscData.cpp b/Blaze/MiscData.cpp index ca625a2..4f48295 100644 --- a/Blaze/MiscData.cpp +++ b/Blaze/MiscData.cpp @@ -60,7 +60,7 @@ bool MiscData::init( RenderDevice const& renderDevice ) VkPushConstantRange const pushConstantRange = { .stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS, .offset = 0, - .size = sizeof( DirectX::XMMATRIX ) + Material::GPU_DATA_SIZE, + .size = 2 * sizeof( DirectX::XMMATRIX ) + Material::GPU_DATA_SIZE, }; std::array const descriptorSetLayouts = { @@ -123,18 +123,24 @@ bool MiscData::init( RenderDevice const& renderDevice ) VkVertexInputAttributeDescription{ .location = 2, .binding = 0, - .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof( Vertex, texCoord0 ), + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = offsetof( Vertex, tangent ), }, VkVertexInputAttributeDescription{ .location = 3, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof( Vertex, texCoord1 ), + .offset = offsetof( Vertex, texCoord0 ), }, VkVertexInputAttributeDescription{ .location = 4, .binding = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof( Vertex, texCoord1 ), + }, + VkVertexInputAttributeDescription{ + .location = 5, + .binding = 0, .format = VK_FORMAT_R32G32B32A32_SFLOAT, .offset = offsetof( Vertex, color0 ), }, diff --git a/Blaze/ModelLoader.cpp b/Blaze/ModelLoader.cpp index 5ad80e9..92222bc 100644 --- a/Blaze/ModelLoader.cpp +++ b/Blaze/ModelLoader.cpp @@ -16,188 +16,154 @@ #include "MacroUtils.h" #include "MathUtil.h" -// TODO: Cache materials while loading. -uint32_t ProcessMaterial( RenderDevice* renderDevice, Model* model, cgltf_material const& material ) +std::optional LoadTexture( + RenderDevice* renderDevice, VkSampler sampler, cgltf_image const& baseColorImage, bool const linear ) { - ASSERT( material.has_pbr_metallic_roughness ); - - DirectX::XMFLOAT4 const baseColorFactor = DirectX::XMFLOAT4{ material.pbr_metallic_roughness.base_color_factor }; - - VkSampler sampler = nullptr; - TextureID baseColorTexture; - - if ( material.pbr_metallic_roughness.base_color_texture.texture ) + byte* data; + if ( baseColorImage.buffer_view->data ) { - cgltf_image* baseColorImage = material.pbr_metallic_roughness.base_color_texture.texture->image; + data = static_cast( baseColorImage.buffer_view->data ); + } + else + { + data = static_cast( baseColorImage.buffer_view->buffer->data ) + baseColorImage.buffer_view->offset; + } + size_t size = baseColorImage.buffer_view->size; + uint32_t width; + uint32_t height; + uint32_t numChannels = 4; + stbi_uc* textureData; + { + int w; + int h; + int nc; + int requestedChannels = static_cast( numChannels ); + + textureData = stbi_load_from_memory( + reinterpret_cast( data ), static_cast( size ), &w, &h, &nc, requestedChannels ); + ASSERT( nc <= requestedChannels ); + + if ( not textureData ) { - byte* data; - if ( baseColorImage->buffer_view->data ) - { - data = static_cast( baseColorImage->buffer_view->data ); - } - else - { - data = static_cast( baseColorImage->buffer_view->buffer->data ) + baseColorImage->buffer_view->offset; - } - size_t size = baseColorImage->buffer_view->size; + return std::nullopt; + } - uint32_t width; - uint32_t height; - uint32_t numChannels = 4; - stbi_uc* textureData; - { - int w; - int h; - int nc; - int requestedChannels = static_cast( numChannels ); + width = static_cast( w ); + height = static_cast( h ); + } - textureData = stbi_load_from_memory( - reinterpret_cast( data ), static_cast( size ), &w, &h, &nc, requestedChannels ); - ASSERT( nc <= requestedChannels ); + auto textureOpt = renderDevice->textureManager->createTexture( + { width, height, 1 }, sampler, linear ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_SRGB ); + if ( not textureOpt ) + { + return std::nullopt; + } - if ( not textureData ) - { - return UINT32_MAX; - } + TextureID texture = std::move( textureOpt.value() ); + VkImage textureImage = renderDevice->textureManager->fetchImage( texture ).value(); - width = static_cast( w ); - height = static_cast( h ); - } + // Staging Buffer Create + VkBuffer stagingBuffer; + VmaAllocation stagingAllocation; + { + VkBufferCreateInfo const stagingBufferCreateInfo = { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = static_cast( width ) * height * numChannels * sizeof( textureData[0] ), + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; - VkSamplerCreateInfo constexpr samplerCreateInfo = { - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .magFilter = VK_FILTER_LINEAR, - .minFilter = VK_FILTER_LINEAR, - .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, - .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, - .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, - .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, - .mipLodBias = 0.0, - .anisotropyEnable = true, - .maxAnisotropy = 1.0f, - .compareEnable = false, - .compareOp = VK_COMPARE_OP_NEVER, - .minLod = 0.0f, - .maxLod = VK_LOD_CLAMP_NONE, - .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, - .unnormalizedCoordinates = false, - }; + VmaAllocationCreateInfo constexpr stagingAllocationCreateInfo = { + .flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .usage = VMA_MEMORY_USAGE_AUTO, + .requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + .preferredFlags = 0, + .memoryTypeBits = 0, + .pool = nullptr, + .pUserData = nullptr, + .priority = 1.0f, + }; - VK_CHECK( vkCreateSampler( renderDevice->device, &samplerCreateInfo, nullptr, &sampler ) ); + VmaAllocationInfo allocationInfo; - auto textureOpt = renderDevice->textureManager->createTexture( { width, height, 1 }, sampler ); - if ( not textureOpt ) - { - return UINT32_MAX; - } + VK_CHECK( vmaCreateBuffer( + renderDevice->gpuAllocator, + &stagingBufferCreateInfo, + &stagingAllocationCreateInfo, + &stagingBuffer, + &stagingAllocation, + &allocationInfo ) ); - baseColorTexture = std::move( textureOpt.value() ); - VkImage textureImage = renderDevice->textureManager->fetchImage( baseColorTexture ).value(); + if ( allocationInfo.pMappedData ) + { + memcpy( allocationInfo.pMappedData, textureData, stagingBufferCreateInfo.size ); + } + } - // Staging Buffer Create - VkBuffer stagingBuffer; - VmaAllocation stagingAllocation; - { - VkBufferCreateInfo const stagingBufferCreateInfo = { - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .size = static_cast( width ) * height * numChannels * sizeof( textureData[0] ), - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - }; + // All data is copied to stagingBuffer, don't need this. + stbi_image_free( textureData ); - VmaAllocationCreateInfo constexpr stagingAllocationCreateInfo = { - .flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, - .usage = VMA_MEMORY_USAGE_AUTO, - .requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, - .preferredFlags = 0, - .memoryTypeBits = 0, - .pool = nullptr, - .pUserData = nullptr, - .priority = 1.0f, - }; + // Staging -> Texture transfer + { + Frame& frameInUse = renderDevice->frames[renderDevice->frameIndex]; - VmaAllocationInfo allocationInfo; + // This should just pass. + VK_CHECK( vkWaitForFences( renderDevice->device, 1, &frameInUse.frameReadyToReuse, VK_TRUE, INT64_MAX ) ); - VK_CHECK( vmaCreateBuffer( - renderDevice->gpuAllocator, - &stagingBufferCreateInfo, - &stagingAllocationCreateInfo, - &stagingBuffer, - &stagingAllocation, - &allocationInfo ) ); + // Reset Frame + VK_CHECK( vkResetFences( renderDevice->device, 1, &frameInUse.frameReadyToReuse ) ); + VK_CHECK( vkResetCommandPool( renderDevice->device, frameInUse.commandPool, 0 ) ); - if ( allocationInfo.pMappedData ) - { - memcpy( allocationInfo.pMappedData, textureData, stagingBufferCreateInfo.size ); - } - } + VkCommandBufferBeginInfo constexpr beginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = nullptr, + }; - // All data is copied to stagingBuffer, don't need this. - stbi_image_free( textureData ); + uint32_t mipLevels = TextureManager::calculateRequiredMipLevels( width, height, 1 ); - // Staging -> Texture transfer - { - Frame& frameInUse = renderDevice->frames[renderDevice->frameIndex]; + VkImageSubresourceRange const subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = mipLevels, + .baseArrayLayer = 0, + .layerCount = 1, + }; - // This should just pass. - VK_CHECK( vkWaitForFences( renderDevice->device, 1, &frameInUse.frameReadyToReuse, VK_TRUE, INT64_MAX ) ); + VkImageMemoryBarrier2 const creationToTransferImageBarrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, + .srcAccessMask = VK_ACCESS_2_NONE, + .dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = renderDevice->textureManager->fetchImage( texture ).value(), + .subresourceRange = subresourceRange, + }; - // Reset Frame - VK_CHECK( vkResetFences( renderDevice->device, 1, &frameInUse.frameReadyToReuse ) ); - VK_CHECK( vkResetCommandPool( renderDevice->device, frameInUse.commandPool, 0 ) ); + VkDependencyInfo const creationToTransferDependency = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = 0, + .memoryBarrierCount = 0, + .pMemoryBarriers = nullptr, + .bufferMemoryBarrierCount = 0, + .pBufferMemoryBarriers = nullptr, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &creationToTransferImageBarrier, + }; - VkCommandBufferBeginInfo constexpr beginInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - .pInheritanceInfo = nullptr, - }; - - uint32_t mipLevels = TextureManager::calculateRequiredMipLevels( width, height, 1 ); - - VkImageSubresourceRange const subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = mipLevels, - .baseArrayLayer = 0, - .layerCount = 1, - }; - - VkImageMemoryBarrier2 const creationToTransferImageBarrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, - .pNext = nullptr, - .srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, - .srcAccessMask = VK_ACCESS_2_NONE, - .dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, - .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = renderDevice->textureManager->fetchImage( baseColorTexture ).value(), - .subresourceRange = subresourceRange, - }; - - VkDependencyInfo const creationToTransferDependency = { - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .pNext = nullptr, - .dependencyFlags = 0, - .memoryBarrierCount = 0, - .pMemoryBarriers = nullptr, - .bufferMemoryBarrierCount = 0, - .pBufferMemoryBarriers = nullptr, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &creationToTransferImageBarrier, - }; - - std::array transferToReadyImageBarriers{ + std::array transferToReadyImageBarriers{ // transferToReadyImageBarrier VkImageMemoryBarrier2{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, @@ -241,204 +207,309 @@ uint32_t ProcessMaterial( RenderDevice* renderDevice, Model* model, cgltf_materi } }; - VkDependencyInfo const transferToReadyDependency = { - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .pNext = nullptr, - .dependencyFlags = 0, - .memoryBarrierCount = 0, - .pMemoryBarriers = nullptr, - .bufferMemoryBarrierCount = 0, - .pBufferMemoryBarriers = nullptr, - .imageMemoryBarrierCount = static_cast( transferToReadyImageBarriers.size() ), - .pImageMemoryBarriers = transferToReadyImageBarriers.data(), - }; + VkDependencyInfo const transferToReadyDependency = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = 0, + .memoryBarrierCount = 0, + .pMemoryBarriers = nullptr, + .bufferMemoryBarrierCount = 0, + .pBufferMemoryBarriers = nullptr, + .imageMemoryBarrierCount = static_cast( transferToReadyImageBarriers.size() ), + .pImageMemoryBarriers = transferToReadyImageBarriers.data(), + }; - VkImageSubresourceRange const mipLevelSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }; + VkImageSubresourceRange const mipLevelSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; - std::array prepareNextMipLevelBarriers{ - // prepareNextMipLevelSrcImageBarrier - VkImageMemoryBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, - .pNext = nullptr, - .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, - .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, - .dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = textureImage, - .subresourceRange = mipLevelSubresource, - }, - // prepareNextMipLevelDstImageBarrier - VkImageMemoryBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, - .pNext = nullptr, - .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, - .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT, - .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = textureImage, - .subresourceRange = mipLevelSubresource, - } - }; + std::array prepareNextMipLevelBarriers{ + // prepareNextMipLevelSrcImageBarrier + VkImageMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = textureImage, + .subresourceRange = mipLevelSubresource, + }, + // prepareNextMipLevelDstImageBarrier + VkImageMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, + .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_BLIT_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = textureImage, + .subresourceRange = mipLevelSubresource, + } + }; - VkDependencyInfo const prepareNextMipLevelDependency = { - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .pNext = nullptr, - .dependencyFlags = 0, - .memoryBarrierCount = 0, - .pMemoryBarriers = nullptr, - .bufferMemoryBarrierCount = 0, - .pBufferMemoryBarriers = nullptr, - .imageMemoryBarrierCount = static_cast( prepareNextMipLevelBarriers.size() ), - .pImageMemoryBarriers = prepareNextMipLevelBarriers.data(), - }; + VkDependencyInfo const prepareNextMipLevelDependency = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = 0, + .memoryBarrierCount = 0, + .pMemoryBarriers = nullptr, + .bufferMemoryBarrierCount = 0, + .pBufferMemoryBarriers = nullptr, + .imageMemoryBarrierCount = static_cast( prepareNextMipLevelBarriers.size() ), + .pImageMemoryBarriers = prepareNextMipLevelBarriers.data(), + }; - vkBeginCommandBuffer( frameInUse.commandBuffer, &beginInfo ); - { - VkImageSubresourceLayers imageSubresourceLayers = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1, - }; + vkBeginCommandBuffer( frameInUse.commandBuffer, &beginInfo ); + { + VkImageSubresourceLayers imageSubresourceLayers = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }; - // TODO: Ensure `bufferRowLength` and `bufferImageHeight` are not required. - VkBufferImageCopy copyRegion = { - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = imageSubresourceLayers, - .imageOffset = { 0, 0, 0 }, - .imageExtent = { width, height, 1 } - }; + // TODO: Ensure `bufferRowLength` and `bufferImageHeight` are not required. + VkBufferImageCopy copyRegion = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = imageSubresourceLayers, + .imageOffset = { 0, 0, 0 }, + .imageExtent = { width, height, 1 } + }; - // Start - vkCmdPipelineBarrier2( frameInUse.commandBuffer, &creationToTransferDependency ); + // Start + vkCmdPipelineBarrier2( frameInUse.commandBuffer, &creationToTransferDependency ); - // Staging -> Image L0 - vkCmdCopyBufferToImage( - frameInUse.commandBuffer, - stagingBuffer, - textureImage, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - ©Region ); + // Staging -> Image L0 + vkCmdCopyBufferToImage( + frameInUse.commandBuffer, stagingBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region ); - prepareNextMipLevelBarriers[0].subresourceRange.baseMipLevel = 0; - prepareNextMipLevelBarriers[1].subresourceRange.baseMipLevel = 1; + prepareNextMipLevelBarriers[0].subresourceRange.baseMipLevel = 0; + prepareNextMipLevelBarriers[1].subresourceRange.baseMipLevel = 1; - int32_t mipSrcWidth = static_cast( width ); - int32_t mipSrcHeight = static_cast( height ); - int32_t mipDstWidth = std::max( mipSrcWidth / 2, 1 ); - int32_t mipDstHeight = std::max( mipSrcHeight / 2, 1 ); + int32_t mipSrcWidth = static_cast( width ); + int32_t mipSrcHeight = static_cast( height ); + int32_t mipDstWidth = std::max( mipSrcWidth / 2, 1 ); + int32_t mipDstHeight = std::max( mipSrcHeight / 2, 1 ); - VkImageSubresourceLayers constexpr mipSubresourceLayers = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1, - }; + VkImageSubresourceLayers constexpr mipSubresourceLayers = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }; - VkImageBlit2 imageBlit = { - .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2, - .pNext = nullptr, - .srcSubresource = mipSubresourceLayers, - .srcOffsets = { { 0, 0, 0 }, { mipSrcWidth, mipSrcHeight, 1 } }, - .dstSubresource = mipSubresourceLayers, - .dstOffsets = { { 0, 0, 0 }, { mipDstWidth, mipDstHeight, 1 } }, - }; + VkImageBlit2 imageBlit = { + .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2, + .pNext = nullptr, + .srcSubresource = mipSubresourceLayers, + .srcOffsets = { { 0, 0, 0 }, { mipSrcWidth, mipSrcHeight, 1 } }, + .dstSubresource = mipSubresourceLayers, + .dstOffsets = { { 0, 0, 0 }, { mipDstWidth, mipDstHeight, 1 } }, + }; - imageBlit.srcSubresource.mipLevel = 0; - imageBlit.dstSubresource.mipLevel = 1; - imageBlit.srcOffsets[1].x = mipSrcWidth; - imageBlit.srcOffsets[1].y = mipSrcHeight; - imageBlit.dstOffsets[1].x = mipDstWidth; - imageBlit.dstOffsets[1].y = mipDstHeight; + imageBlit.srcSubresource.mipLevel = 0; + imageBlit.dstSubresource.mipLevel = 1; + imageBlit.srcOffsets[1].x = mipSrcWidth; + imageBlit.srcOffsets[1].y = mipSrcHeight; + imageBlit.dstOffsets[1].x = mipDstWidth; + imageBlit.dstOffsets[1].y = mipDstHeight; - VkBlitImageInfo2 blitInfo = { - .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2, - .pNext = nullptr, - .srcImage = textureImage, - .srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .dstImage = textureImage, - .dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .regionCount = 1, - .pRegions = &imageBlit, - .filter = VK_FILTER_LINEAR, - }; + VkBlitImageInfo2 blitInfo = { + .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2, + .pNext = nullptr, + .srcImage = textureImage, + .srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .dstImage = textureImage, + .dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .regionCount = 1, + .pRegions = &imageBlit, + .filter = VK_FILTER_LINEAR, + }; - // MipMapping - for ( uint32_t dstMipLevel = 1; dstMipLevel < mipLevels; ++dstMipLevel ) - { - vkCmdPipelineBarrier2( frameInUse.commandBuffer, &prepareNextMipLevelDependency ); - vkCmdBlitImage2( frameInUse.commandBuffer, &blitInfo ); + // MipMapping + for ( uint32_t dstMipLevel = 1; dstMipLevel < mipLevels; ++dstMipLevel ) + { + vkCmdPipelineBarrier2( frameInUse.commandBuffer, &prepareNextMipLevelDependency ); + vkCmdBlitImage2( frameInUse.commandBuffer, &blitInfo ); - // Prep for NEXT iteration + // Prep for NEXT iteration - mipSrcWidth = mipDstWidth; - mipSrcHeight = mipDstHeight; - mipDstWidth = std::max( mipSrcWidth / 2, 1 ); - mipDstHeight = std::max( mipSrcHeight / 2, 1 ); + mipSrcWidth = mipDstWidth; + mipSrcHeight = mipDstHeight; + mipDstWidth = std::max( mipSrcWidth / 2, 1 ); + mipDstHeight = std::max( mipSrcHeight / 2, 1 ); - imageBlit.srcSubresource.mipLevel = dstMipLevel; - imageBlit.dstSubresource.mipLevel = dstMipLevel + 1; - imageBlit.srcOffsets[1].x = mipSrcWidth; - imageBlit.srcOffsets[1].y = mipSrcHeight; - imageBlit.dstOffsets[1].x = mipDstWidth; - imageBlit.dstOffsets[1].y = mipDstHeight; + imageBlit.srcSubresource.mipLevel = dstMipLevel; + imageBlit.dstSubresource.mipLevel = dstMipLevel + 1; + imageBlit.srcOffsets[1].x = mipSrcWidth; + imageBlit.srcOffsets[1].y = mipSrcHeight; + imageBlit.dstOffsets[1].x = mipDstWidth; + imageBlit.dstOffsets[1].y = mipDstHeight; - // Prep current mip level as source - prepareNextMipLevelBarriers[0].subresourceRange.baseMipLevel = dstMipLevel; - prepareNextMipLevelBarriers[1].subresourceRange.baseMipLevel = dstMipLevel + 1; - } - - // End - vkCmdPipelineBarrier2( frameInUse.commandBuffer, &transferToReadyDependency ); - } - vkEndCommandBuffer( frameInUse.commandBuffer ); - - VkSubmitInfo submitInfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = nullptr, - .waitSemaphoreCount = 0, - .pWaitSemaphores = nullptr, - .pWaitDstStageMask = nullptr, - .commandBufferCount = 1, - .pCommandBuffers = &frameInUse.commandBuffer, - .signalSemaphoreCount = 0, - .pSignalSemaphores = nullptr, - }; - VK_CHECK( vkQueueSubmit( renderDevice->directQueue, 1, &submitInfo, frameInUse.frameReadyToReuse ) ); - - // Do not reset this. Else, the frame will never be available to the main loop. - VK_CHECK( vkWaitForFences( renderDevice->device, 1, &frameInUse.frameReadyToReuse, VK_TRUE, UINT64_MAX ) ); - - renderDevice->frameIndex = ( renderDevice->frameIndex + 1 ) % renderDevice->getNumFrames(); + // Prep current mip level as source + prepareNextMipLevelBarriers[0].subresourceRange.baseMipLevel = dstMipLevel; + prepareNextMipLevelBarriers[1].subresourceRange.baseMipLevel = dstMipLevel + 1; } - vmaDestroyBuffer( renderDevice->gpuAllocator, stagingBuffer, stagingAllocation ); + // End + vkCmdPipelineBarrier2( frameInUse.commandBuffer, &transferToReadyDependency ); } + vkEndCommandBuffer( frameInUse.commandBuffer ); + + VkSubmitInfo submitInfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = 0, + .pWaitSemaphores = nullptr, + .pWaitDstStageMask = nullptr, + .commandBufferCount = 1, + .pCommandBuffers = &frameInUse.commandBuffer, + .signalSemaphoreCount = 0, + .pSignalSemaphores = nullptr, + }; + VK_CHECK( vkQueueSubmit( renderDevice->directQueue, 1, &submitInfo, frameInUse.frameReadyToReuse ) ); + + // Do not reset this. Else, the frame will never be available to the main loop. + VK_CHECK( vkWaitForFences( renderDevice->device, 1, &frameInUse.frameReadyToReuse, VK_TRUE, UINT64_MAX ) ); + + renderDevice->frameIndex = ( renderDevice->frameIndex + 1 ) % renderDevice->getNumFrames(); + } + + vmaDestroyBuffer( renderDevice->gpuAllocator, stagingBuffer, stagingAllocation ); + return texture; +} + +// TODO: Cache materials while loading. +uint32_t ProcessMaterial( RenderDevice* renderDevice, Model* model, cgltf_material const& material ) +{ + ASSERT( material.has_pbr_metallic_roughness ); + + auto const baseColorFactor = DirectX::XMFLOAT4{ material.pbr_metallic_roughness.base_color_factor }; + auto const emissiveFactor = DirectX::XMFLOAT4{ + material.emissive_factor[0], + material.emissive_factor[1], + material.emissive_factor[2], + std::max( material.emissive_strength.emissive_strength, 1.0f ), + }; + + VkSampler sampler = nullptr; + TextureID baseColorTexture; + TextureID normalTexture; + TextureID metalRoughTexture; + TextureID emissiveTexture; + + VkSamplerCreateInfo constexpr samplerCreateInfo = { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .mipLodBias = 0.0, + .anisotropyEnable = true, + .maxAnisotropy = 1.0f, + .compareEnable = false, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = 0.0f, + .maxLod = VK_LOD_CLAMP_NONE, + .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, + .unnormalizedCoordinates = false, + }; + + VK_CHECK( vkCreateSampler( renderDevice->device, &samplerCreateInfo, nullptr, &sampler ) ); + + if ( material.pbr_metallic_roughness.base_color_texture.texture ) + { + cgltf_image const* baseColorImage = material.pbr_metallic_roughness.base_color_texture.texture->image; + + auto baseColorTextureOpt = LoadTexture( renderDevice, sampler, *baseColorImage, false ); + if ( not baseColorTextureOpt ) + { + vkDestroySampler( renderDevice->device, Take( sampler ), nullptr ); + return UINT32_MAX; + } + baseColorTexture = std::move( baseColorTextureOpt.value() ); + } + + if ( material.pbr_metallic_roughness.metallic_roughness_texture.texture ) + { + cgltf_image const* metalRoughImage = material.pbr_metallic_roughness.metallic_roughness_texture.texture->image; + + auto metalRoughTextureOpt = LoadTexture( renderDevice, sampler, *metalRoughImage, true ); + if ( not metalRoughTextureOpt ) + { + vkDestroySampler( renderDevice->device, Take( sampler ), nullptr ); + renderDevice->textureManager->freeTexture( std::move( baseColorTexture ) ); + return UINT32_MAX; + } + metalRoughTexture = std::move( metalRoughTextureOpt.value() ); + } + + if ( material.normal_texture.texture ) + { + cgltf_image const* normalImage = material.normal_texture.texture->image; + + auto normalTextureOpt = LoadTexture( renderDevice, sampler, *normalImage, true ); + if ( not normalTextureOpt ) + { + vkDestroySampler( renderDevice->device, Take( sampler ), nullptr ); + renderDevice->textureManager->freeTexture( std::move( metalRoughTexture ) ); + renderDevice->textureManager->freeTexture( std::move( baseColorTexture ) ); + return UINT32_MAX; + } + normalTexture = std::move( normalTextureOpt.value() ); + } + + if ( material.emissive_texture.texture ) + { + cgltf_image const* emissiveImage = material.emissive_texture.texture->image; + + auto emissiveTextureOpt = LoadTexture( renderDevice, sampler, *emissiveImage, true ); + if ( not emissiveTextureOpt ) + { + vkDestroySampler( renderDevice->device, Take( sampler ), nullptr ); + renderDevice->textureManager->freeTexture( std::move( baseColorTexture ) ); + renderDevice->textureManager->freeTexture( std::move( normalTexture ) ); + renderDevice->textureManager->freeTexture( std::move( metalRoughTexture ) ); + return UINT32_MAX; + } + emissiveTexture = std::move( emissiveTextureOpt.value() ); } float const metallic = material.pbr_metallic_roughness.metallic_factor; float const roughness = material.pbr_metallic_roughness.roughness_factor; uint32_t const materialIdx = static_cast( model->materials.size() ); - model->materials.push_back( { sampler, std::move( baseColorTexture ), {}, roughness, metallic, baseColorFactor } ); + model->materials.push_back( { + sampler, + baseColorFactor, + emissiveFactor, + std::move( baseColorTexture ), + std::move( normalTexture ), + std::move( metalRoughTexture ), + std::move( emissiveTexture ), + roughness, + metallic, + } ); return materialIdx; } @@ -453,15 +524,15 @@ void LoadAttribute( size_t const components ) { size_t const floatCount = cgltf_accessor_unpack_floats( positionAttr.data, nullptr, 0 ); - ASSERT( floatCount % 3 == 0 ); + ASSERT( floatCount % components == 0 ); scratch->resize( floatCount ); cgltf_accessor_unpack_floats( positionAttr.data, scratch->data(), scratch->size() ); // Guaranteed to have space for these vertices. pVertices->resize( vertexStart + floatCount / components ); - byte* writePtr = reinterpret_cast( pVertices->data() + vertexStart ) + offset; - float* readPtr = scratch->data(); + byte* writePtr = reinterpret_cast( pVertices->data() + vertexStart ) + offset; + float const* readPtr = scratch->data(); for ( size_t i = vertexStart; i < pVertices->size(); ++i ) { memcpy( writePtr, readPtr, components * sizeof( float ) ); @@ -547,6 +618,18 @@ ModelMesh ProcessMesh( LoadAttribute( pVertices, vertexStart, &scratch, normalAttr, stride, offset, components ); } + if ( "TANGENT"sv == attributes[attribIndex].name ) + { + cgltf_attribute const& tangentAttr = attributes[attribIndex]; + ASSERT( tangentAttr.data->component_type == cgltf_component_type_r_32f ); + ASSERT( tangentAttr.data->type == cgltf_type_vec4 ); + + size_t constexpr stride = sizeof( Vertex ); + size_t constexpr offset = offsetof( Vertex, tangent ); + size_t constexpr components = 4; + + LoadAttribute( pVertices, vertexStart, &scratch, tangentAttr, stride, offset, components ); + } if ( "TEXCOORD_0"sv == attributes[attribIndex].name ) { cgltf_attribute const& texCoordAttr = attributes[attribIndex]; diff --git a/Blaze/ModelLoader.h b/Blaze/ModelLoader.h index 7e16bde..8cee515 100644 --- a/Blaze/ModelLoader.h +++ b/Blaze/ModelLoader.h @@ -15,7 +15,8 @@ struct GlobalMemory; struct Vertex { DirectX::XMFLOAT4 position = { 0.0f, 0.0f, 0.0f, 1.0f }; - DirectX::XMFLOAT4 normal = { 1.0f, 1.0f, 1.0f, 0.0f }; + DirectX::XMFLOAT4 normal = { 0.0f, 0.0f, 1.0f, 0.0f }; + DirectX::XMFLOAT4 tangent = { 1.0f, 0.0f, 0.0f, 0.0f }; DirectX::XMFLOAT2 texCoord0 = { 0.0f, 0.0f }; DirectX::XMFLOAT2 texCoord1 = { 0.0f, 0.0f }; DirectX::XMFLOAT4 color0 = { 1.0f, 1.0f, 1.0f, 1.0f }; @@ -42,21 +43,23 @@ struct ModelMesh struct Material { - constexpr static size_t GPU_DATA_OFFSET = sizeof( VkSampler ); - constexpr static size_t GPU_DATA_SIZE = - sizeof( TextureID ) + sizeof( uint32_t ) + 2 * sizeof( float ) + sizeof( DirectX::XMFLOAT4 ); + size_t constexpr static GPU_DATA_OFFSET = sizeof( VkSampler ); + size_t constexpr static GPU_DATA_SIZE = 56; VkSampler sampler; // TODO: Reuse // To copy directly. - TextureID texture; - uint32_t padding0; // FIXME: Wasting space. + DirectX::XMFLOAT4 baseColor = { 1.0f, 1.0f, 1.0f, 1.0f }; + DirectX::XMFLOAT4 emission = { 0.0f, 0.0f, 0.0f, 1.0f }; + TextureID albedoTextureID; + TextureID normalTextureID; + TextureID metalRoughTextureID; + TextureID emissiveTextureID; float roughness = 1.0f; float metallic = 1.0f; - DirectX::XMFLOAT4 baseColor = { 1.0f, 1.0f, 1.0f, 1.0f }; [[nodiscard]] bool isNull() const { - return texture.isNull() or sampler; + return not( albedoTextureID and normalTextureID and metalRoughTextureID and emissiveTextureID and sampler ); } }; diff --git a/Blaze/TextureManager.cpp b/Blaze/TextureManager.cpp index 29e4d17..79b5a0b 100644 --- a/Blaze/TextureManager.cpp +++ b/Blaze/TextureManager.cpp @@ -7,7 +7,7 @@ template struct RID; -std::optional TextureManager::createTexture( VkExtent3D const extent, VkSampler sampler ) +std::optional TextureManager::createTexture( VkExtent3D const extent, VkSampler const sampler, VkFormat const format ) { if ( m_freeList.empty() ) { @@ -20,8 +20,6 @@ std::optional TextureManager::createTexture( VkExtent3D const extent, ASSERT( m_pRenderDevice ); RenderDevice const& renderDevice = *m_pRenderDevice; - VkFormat const format = VK_FORMAT_R8G8B8A8_SRGB; - VkImage texture; VmaAllocation textureAllocation; VkImageView textureView; diff --git a/Blaze/TextureManager.h b/Blaze/TextureManager.h index 7f787e9..0196443 100644 --- a/Blaze/TextureManager.h +++ b/Blaze/TextureManager.h @@ -67,7 +67,8 @@ public: void freeTexture( TextureID&& rid ); DEPRECATE_JULY_2025 - [[nodiscard]] std::optional createTexture( VkExtent3D extent, VkSampler sampler ); + [[nodiscard]] std::optional createTexture( + VkExtent3D extent, VkSampler sampler, VkFormat format = VK_FORMAT_R8G8B8A8_SRGB ); DEPRECATE_JULY_2025 std::optional fetchImage( TextureID const& rid );