feat(renderer): buffer #61

Merged
light7734 merged 1 commit from feat/renderer/buffers into main 2025-10-25 12:56:15 +00:00
17 changed files with 493 additions and 31 deletions

View file

@ -8,15 +8,17 @@ add_library_module(
backend/vk/context/instance.cpp
backend/vk/context/surface.cpp
backend/vk/context/swapchain.cpp
backend/vk/data/buffer.cpp
backend/vk/renderer/pass.cpp
backend/vk/renderer/renderer.cpp
# Vulkan - frontend
# frontend
frontend/messenger.cpp
frontend/context/device.cpp
frontend/context/gpu.cpp
frontend/context/instance.cpp
frontend/context/surface.cpp
frontend/context/swapchain.cpp
frontend/data/buffer.cpp
frontend/renderer/renderer.cpp
frontend/renderer/pass.cpp)
@ -34,11 +36,10 @@ add_test_module(
frontend/context/surface.test.cpp
frontend/context/device.test.cpp
frontend/context/swapchain.test.cpp
frontend/data/buffer.test.cpp
frontend/renderer/pass.test.cpp
frontend/renderer/renderer.test.cpp
# backend specific tests -- vk
backend/vk/context/instance.test.cpp
# backend specific tests -- dx backend specific tests -- mt
)
backend/vk/context/instance.test.cpp)
target_link_libraries(renderer_tests PRIVATE surface pthread)

View file

@ -207,6 +207,31 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return images;
}
[[nodiscard]] auto Device::get_memory_requirements(VkBuffer buffer) const -> VkMemoryRequirements
{
auto requirements = VkMemoryRequirements {};
vk_get_buffer_memory_requirements(m_device, buffer, &requirements);
return requirements;
}
void Device::bind_memory(VkBuffer buffer, VkDeviceMemory memory, size_t offset /* = 0u */) const
{
vkc(vk_bind_buffer_memory(m_device, buffer, memory, offset));
}
[[nodiscard]] auto Device::map_memory(VkDeviceMemory memory, size_t size, size_t offset) const
-> std::span<std::byte>
{
void *data = {};
vkc(vk_map_memory(m_device, memory, offset, size, {}, &data));
return { std::bit_cast<std::byte *>(data), size };
}
void Device::unmap_memory(VkDeviceMemory memory)
{
vk_unmap_memory(m_device, memory);
}
[[nodiscard]] auto Device::create_swapchain(VkSwapchainCreateInfoKHR info) const -> VkSwapchainKHR
{
auto *swapchain = VkSwapchainKHR {};
@ -299,6 +324,13 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return fences;
}
[[nodiscard]] auto Device::create_buffer(VkBufferCreateInfo info) const -> VkBuffer
{
auto *buffer = VkBuffer {};
vkc(vk_create_buffer(m_device, &info, nullptr, &buffer));
return buffer;
}
[[nodiscard]] auto Device::allocate_command_buffers(VkCommandBufferAllocateInfo info) const
-> std::vector<VkCommandBuffer>
{
@ -307,6 +339,18 @@ void Device::wait_for_fences(std::span<VkFence> fences) const
return command_buffers;
}
[[nodiscard]] auto Device::allocate_memory(VkMemoryAllocateInfo info) const -> VkDeviceMemory
{
auto *memory = VkDeviceMemory {};
vkc(vk_allocate_memory(m_device, &info, nullptr, &memory));
return memory;
}
void Device::free_memory(VkDeviceMemory memory) const
{
vk_free_memory(m_device, memory, nullptr);
}
void Device::destroy_swapchain(VkSwapchainKHR swapchain) const
{
vk_destroy_swapchain_khr(m_device, swapchain, m_allocator);
@ -389,4 +433,9 @@ void Device::destroy_fences(std::span<VkFence> fences) const
}
}
void Device::destroy_buffer(VkBuffer buffer) const
{
vk_destroy_buffer(m_device, buffer, nullptr);
}
} // namespace lt::renderer::vk

View file

@ -96,6 +96,16 @@ public:
[[nodiscard]] auto get_swapchain_images(VkSwapchainKHR swapchain) const -> std::vector<VkImage>;
[[nodiscard]] auto get_memory_requirements(VkBuffer buffer) const -> VkMemoryRequirements;
/** binders / mappers */
void bind_memory(VkBuffer buffer, VkDeviceMemory memory, size_t offset = 0u) const;
[[nodiscard]] auto map_memory(VkDeviceMemory memory, size_t size, size_t offset) const
-> std::span<std::byte>;
void unmap_memory(VkDeviceMemory memory);
/** create functions */
[[nodiscard]] auto create_swapchain(VkSwapchainCreateInfoKHR info) const -> VkSwapchainKHR;
@ -120,10 +130,17 @@ public:
[[nodiscard]] auto create_fences(VkFenceCreateInfo info, uint32_t count) const
-> std::vector<VkFence>;
[[nodiscard]] auto create_buffer(VkBufferCreateInfo info) const -> VkBuffer;
/** allocation functions */
[[nodiscard]] auto allocate_memory(VkMemoryAllocateInfo info) const -> VkDeviceMemory;
[[nodiscard]] auto allocate_command_buffers(VkCommandBufferAllocateInfo info) const
-> std::vector<VkCommandBuffer>;
/** de-allocation functions */
void free_memory(VkDeviceMemory memory) const;
/** destroy functions */
void destroy_swapchain(VkSwapchainKHR swapchain) const;
@ -153,6 +170,8 @@ public:
void destroy_fences(std::span<VkFence> fences) const;
void destroy_buffer(VkBuffer buffer) const;
private:
template<typename T>
static auto get_object_type(const T &object) -> VkObjectType

View file

@ -67,5 +67,11 @@ Gpu::Gpu(IInstance *instance)
return formats;
}
[[nodiscard]] auto Gpu::get_memory_properties() const -> VkPhysicalDeviceMemoryProperties
{
auto memory_properties = VkPhysicalDeviceMemoryProperties {};
vk_get_physical_device_memory_properties(m_gpu, &memory_properties);
return memory_properties;
}
} // namespace lt::renderer::vk

View file

@ -30,6 +30,8 @@ public:
[[nodiscard]] auto get_surface_formats(VkSurfaceKHR surface) const
-> std::vector<VkSurfaceFormatKHR>;
[[nodiscard]] auto get_memory_properties() const -> VkPhysicalDeviceMemoryProperties;
private:
memory::NullOnMove<VkPhysicalDevice> m_gpu = VK_NULL_HANDLE;
};

View file

@ -31,6 +31,7 @@ PFN_vkGetDeviceProcAddr vk_get_device_proc_address {};
PFN_vkDestroyDevice vk_destroy_device {};
PFN_vkGetPhysicalDeviceFeatures vk_get_physical_device_features {};
PFN_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties {};
PFN_vkGetPhysicalDeviceMemoryProperties vk_get_physical_device_memory_properties {};
// extension instance functions
PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label {};
@ -87,6 +88,16 @@ PFN_vkCmdDraw vk_cmd_draw {};
PFN_vkCmdSetViewport vk_cmd_set_viewport {};
PFN_vkCmdSetScissor vk_cmd_set_scissors {};
PFN_vkCreateBuffer vk_create_buffer {};
PFN_vkDestroyBuffer vk_destroy_buffer {};
PFN_vkGetBufferMemoryRequirements vk_get_buffer_memory_requirements {};
PFN_vkAllocateMemory vk_allocate_memory {};
PFN_vkBindBufferMemory vk_bind_buffer_memory {};
PFN_vkMapMemory vk_map_memory {};
PFN_vkUnmapMemory vk_unmap_memory {};
PFN_vkFreeMemory vk_free_memory {};
PFN_vkResetCommandBuffer vk_reset_command_buffer {};
PFN_vkGetPhysicalDeviceSurfaceSupportKHR vk_get_physical_device_surface_support {};
@ -298,6 +309,7 @@ void Instance::load_instance_functions()
load_fn(vk_destroy_device, "vkDestroyDevice");
load_fn(vk_get_physical_device_features, "vkGetPhysicalDeviceFeatures");
load_fn(vk_enumerate_device_extension_properties, "vkEnumerateDeviceExtensionProperties");
load_fn(vk_get_physical_device_memory_properties, "vkGetPhysicalDeviceMemoryProperties");
load_fn(vk_cmd_begin_debug_label, "vkCmdBeginDebugUtilsLabelEXT");
load_fn(vk_cmd_end_debug_label, "vkCmdEndDebugUtilsLabelEXT");
@ -370,6 +382,14 @@ void Instance::load_device_functions_impl(VkDevice device)
load_fn(vk_cmd_draw, "vkCmdDraw");
load_fn(vk_cmd_set_viewport, "vkCmdSetViewport");
load_fn(vk_cmd_set_scissors, "vkCmdSetScissor");
load_fn(vk_create_buffer, "vkCreateBuffer");
load_fn(vk_destroy_buffer, "vkDestroyBuffer");
load_fn(vk_allocate_memory, "vkAllocateMemory");
load_fn(vk_bind_buffer_memory, "vkBindBufferMemory");
load_fn(vk_map_memory, "vkMapMemory");
load_fn(vk_unmap_memory, "vkUnmapMemory");
load_fn(vk_free_memory, "vkFreeMemory");
load_fn(vk_get_buffer_memory_requirements, "vkGetBufferMemoryRequirements");
load_fn(vk_reset_command_buffer, "vkResetCommandBuffer");
}

View file

@ -0,0 +1,72 @@
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/gpu.hpp>
#include <renderer/backend/vk/data/buffer.hpp>
namespace lt::renderer::vk {
Buffer::Buffer(IDevice *device, IGpu *gpu, const CreateInfo &info)
: m_device(static_cast<Device *>(device))
, m_gpu(static_cast<Gpu *>(gpu))
, m_buffer(
m_device,
VkBufferCreateInfo {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = info.size,
.usage = to_native_usage_flags(info.usage),
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
}
)
, m_memory(m_device, m_buffer, allocation_info_from_memory_requirements())
, m_size(info.size)
{
}
[[nodiscard]] auto Buffer::map() -> std::span<std::byte> /* override */
{
return m_device->map_memory(m_memory, m_size, 0ul);
}
void Buffer::unmap() /* override */
{
m_device->unmap_memory(m_memory);
}
[[nodiscard]] auto Buffer::to_native_usage_flags(Usage usage) const -> VkBufferUsageFlags
{
switch (usage)
{
case Usage::vertex: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
case Usage::index: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
}
std::unreachable();
}
[[nodiscard]] auto Buffer::allocation_info_from_memory_requirements() const -> VkMemoryAllocateInfo
{
const auto requirements = m_device->get_memory_requirements(m_buffer);
auto memory_properties = m_gpu->get_memory_properties();
const auto required_properties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
auto type = 0u;
for (auto idx = 0; idx < memory_properties.memoryTypeCount; ++idx)
{
if ((requirements.memoryTypeBits & (1 << idx))
&& ((memory_properties.memoryTypes[idx].propertyFlags & required_properties)
== required_properties))
{
type = idx;
break;
}
}
return VkMemoryAllocateInfo {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = requirements.size,
.memoryTypeIndex = type,
};
}
} // namespace lt::renderer::vk

View file

@ -0,0 +1,39 @@
#pragma once
#include <renderer/backend/vk/raii/raii.hpp>
#include <renderer/frontend/data/buffer.hpp>
namespace lt::renderer::vk {
class Buffer: public IBuffer
{
public:
Buffer(class IDevice *device, class IGpu *gpu, const CreateInfo &info);
[[nodiscard]] auto map() -> std::span<std::byte> override;
void unmap() override;
[[nodiscard]] auto get_size() const -> size_t override
{
return m_size;
}
private:
[[nodiscard]] auto to_native_usage_flags(Usage usage) const -> VkBufferUsageFlags;
[[nodiscard]] auto allocation_info_from_memory_requirements() const -> VkMemoryAllocateInfo;
Device *m_device {};
Gpu *m_gpu {};
raii::Buffer m_buffer;
raii::Memory m_memory;
// TODO(Light): should this reflect the allocation size instead?
size_t m_size {};
};
} // namespace lt::renderer::vk

View file

@ -1,10 +1,11 @@
#include <memory/pointer_types/null_on_move.hpp>
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/context/instance.hpp>
#include <renderer/backend/vk/vulkan.hpp>
namespace lt::renderer::vk::raii {
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
namespace lt::renderer::vk::raii { // NOLINTBEGIN(cppcoreguidelines-special-member-functions)
class DebugMessenger
{
public:
@ -16,12 +17,10 @@ public:
~DebugMessenger()
{
if (!m_instance)
if (m_instance)
{
return;
m_instance->destroy_messenger(m_object);
}
m_instance->destroy_messenger(m_object);
}
private:
@ -30,4 +29,74 @@ private:
VkDebugUtilsMessengerEXT m_object;
};
// NOLINTNEXTLINE(cppcoreguidelines-special-member-functions)
class Buffer
{
public:
Buffer(Device *device, VkBufferCreateInfo info)
: m_device(device)
, m_object(m_device->create_buffer(info))
{
}
~Buffer()
{
if (m_device)
{
m_device->destroy_buffer(m_object);
}
}
[[nodiscard]] auto operator*() const -> VkBuffer
{
return m_object;
}
[[nodiscard]] operator VkBuffer() const
{
return m_object;
}
private:
memory::NullOnMove<Device *> m_device {};
VkBuffer m_object;
};
class Memory
{
public:
Memory(Device *device, VkBuffer buffer, VkMemoryAllocateInfo info)
: m_device(device)
, m_object(m_device->allocate_memory(info))
{
m_device->bind_memory(buffer, m_object);
}
~Memory()
{
if (m_device)
{
m_device->free_memory(m_object);
}
}
[[nodiscard]] auto operator*() const -> VkDeviceMemory
{
return m_object;
}
[[nodiscard]] operator VkDeviceMemory() const
{
return m_object;
}
private:
memory::NullOnMove<Device *> m_device {};
VkDeviceMemory m_object = VK_NULL_HANDLE;
};
// NOLINTEND(cppcoreguidelines-special-member-functions)
} // namespace lt::renderer::vk::raii

View file

@ -1,18 +0,0 @@
// template<>
// struct std::formatter<VkExtent2D>
// {
// constexpr auto parse(std::format_parse_context &context)
// {
// return context.begin();
// }
//
// auto format(const VkExtent2D &val, std::format_context &context) const
// {
// return std::format_to(context.out(), "{}, {}", val.width, val.height);
// }
// };
//
// inline auto operator==(VkExtent2D lhs, VkExtent2D rhs) -> bool
// {
// return lhs.width == rhs.width && lhs.height == rhs.height;
// }

View file

@ -15,6 +15,7 @@ extern PFN_vkGetDeviceProcAddr vk_get_device_proc_address;
extern PFN_vkDestroyDevice vk_destroy_device;
extern PFN_vkGetPhysicalDeviceFeatures vk_get_physical_device_features;
extern PFN_vkEnumerateDeviceExtensionProperties vk_enumerate_device_extension_properties;
extern PFN_vkGetPhysicalDeviceMemoryProperties vk_get_physical_device_memory_properties;
// extension instance functions
extern PFN_vkCmdBeginDebugUtilsLabelEXT vk_cmd_begin_debug_label;
@ -76,6 +77,15 @@ extern PFN_vkCmdDraw vk_cmd_draw;
extern PFN_vkCmdSetViewport vk_cmd_set_viewport;
extern PFN_vkCmdSetScissor vk_cmd_set_scissors;
extern PFN_vkCreateBuffer vk_create_buffer;
extern PFN_vkDestroyBuffer vk_destroy_buffer;
extern PFN_vkGetBufferMemoryRequirements vk_get_buffer_memory_requirements;
extern PFN_vkAllocateMemory vk_allocate_memory;
extern PFN_vkBindBufferMemory vk_bind_buffer_memory;
extern PFN_vkMapMemory vk_map_memory;
extern PFN_vkUnmapMemory vk_unmap_memory;
extern PFN_vkFreeMemory vk_free_memory;
extern PFN_vkResetCommandBuffer vk_reset_command_buffer;
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)

View file

@ -0,0 +1,29 @@
#pragma once
#include <renderer/backend/vk/data/buffer.hpp>
#include <renderer/frontend/data/buffer.hpp>
namespace lt::renderer {
[[nodiscard]] /* static */ auto IBuffer::create(
Api target_api,
class IDevice *device,
class IGpu *gpu,
const CreateInfo &info
) -> memory::Scope<IBuffer>
{
ensure(device, "Failed to create renderer::IBuffer: null device");
ensure(gpu, "Failed to create renderer::IBuffer: null gpu");
ensure(info.size > 0, "Failed to create renderer::IBuffer: null size");
switch (target_api)
{
case Api::vulkan: return memory::create_scope<vk::Buffer>(device, gpu, info);
case Api::none:
case Api::metal:
case Api::direct_x: throw std::runtime_error { "Invalid API" };
}
}
} // namespace lt::renderer

View file

@ -0,0 +1,55 @@
#pragma once
#include <memory/scope.hpp>
#include <renderer/api.hpp>
namespace lt::renderer {
class IBuffer
{
public:
enum Usage : uint8_t
{
vertex,
index,
};
struct CreateInfo
{
Usage usage;
size_t size;
std::string debug_name;
};
[[nodiscard]] static auto create(
Api target_api,
class IDevice *device,
class IGpu *gpu,
const CreateInfo &info
) -> memory::Scope<IBuffer>;
IBuffer() = default;
virtual ~IBuffer() = default;
IBuffer(IBuffer &&) = default;
IBuffer(const IBuffer &) = delete;
auto operator=(IBuffer &&) -> IBuffer & = default;
auto operator=(const IBuffer &) -> IBuffer & = delete;
[[nodiscard]] virtual auto map() -> std::span<std::byte> = 0;
virtual void unmap() = 0;
[[nodiscard]] virtual auto get_size() const -> size_t = 0;
private:
};
} // namespace lt::renderer

View file

@ -0,0 +1,112 @@
#include <renderer/frontend/data/buffer.hpp>
#include <renderer/test/utils.hpp>
using ::lt::renderer::IBuffer;
using enum ::lt::renderer::IMessenger::MessageSeverity;
Suite raii = "buffer_raii"_suite = [] {
Case { "happy path won't throw" } = [] {
auto fixture = FixtureDeviceSwapchain {};
ignore = IBuffer::create(
lt::renderer::Api::vulkan,
fixture.device(),
fixture.gpu(),
IBuffer::CreateInfo {
.usage = IBuffer::Usage::vertex,
.size = 1000u,
.debug_name = "",
}
);
expect_false(fixture.has_any_messages_of(error));
expect_false(fixture.has_any_messages_of(warning));
};
Case { "unhappy path throws" } = [] {
auto fixture = FixtureDeviceSwapchain {};
auto info = IBuffer::CreateInfo {
.usage = IBuffer::Usage::vertex,
.size = 10000u,
.debug_name = "",
};
expect_throw([&] {
ignore = IBuffer::create(lt::renderer::Api::vulkan, nullptr, fixture.gpu(), info);
});
expect_throw([&] {
ignore = IBuffer::create(lt::renderer::Api::vulkan, fixture.device(), nullptr, info);
});
expect_throw([&, info] mutable {
info.size = 0;
ignore = IBuffer::create(
lt::renderer::Api::vulkan,
fixture.device(),
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = IBuffer::create(
lt::renderer::Api::direct_x,
fixture.device(),
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = IBuffer::create(
lt::renderer::Api::metal,
fixture.device(),
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = IBuffer::create(
lt::renderer::Api::none,
fixture.device(),
fixture.gpu(),
info
);
});
/** Make sure the default-case was good */
ignore = IBuffer::create(lt::renderer::Api::vulkan, fixture.device(), fixture.gpu(), info);
expect_false(fixture.has_any_messages_of(error));
expect_false(fixture.has_any_messages_of(warning));
};
};
Suite mapping = "buffer_mapping"_suite = [] {
Case { "mapping" } = [] {
auto fixture = FixtureDeviceSwapchain {};
constexpr auto size = 1000u;
auto buffer = IBuffer::create(
lt::renderer::Api::vulkan,
fixture.device(),
fixture.gpu(),
IBuffer::CreateInfo {
.usage = IBuffer::Usage::vertex,
.size = size,
.debug_name = "",
}
);
auto map = buffer->map();
expect_eq(map.size(), size);
expect_not_nullptr(map.data());
expect_false(fixture.has_any_messages_of(error));
expect_false(fixture.has_any_messages_of(warning));
};
};

View file

@ -1 +0,0 @@

View file

@ -148,7 +148,6 @@ public:
);
}
[[nodiscard]] auto has_any_messages() const -> bool
{
return m_user_data->m_has_any_messages;

View file

@ -7,7 +7,6 @@
namespace lt::renderer {
class IMessenger
{
public: