diff --git a/samples/03_model_render/CMakeLists.txt b/samples/03_model_render/CMakeLists.txt index 38492a7..e8694d8 100644 --- a/samples/03_model_render/CMakeLists.txt +++ b/samples/03_model_render/CMakeLists.txt @@ -12,7 +12,11 @@ add_executable(model_render model_render.cpp render_resource_manager.h model_loader.cpp model_loader.h -) + light_manager.cpp + light_manager.h + nodes.cpp + nodes.h) + add_shader(model_render shader/model.vs.hlsl) add_shader(model_render shader/model.ps.hlsl) diff --git a/samples/03_model_render/light_manager.cpp b/samples/03_model_render/light_manager.cpp new file mode 100644 index 0000000..1088db9 --- /dev/null +++ b/samples/03_model_render/light_manager.cpp @@ -0,0 +1,276 @@ +// ============================================= +// Aster: light_manager.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "light_manager.h" + +#include "glm/ext/matrix_transform.hpp" + +struct Light +{ + union { + vec3 um_Position; + vec3 um_Direction; + }; + f32 m_Range; // < 0.0 for invalid + u32 m_Color_; // LSB is used for flags. (R G B Flags) + f32 m_Intensity; + + constexpr static u32 MAX_GEN = 0x40; + constexpr static u32 GEN_MASK = MAX_GEN - 1; + + constexpr static u32 TYPE_MASK = 0xC0; + constexpr static u32 TYPE_INVALID = 0x0; + constexpr static u32 TYPE_DIRECTIONAL = 1 << 6; + constexpr static u32 TYPE_POINT = 2 << 6; + constexpr static u32 TYPE_SPOT = 3 << 6; // Currently Unused + + constexpr static u32 COLOR_MASK = ~(GEN_MASK | TYPE_MASK); +}; + +// Static Checks + +// Ensure layouts are exact. +static_assert(offsetof(DirectionalLight, m_Direction) == offsetof(Light, um_Direction)); +static_assert(offsetof(DirectionalLight, m_Color_) == offsetof(Light, m_Color_)); +static_assert(offsetof(DirectionalLight, m_Intensity) == offsetof(Light, m_Intensity)); +static_assert(sizeof(DirectionalLight) <= sizeof(Light)); + +// Ensure layouts are exact. +static_assert(offsetof(PointLight, m_Position) == offsetof(Light, um_Position)); +static_assert(offsetof(PointLight, m_Range) == offsetof(Light, m_Range)); +static_assert(offsetof(PointLight, m_Color_) == offsetof(Light, m_Color_)); +static_assert(offsetof(PointLight, m_Intensity) == offsetof(Light, m_Intensity)); +static_assert(sizeof(PointLight) <= sizeof(Light)); + +// Ensure bitmask are in the right place. +static_assert((Light::TYPE_MASK & Light::TYPE_INVALID) == Light::TYPE_INVALID); +static_assert((Light::TYPE_MASK & Light::TYPE_DIRECTIONAL) == Light::TYPE_DIRECTIONAL); +static_assert((Light::TYPE_MASK & Light::TYPE_POINT) == Light::TYPE_POINT); +static_assert((Light::TYPE_MASK & Light::TYPE_SPOT) == Light::TYPE_SPOT); +static_assert(Light::COLOR_MASK == 0xFFFFFF00); + +inline u32 +ToColor32(const vec4 &col) +{ + const u32 r = Cast(eastl::min(col.r, 1.0f) * 255.99f); + const u32 g = Cast(eastl::min(col.g, 1.0f) * 255.99f); + const u32 b = Cast(eastl::min(col.b, 1.0f) * 255.99f); + const u32 a = Cast(eastl::min(col.a, 1.0f) * 255.99f); + + return r << 24 | g << 16 | b << 8 | a; +} + +inline u32 +ToColor32(const vec3 &col) +{ + const u32 r = Cast(eastl::min(col.r, 1.0f) * 255.99f); + const u32 g = Cast(eastl::min(col.g, 1.0f) * 255.99f); + const u32 b = Cast(eastl::min(col.b, 1.0f) * 255.99f); + constexpr u32 a = 255; + + return r << 24 | g << 16 | b << 8 | a; +} + +LightManager::LightManager(GpuResourceManager *resourceManager) + : m_ResourceManager{resourceManager} + , m_DirectionalLightCount{} + , m_PointLightCount{} + , m_MetaInfo{} + , m_GpuBufferCapacity_{0} +{ +} + +LightManager::~LightManager() +{ + m_ResourceManager->Release(m_MetaInfo.m_LightBuffer); +} + +LightManager::LightManager(LightManager &&other) noexcept + : m_ResourceManager(other.m_ResourceManager) + , m_Lights(std::move(other.m_Lights)) + , m_DirectionalLightCount(other.m_DirectionalLightCount) + , m_PointLightCount(other.m_PointLightCount) + , m_MetaInfo(other.m_MetaInfo) + , m_GpuBufferCapacity_(other.m_GpuBufferCapacity_) +{ + other.m_MetaInfo.m_LightBuffer = {}; +} + +LightManager & +LightManager::operator=(LightManager &&other) noexcept +{ + if (this == &other) + return *this; + m_ResourceManager = other.m_ResourceManager; + m_Lights = std::move(other.m_Lights); + m_DirectionalLightCount = other.m_DirectionalLightCount; + m_PointLightCount = other.m_PointLightCount; + m_MetaInfo = other.m_MetaInfo; + other.m_MetaInfo.m_LightBuffer = {}; + m_GpuBufferCapacity_ = other.m_GpuBufferCapacity_; + return *this; +} + +LightHandle +LightManager::AddDirectional(const vec3 &direction, const vec3 &color) +{ + const vec3 normDirection = normalize(direction); + if (m_DirectionalLightCount < m_MetaInfo.m_DirectionalLightMaxCount) + { + u16 index = 0; + for (auto &light : m_Lights) + { + if (light.m_Range < 0) + { + const u8 gen = light.m_Color_ & Light::GEN_MASK; + + light.m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen; + light.m_Range = 1.0f; + light.um_Direction = normDirection; + + m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; + + return {Light::TYPE_DIRECTIONAL, gen, index}; + } + ++index; + assert(index < m_MetaInfo.m_DirectionalLightMaxCount); // Gap not found. But must exist + } + } + + // In case we will end up intersecting, we move point lights away. (2 at a time) + if (m_DirectionalLightCount == m_MetaInfo.m_DirectionalLightMaxCount && + m_MetaInfo.m_DirectionalLightMaxCount == m_MetaInfo.m_PointLightOffset) + { + const u16 oldPointLightOffset = m_MetaInfo.m_PointLightOffset; + const u32 pointLightMaxCount = m_MetaInfo.m_PointLightMaxCount; + // Might cause a capacity increase, but I want to use that for my gpu buffer resize. + m_Lights.push_back(); + m_Lights.push_back(); + + if (m_MetaInfo.m_PointLightMaxCount > 0) // Edge Case: nullptr at size 0 + { + Light *oldPointStart = m_Lights.data() + oldPointLightOffset; + Light *newPointStart = oldPointStart + 2; + + static_assert(std::is_trivially_copyable_v); + memcpy(newPointStart, oldPointStart, pointLightMaxCount * sizeof *newPointStart); + } + + m_MetaInfo.m_PointLightOffset += 2; + } + + constexpr u8 gen = 0; // New light + m_Lights[m_DirectionalLightCount].m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_DIRECTIONAL | gen; + m_Lights[m_DirectionalLightCount].m_Range = 1.0f; + m_Lights[m_DirectionalLightCount].um_Direction = normDirection; + const u16 index = m_DirectionalLightCount; + + ++m_DirectionalLightCount; + ++m_MetaInfo.m_DirectionalLightMaxCount; + + return {Light::TYPE_DIRECTIONAL, gen, index}; +} + +LightHandle +LightManager::AddPoint(const vec3 & position, const vec3 &color, const f32 radius) +{ + assert(m_PointLightCount <= m_MetaInfo.m_PointLightMaxCount); + assert(radius >= 0.0f); + if (m_PointLightCount < m_MetaInfo.m_PointLightMaxCount) + { + Light *light = m_Lights.data() + m_MetaInfo.m_PointLightOffset; + for (u32 index = 0; index < m_MetaInfo.m_PointLightMaxCount; ++index) + { + if (light->m_Range < 0) + { + const u8 gen = light->m_Color_ & Light::GEN_MASK; + + light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen; + light->m_Range = radius; + light->um_Position = position; + + m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; + + return {Light::TYPE_POINT, gen, Cast(index)}; + } + ++light; + } + assert(false); // gap must exists. + return {}; + } + + m_Lights.push_back(); + const u16 index = m_PointLightCount; + + Light *light = &m_Lights[index + m_MetaInfo.m_PointLightOffset]; + constexpr u8 gen = 0; // New light + + light->m_Color_ = (ToColor32(color) & Light::COLOR_MASK) | Light::TYPE_POINT | gen; + light->m_Range = radius; + light->um_Position = position; + + ++m_PointLightCount; + ++m_MetaInfo.m_PointLightMaxCount; + + m_GpuBufferCapacity_ |= UPDATE_REQUIRED_BIT; + + return {Light::TYPE_POINT, gen, index}; +} + +void +LightManager::Update() +{ + const u16 requiredBufferCapacity = eastl::min(Cast(m_Lights.capacity()), MAX_LIGHTS); + if ((m_GpuBufferCapacity_ & CAPACITY_MASK) < requiredBufferCapacity) + { + StorageBuffer newBuffer; + newBuffer.Init(m_ResourceManager->m_Device, requiredBufferCapacity * sizeof m_Lights[0], true, "Light Buffer"); + m_GpuBufferCapacity_ = requiredBufferCapacity | UPDATE_REQUIRED_BIT; + + m_ResourceManager->Release(m_MetaInfo.m_LightBuffer); + m_MetaInfo.m_LightBuffer = m_ResourceManager->Commit(&newBuffer); + } + if (m_GpuBufferCapacity_ & UPDATE_REQUIRED_BIT) + { + m_ResourceManager->Write(m_MetaInfo.m_LightBuffer, 0, m_Lights.size() * sizeof m_Lights[0], m_Lights.data()); + } +} + +void +LightManager::RemoveLight(const LightHandle handle) +{ + const u8 handleGen = handle.m_Generation; + + + if (handle.m_Type == Light::TYPE_DIRECTIONAL) + { + Light *lightSlot = &m_Lights[handle.m_Index]; + const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK; + if (slotGen > handleGen) + { + WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen); + return; + } + + lightSlot->m_Range = -1.0f; + lightSlot->m_Color_ = Light::TYPE_INVALID | (slotGen + 1) % Light::MAX_GEN; + --m_DirectionalLightCount; + } + + if (handle.m_Type == Light::TYPE_POINT) + { + Light *lightSlot = &m_Lights[handle.m_Index + m_MetaInfo.m_PointLightOffset]; + const u8 slotGen = lightSlot->m_Color_ & Light::GEN_MASK; + if (slotGen > handleGen) + { + WARN("Invalid handle gen: {} being freed. (slot gen: {})", handleGen, slotGen); + return; + } + + lightSlot->m_Range = -1.0f; + lightSlot->m_Color_ = Light::TYPE_INVALID | (slotGen + 1) % Light::MAX_GEN; + --m_PointLightCount; + } +} \ No newline at end of file diff --git a/samples/03_model_render/light_manager.h b/samples/03_model_render/light_manager.h new file mode 100644 index 0000000..aac9e54 --- /dev/null +++ b/samples/03_model_render/light_manager.h @@ -0,0 +1,84 @@ +// ============================================= +// Aster: light_manager.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "global.h" + +// TODO: Separate files so you only import handles. +#include "render_resource_manager.h" + +struct DirectionalLight +{ + vec3 m_Direction; + u32 m_UnusedPadding0_; + u32 m_Color_; // LSB is used for flags. (R G B Flags) + f32 m_Intensity; + +}; + +struct PointLight +{ + vec3 m_Position; + f32 m_Range; + u32 m_Color_; // LSB is used for flags. (R G B Flags) + f32 m_Intensity; +}; + +struct LightHandle +{ + u8 m_Type; + u8 m_Generation; + u16 m_Index; +}; + +struct Light; + +struct LightManager +{ + constexpr static u16 MAX_LIGHTS = MaxValue; + struct LightMetaInfo + { + // The number of directional lights is relatively low (1 - 2) and will almost never change in a scene. + // We can use that with Offset = 0, and point light at further offsets. + // This way we don't need to move point lights often. + + BufferHandle m_LightBuffer; // 04 04 + u16 m_PointLightMaxCount; // 02 06 + u16 m_PointLightOffset; // 02 08 + u16 m_DirectionalLightMaxCount; // 02 10 + u16 m_UnusedPadding0 = 0; // 02 12 + }; + + GpuResourceManager *m_ResourceManager; + eastl::vector m_Lights; + + // We don't need a Directional Light free list. We will just brute force iterate. + u16 m_DirectionalLightCount; + // TODO: A point light free list. We will brute force until we have a lot (100+) of point lights. + u16 m_PointLightCount; + + LightMetaInfo m_MetaInfo; + // Using lower bit for flags. Use CAPACITY_MASK for value. + u16 m_GpuBufferCapacity_; + + // Using lower bit. Capacity can be directly a multiple of 2 + // Thus, range is up to MaxValue + constexpr static u16 UPDATE_REQUIRED_BIT = 1; + constexpr static u16 CAPACITY_MASK = ~(UPDATE_REQUIRED_BIT); + + LightHandle AddDirectional(const vec3 &direction, const vec3 &color); + LightHandle AddPoint(const vec3& position, const vec3 &color, f32 radius); + void Update(); + void RemoveLight(LightHandle handle); + + explicit LightManager(GpuResourceManager *resourceManager); + ~LightManager(); + + LightManager(LightManager &&other) noexcept; + LightManager &operator=(LightManager &&other) noexcept; + + DISALLOW_COPY_AND_ASSIGN(LightManager); +}; \ No newline at end of file diff --git a/samples/03_model_render/model_loader.cpp b/samples/03_model_render/model_loader.cpp index 93ee771..b29321f 100644 --- a/samples/03_model_render/model_loader.cpp +++ b/samples/03_model_render/model_loader.cpp @@ -673,115 +673,6 @@ Model::operator=(Model &&other) noexcept return *this; } -u32 -Nodes::Add(const mat4 &transform, const i32 parent) -{ - m_Dirty = true; - const u32 index = Count(); - m_Transforms.push_back(transform); - m_GlobalTransforms.emplace_back(transform); - const u32 parentVal = (parent < 0 ? ROOT_BIT : parent & PARENT_MASK) | DIRTY_BIT; - m_Parents_.push_back(parentVal); - - return index; -} - -const mat4 & -Nodes::Get(const u32 index) const -{ - return m_Transforms[index]; -} - -void -Nodes::Set(const u32 index, const mat4 &transform) -{ - m_Dirty = true; - m_Transforms[index] = transform; - m_Parents_[index] |= DIRTY_BIT; -} - -const mat4 & -Nodes::operator[](const u32 index) const -{ - return m_Transforms[index]; -} - -mat4 & -Nodes::operator[](const u32 index) -{ - m_Dirty = true; - m_Parents_[index] |= DIRTY_BIT; - return m_Transforms[index]; -} - -u32 -Nodes::Count() const -{ - return Cast(m_Transforms.size()); -} - -usize -Nodes::GetGlobalTransformByteSize() const -{ - return m_GlobalTransforms.size() * sizeof m_GlobalTransforms[0]; -} - -const Nodes::Transform * -Nodes::GetGlobalTransformPtr() const -{ - return m_GlobalTransforms.data(); -} - -bool -Nodes::Update() -{ - if (!m_Dirty) - return false; - - auto transformIter = m_Transforms.begin(); - auto globalTransformIter = m_GlobalTransforms.begin(); - auto parentIter = m_Parents_.begin(); - const auto parentEnd = m_Parents_.end(); - - while (parentIter != parentEnd) - { - const bool isRoot = *parentIter & ROOT_BIT; - const bool isDirty = *parentIter & DIRTY_BIT; - - if (isRoot) - { - if (isDirty) - { - // Copy-update if the root is dirty. - *globalTransformIter = *transformIter; - } - } - else - { - const u32 parentIdx = *parentIter & PARENT_MASK; - const bool isParentDirty = m_Parents_[parentIdx] & DIRTY_BIT; - if (isDirty || isParentDirty) - { - // Update w.r.t parent if either local or parent transforms updated. - *globalTransformIter = m_GlobalTransforms[parentIdx].m_GlobalTransforms * *transformIter; - m_Parents_[parentIdx] |= DIRTY_BIT; // Set dirty to propagate the update. - } - } - - ++parentIter; - ++globalTransformIter; - ++transformIter; - } - - for (u32 &parentValue : m_Parents_) - { - parentValue &= ~DIRTY_BIT; // Unset dirty. - } - - m_Dirty = false; - return true; -} - const mat4 & Model::GetModelTransform() const { diff --git a/samples/03_model_render/model_loader.h b/samples/03_model_render/model_loader.h index c51adb8..ea477c2 100644 --- a/samples/03_model_render/model_loader.h +++ b/samples/03_model_render/model_loader.h @@ -9,6 +9,7 @@ #include "global.h" #include "render_resource_manager.h" +#include "nodes.h" #include @@ -27,52 +28,6 @@ struct MeshPrimitive i32 m_TransformIdx; }; -struct Nodes -{ - struct Transform - { - mat4 m_GlobalTransforms; - mat4 m_NormalTransforms; - - explicit Transform(const mat4& transform) - : m_GlobalTransforms(transform) - , m_NormalTransforms(transpose(inverse(mat3{transform}))) - { - } - - Transform & - operator=(const mat4& transform) - { - m_GlobalTransforms = transform; - m_NormalTransforms = transpose(inverse(mat3{transform})); - return *this; - } - }; - - eastl::vector m_Transforms; - eastl::vector m_GlobalTransforms; - /// Parents are also used for bookkeeping - eastl::vector m_Parents_; - bool m_Dirty = true; - - constexpr static u32 ROOT_BIT = 1u << 31; - constexpr static u32 DIRTY_BIT = 1u << 30; - constexpr static u32 PARENT_MASK = ~(ROOT_BIT | DIRTY_BIT); - - u32 Add(const mat4 &transform, const i32 parent = -1); - [[nodiscard]] const mat4 &Get(const u32 index) const; - void Set(const u32 index, const mat4 &transform); - [[nodiscard]] u32 Count() const; - - [[nodiscard]] const mat4 &operator[](const u32 index) const; - [[nodiscard]] mat4 &operator[](const u32 index); - - [[nodiscard]] usize GetGlobalTransformByteSize() const; - [[nodiscard]] const Transform *GetGlobalTransformPtr() const; - - bool Update(); -}; - struct Material { vec4 m_AlbedoFactor; // 16 16 diff --git a/samples/03_model_render/model_render.cpp b/samples/03_model_render/model_render.cpp index cafc019..7d6b167 100644 --- a/samples/03_model_render/model_render.cpp +++ b/samples/03_model_render/model_render.cpp @@ -16,6 +16,7 @@ #include "frame.h" #include "helpers.h" +#include "light_manager.h" #include "model_loader.h" #include "pipeline_utils.h" @@ -73,8 +74,9 @@ main(int, char **) GpuResourceManager resourceManager = {&device, 1000}; ModelLoader modelLoader = {&resourceManager, commandQueue, queueAllocation.m_Family, queueAllocation.m_Family}; + LightManager lightManager = LightManager{&resourceManager}; - auto model = modelLoader.LoadModel(MODEL_FILE); + Model model = modelLoader.LoadModel(MODEL_FILE); Pipeline pipeline = CreatePipeline(&device, &swapchain, &resourceManager); @@ -84,6 +86,12 @@ main(int, char **) 70_deg, Cast(swapchain.m_Extent.width) / Cast(swapchain.m_Extent.height), 0.1f, 100.0f), }; + lightManager.AddDirectional(vec3(0.0f, -1.0f, 0.0f), {0.0f, 1.0f, 0.0f}); + lightManager.AddPoint(vec3{2.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 15.0f); + lightManager.AddPoint(vec3{-2.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, 15.0f); + + lightManager.Update(); + vk::DescriptorPool descriptorPool; vk::DescriptorSet descriptorSet; @@ -264,16 +272,23 @@ main(int, char **) cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline.m_Layout, 1, 1, &descriptorSet, 0, nullptr); cmd.bindIndexBuffer(model.m_IndexBuffer.m_Buffer, 0, vk::IndexType::eUint32); - cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, 0, sizeof model.m_Handles, + + u32 pcbOffset = 0; + cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset, sizeof model.m_Handles, &model.m_Handles); + pcbOffset += sizeof model.m_Handles; + cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset, sizeof lightManager.m_MetaInfo, + &lightManager.m_MetaInfo); + pcbOffset += sizeof lightManager.m_MetaInfo; for (auto &prim : model.m_MeshPrimitives) { - cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, sizeof model.m_Handles, + cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset, sizeof prim.m_MaterialIdx, &prim.m_MaterialIdx); - cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, - sizeof model.m_Handles + sizeof prim.m_MaterialIdx, + pcbOffset += sizeof prim.m_MaterialIdx; + cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, pcbOffset, sizeof prim.m_TransformIdx, &prim.m_TransformIdx); + pcbOffset += sizeof prim.m_TransformIdx; cmd.drawIndexed(prim.m_IndexCount, 1, prim.m_FirstIndex, Cast(prim.m_VertexOffset), 0); } diff --git a/samples/03_model_render/nodes.cpp b/samples/03_model_render/nodes.cpp new file mode 100644 index 0000000..9511d53 --- /dev/null +++ b/samples/03_model_render/nodes.cpp @@ -0,0 +1,115 @@ +// ============================================= +// Aster: nodes.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "nodes.h" + +u32 +Nodes::Add(const mat4 &transform, const i32 parent) +{ + m_Dirty = true; + const u32 index = Count(); + m_Transforms.push_back(transform); + m_GlobalTransforms.emplace_back(transform); + const u32 parentVal = (parent < 0 ? ROOT_BIT : parent & PARENT_MASK) | DIRTY_BIT; + m_Parents_.push_back(parentVal); + + return index; +} + +const mat4 & +Nodes::Get(const u32 index) const +{ + return m_Transforms[index]; +} + +void +Nodes::Set(const u32 index, const mat4 &transform) +{ + m_Dirty = true; + m_Transforms[index] = transform; + m_Parents_[index] |= DIRTY_BIT; +} + +const mat4 & +Nodes::operator[](const u32 index) const +{ + return m_Transforms[index]; +} + +mat4 & +Nodes::operator[](const u32 index) +{ + m_Dirty = true; + m_Parents_[index] |= DIRTY_BIT; + return m_Transforms[index]; +} + +u32 +Nodes::Count() const +{ + return Cast(m_Transforms.size()); +} + +usize +Nodes::GetGlobalTransformByteSize() const +{ + return m_GlobalTransforms.size() * sizeof m_GlobalTransforms[0]; +} + +const Nodes::Transform * +Nodes::GetGlobalTransformPtr() const +{ + return m_GlobalTransforms.data(); +} + +bool +Nodes::Update() +{ + if (!m_Dirty) + return false; + + auto transformIter = m_Transforms.begin(); + auto globalTransformIter = m_GlobalTransforms.begin(); + auto parentIter = m_Parents_.begin(); + const auto parentEnd = m_Parents_.end(); + + while (parentIter != parentEnd) + { + const bool isRoot = *parentIter & ROOT_BIT; + const bool isDirty = *parentIter & DIRTY_BIT; + + if (isRoot) + { + if (isDirty) + { + // Copy-update if the root is dirty. + *globalTransformIter = *transformIter; + } + } + else + { + const u32 parentIdx = *parentIter & PARENT_MASK; + const bool isParentDirty = m_Parents_[parentIdx] & DIRTY_BIT; + if (isDirty || isParentDirty) + { + // Update w.r.t parent if either local or parent transforms updated. + *globalTransformIter = m_GlobalTransforms[parentIdx].m_GlobalTransforms * *transformIter; + m_Parents_[parentIdx] |= DIRTY_BIT; // Set dirty to propagate the update. + } + } + + ++parentIter; + ++globalTransformIter; + ++transformIter; + } + + for (u32 &parentValue : m_Parents_) + { + parentValue &= ~DIRTY_BIT; // Unset dirty. + } + + m_Dirty = false; + return true; +} \ No newline at end of file diff --git a/samples/03_model_render/nodes.h b/samples/03_model_render/nodes.h new file mode 100644 index 0000000..545f15c --- /dev/null +++ b/samples/03_model_render/nodes.h @@ -0,0 +1,56 @@ +// ============================================= +// Aster: nodes.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "global.h" + +#include "EASTL/vector.h" + +struct Nodes +{ + struct Transform + { + mat4 m_GlobalTransforms; + mat4 m_NormalTransforms; + + explicit Transform(const mat4 &transform) + : m_GlobalTransforms(transform) + , m_NormalTransforms(transpose(inverse(mat3{transform}))) + { + } + + Transform & + operator=(const mat4 &transform) + { + m_GlobalTransforms = transform; + m_NormalTransforms = transpose(inverse(mat3{transform})); + return *this; + } + }; + + eastl::vector m_Transforms; + eastl::vector m_GlobalTransforms; + /// Parents are also used for bookkeeping + eastl::vector m_Parents_; + bool m_Dirty = true; + + constexpr static u32 ROOT_BIT = 1u << 31; + constexpr static u32 DIRTY_BIT = 1u << 30; + constexpr static u32 PARENT_MASK = ~(ROOT_BIT | DIRTY_BIT); + + u32 Add(const mat4 &transform, const i32 parent = -1); + [[nodiscard]] const mat4 &Get(const u32 index) const; + void Set(const u32 index, const mat4 &transform); + [[nodiscard]] u32 Count() const; + + [[nodiscard]] const mat4 &operator[](const u32 index) const; + [[nodiscard]] mat4 &operator[](const u32 index); + + [[nodiscard]] usize GetGlobalTransformByteSize() const; + [[nodiscard]] const Transform *GetGlobalTransformPtr() const; + + bool Update(); +}; diff --git a/samples/03_model_render/pipeline_utils.cpp b/samples/03_model_render/pipeline_utils.cpp index 5c84431..0558181 100644 --- a/samples/03_model_render/pipeline_utils.cpp +++ b/samples/03_model_render/pipeline_utils.cpp @@ -58,7 +58,7 @@ CreatePipeline(const Device *device, const Swapchain *swapchain, const GpuResour vk::PushConstantRange pushConstantRange = { .stageFlags = vk::ShaderStageFlagBits::eAll, .offset = 0, - .size = 24, + .size = 36, }; vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = { diff --git a/samples/03_model_render/shader/bindless_structs.hlsli b/samples/03_model_render/shader/bindless_structs.hlsli index c173363..2d53e99 100644 --- a/samples/03_model_render/shader/bindless_structs.hlsli +++ b/samples/03_model_render/shader/bindless_structs.hlsli @@ -32,22 +32,38 @@ struct Block uint vertexDataHandle; uint materialBufferHandle; uint nodeBufferHandle; + uint lightHandle; + uint pointLightIndexer; + uint directionLightIndexer; int m_MaterialIdx; uint m_NodeIdx; }; +struct Light +{ + float m_Position[3]; + float m_Range; + uint m_Color; + float m_Intensity; +}; + struct Camera { float4x4 view; float4x4 proj; }; +// Little Endian storage. First short is least significant. +#define IndexerCount(Indexer) (Indexer & 0xFFFF) +#define IndexerOffset(Indexer) ((Indexer & 0xFFFF0000) >> 16); + #define INVALID_HANDLE 0xFFFFFFFF [[vk::binding(0, 0)]] StructuredBuffer vertexBuffer[]; [[vk::binding(0, 0)]] StructuredBuffer vertexDataBuffer[]; [[vk::binding(0, 0)]] StructuredBuffer materialsBuffer[]; [[vk::binding(0, 0)]] StructuredBuffer nodeBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer lightBuffer[]; [[vk::binding(1, 0)]] Texture2D textures[]; [[vk::binding(1, 0)]] SamplerState immutableSamplers[]; diff --git a/samples/03_model_render/shader/model.ps.hlsl b/samples/03_model_render/shader/model.ps.hlsl index f7a70d2..ca112d3 100644 --- a/samples/03_model_render/shader/model.ps.hlsl +++ b/samples/03_model_render/shader/model.ps.hlsl @@ -26,23 +26,89 @@ float4 GetAlbedo(uint materialBufferId, int materialId, float2 uv) } } -FS_Output main(FS_Input stage_input) +float3 GetDirectionalLightInfluence(float3 normal) +{ + if (pcb.lightHandle == INVALID_HANDLE) + return float3(0.0f, 0.0f, 0.0f); + + uint count = IndexerCount(pcb.directionLightIndexer); + + float3 contrib = 0.0f; + for (uint i = 0; i < count; ++i) + { + Light light = lightBuffer[pcb.lightHandle][i]; + float3 lightDir = - (float3) light.m_Position; // Position is actually direction for directionalLight; LightDir is Direction towards the light (-direction) + float diff = max(dot(normal, lightDir), 0.0f); + + int ur = (light.m_Color & 0xFF000000) >> 24; + int ug = (light.m_Color & 0x00FF0000) >> 16; + int ub = (light.m_Color & 0x0000FF00) >> 8; + + float r = ur; + float g = ug; + float b = ub; + + float3 color = float3(r, g, b) * 0.00392156862f; // 0.00392156862 = 1/255 + + float3 diffuse = diff * color; + + contrib += (light.m_Range < 0 ? float3(0.0f, 0.0f, 0.0f) : diffuse); + } + + return contrib; +} + +float3 GetPointLightInfluence(float3 position, float3 normal) +{ + if (pcb.lightHandle == INVALID_HANDLE) + return float3(0.0f, 0.0f, 0.0f); + + uint offset = IndexerOffset(pcb.pointLightIndexer); + uint count = IndexerCount(pcb.pointLightIndexer); + + float3 contrib = 0.0f; + for (uint i = 0; i < count; ++i) + { + Light light = lightBuffer[pcb.lightHandle][i + offset]; + float3 lightDir = normalize(((float3)light.m_Position) - position); + float diff = max(dot(normal, lightDir), 0.0f); + + int ur = (light.m_Color & 0xFF000000) >> 24; + int ug = (light.m_Color & 0x00FF0000) >> 16; + int ub = (light.m_Color & 0x0000FF00) >> 8; + + float r = ur; + float g = ug; + float b = ub; + + float3 color = float3(r, g, b) * 0.00392156862f; // 0.00392156862 = 1/255 + + float3 diffuse = diff * color; + + contrib += (light.m_Range < 0 ? float3(0.0f, 0.0f, 0.0f) : diffuse); + } + + return contrib; +} + +FS_Output + main(FS_Input stage_input) { - // Hereby assume that we always have a point light at - float3 lightPos = float3(6.0f, 6.0f, 6.0f); - // with - float3 lightColor = float3(0.7f, 0.7f, 0.7f); //float3(0.7f, 0.4f, 0.1f); - // and + //// Hereby assume that we always have a point light at + //float3 lightPos = float3(6.0f, 6.0f, 6.0f); + //// with + //float3 lightColor = float3(0.7f, 0.7f, 0.7f); //float3(0.7f, 0.4f, 0.1f); + //// and float3 ambient = float3(0.02f, 0.02f, 0.02f); float3 norm = normalize(stage_input.inNormal.xyz); - float3 lightDir = normalize(lightPos - stage_input.inPosition.xyz); - float diff = max(dot(norm, lightDir), 0.0f); - float3 diffuse = diff * lightColor; + float3 pos = stage_input.inPosition.xyz; float4 objColor = pcb.m_MaterialIdx < 0 ? stage_input.inColor : GetAlbedo(pcb.materialBufferHandle, pcb.m_MaterialIdx, stage_input.inUV); + float3 diffuse = GetDirectionalLightInfluence(norm) + GetPointLightInfluence(pos, norm); + FS_Output output; output.outColor = float4(objColor.rgb * (diffuse + ambient), objColor.a); return output;