2D scene rendering + image loading #15

Merged
burkkyy merged 18 commits from issue-14/2d-scene-support-spritecomponent-2d-render-system into main 2026-09-30 15:56:33 -07:00
9 changed files with 118 additions and 119 deletions
Showing only changes of commit 97ff85ea8a - Show all commits
+1
View File
@@ -24,6 +24,7 @@ target_compile_definitions(${ENGINE_NAME} PUBLIC
target_link_libraries(${ENGINE_NAME} PUBLIC target_link_libraries(${ENGINE_NAME} PUBLIC
vulkan vulkan
vma
shaderc shaderc
glfw glfw
glm glm
+18 -18
View File
@@ -28,26 +28,32 @@ Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
alignmentSize = getAlignment(instanceSize, minOffsetAlignment); alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
bufferSize = alignmentSize * instanceCount; bufferSize = alignmentSize * instanceCount;
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer, device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer,
memory); allocation);
} }
Buffer::~Buffer() { Buffer::~Buffer() {
unmap(); unmap();
this->device.get()->destroyBuffer(this->buffer); vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation);
this->device.get()->freeMemory(this->memory);
} }
// VMA always maps the whole allocation, so `size` only exists to keep the
// interface; `offset` is applied to the returned pointer.
vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) { vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) {
SOPHIA_ASSERT(buffer && memory, "Called map on buffer before create"); SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create");
(void)size;
vk::MemoryMapFlags emptyFlag{}; void* base = nullptr;
return this->device.get()->mapMemory(this->memory, offset, size, emptyFlag, vk::Result result = static_cast<vk::Result>(
&this->mapped); vmaMapMemory(this->device.getAllocator(), this->allocation, &base));
if (result == vk::Result::eSuccess) {
this->mapped = static_cast<char*>(base) + offset;
}
return result;
} }
void Buffer::unmap() { void Buffer::unmap() {
if (mapped) { if (mapped) {
this->device.get()->unmapMemory(this->memory); vmaUnmapMemory(this->device.getAllocator(), this->allocation);
mapped = nullptr; mapped = nullptr;
} }
} }
@@ -66,19 +72,13 @@ void Buffer::writeToBuffer(void* data, vk::DeviceSize size,
} }
vk::Result Buffer::flush(vk::DeviceSize size, vk::DeviceSize offset) { vk::Result Buffer::flush(vk::DeviceSize size, vk::DeviceSize offset) {
vk::MappedMemoryRange mappedRange = {}; return static_cast<vk::Result>(vmaFlushAllocation(
mappedRange.memory = memory; this->device.getAllocator(), this->allocation, offset, size));
mappedRange.offset = offset;
mappedRange.size = size;
return this->device.get()->flushMappedMemoryRanges(1, &mappedRange);
} }
vk::Result Buffer::invalidate(vk::DeviceSize size, vk::DeviceSize offset) { vk::Result Buffer::invalidate(vk::DeviceSize size, vk::DeviceSize offset) {
vk::MappedMemoryRange mappedRange = {}; return static_cast<vk::Result>(vmaInvalidateAllocation(
mappedRange.memory = memory; this->device.getAllocator(), this->allocation, offset, size));
mappedRange.offset = offset;
mappedRange.size = size;
return this->device.get()->invalidateMappedMemoryRanges(1, &mappedRange);
} }
vk::DescriptorBufferInfo Buffer::descriptorInfo(vk::DeviceSize size, vk::DescriptorBufferInfo Buffer::descriptorInfo(vk::DeviceSize size,
+1 -1
View File
@@ -55,7 +55,7 @@ class Buffer {
Device& device; Device& device;
void* mapped = nullptr; void* mapped = nullptr;
vk::Buffer buffer = VK_NULL_HANDLE; vk::Buffer buffer = VK_NULL_HANDLE;
vk::DeviceMemory memory = VK_NULL_HANDLE; VmaAllocation allocation = VK_NULL_HANDLE;
vk::DeviceSize bufferSize; vk::DeviceSize bufferSize;
uint32_t instanceCount; uint32_t instanceCount;
+43 -72
View File
@@ -69,10 +69,14 @@ Device::Device(Window* window)
} }
pickPhysicalDevice(); pickPhysicalDevice();
createLogicalDevice(); createLogicalDevice();
createAllocator();
createCommandPool(); createCommandPool();
} }
Device::~Device() { Device::~Device() {
vmaDestroyAllocator(this->allocator);
log::trace("destroyed VmaAllocator");
if (this->enableValidationLayers) { if (this->enableValidationLayers) {
destroyDebugUtilsMessengerEXT(this->instance.get(), this->debugMessenger, destroyDebugUtilsMessengerEXT(this->instance.get(), this->debugMessenger,
nullptr); nullptr);
@@ -88,22 +92,6 @@ Device::~Device() {
} }
} }
u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
vk::PhysicalDeviceMemoryProperties memoryProperties =
this->physicalDevice.getMemoryProperties();
for (u32 i = 0; i < memoryProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) &&
(memoryProperties.memoryTypes[i].propertyFlags & properties) ==
properties) {
return i;
}
}
log::fatal("failed to find suitable memory type");
throw std::runtime_error("failed to find suitable memory type");
}
vk::Format Device::findSupportedFormat( vk::Format Device::findSupportedFormat(
const std::vector<vk::Format>& candidates, vk::ImageTiling tiling, const std::vector<vk::Format>& candidates, vk::ImageTiling tiling,
vk::FormatFeatureFlags features) { vk::FormatFeatureFlags features) {
@@ -125,35 +113,26 @@ vk::Format Device::findSupportedFormat(
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags properties, vk::MemoryPropertyFlags properties,
vk::Buffer& buffer, vk::DeviceMemory& bufferMemory) { vk::Buffer& buffer, VmaAllocation& allocation) {
vk::BufferCreateInfo bufferInfo = {}; vk::BufferCreateInfo bufferInfo = {};
bufferInfo.size = size; bufferInfo.size = size;
bufferInfo.usage = usage; bufferInfo.usage = usage;
bufferInfo.sharingMode = vk::SharingMode::eExclusive; bufferInfo.sharingMode = vk::SharingMode::eExclusive;
try { VmaAllocationCreateInfo allocInfo = {};
buffer = this->device->createBuffer(bufferInfo); allocInfo.requiredFlags = static_cast<VkMemoryPropertyFlags>(properties);
} catch (const vk::SystemError& error) {
log::fatal("failed to create vertex buffer"); VkBuffer rawBuffer = VK_NULL_HANDLE;
throw std::runtime_error("failed to create vertex buffer"); VkResult result = vmaCreateBuffer(
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
&allocInfo, &rawBuffer, &allocation, nullptr);
if (result != VK_SUCCESS) {
log::fatal("failed to create buffer. Error: ",
vk::to_string(static_cast<vk::Result>(result)));
throw std::runtime_error("failed to create buffer");
} }
vk::MemoryRequirements memoryRequirements = buffer = rawBuffer;
this->device->getBufferMemoryRequirements(buffer);
vk::MemoryAllocateInfo allocInfo = {};
allocInfo.allocationSize = memoryRequirements.size;
allocInfo.memoryTypeIndex =
findMemoryType(memoryRequirements.memoryTypeBits, properties);
try {
bufferMemory = this->device->allocateMemory(allocInfo);
} catch (const vk::SystemError& error) {
log::fatal("failed to allocate vertex buffer memory");
throw std::runtime_error("failed to allocate vertex buffer memory");
}
this->device->bindBufferMemory(buffer, bufferMemory, 0);
} }
vk::CommandBuffer Device::beginSingleTimeCommands() { vk::CommandBuffer Device::beginSingleTimeCommands() {
@@ -220,43 +199,21 @@ void Device::copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer,
void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo, void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
vk::MemoryPropertyFlags properties, vk::MemoryPropertyFlags properties,
vk::Image& image, vk::Image& image, VmaAllocation& allocation) {
vk::DeviceMemory& imageMemory) { VmaAllocationCreateInfo allocInfo = {};
try { allocInfo.requiredFlags = static_cast<VkMemoryPropertyFlags>(properties);
vk::Result result = this->device->createImage(&imageInfo, nullptr, &image);
if (result != vk::Result::eSuccess) { VkImage rawImage = VK_NULL_HANDLE;
throw vk::SystemError(result); VkResult result = vmaCreateImage(
} this->allocator, &static_cast<const VkImageCreateInfo&>(imageInfo),
} catch (const vk::SystemError& error) { &allocInfo, &rawImage, &allocation, nullptr);
log::fatal("failed to create image. Error: ", error.what()); if (result != VK_SUCCESS) {
log::fatal("failed to create image. Error: ",
vk::to_string(static_cast<vk::Result>(result)));
throw std::runtime_error("failed to create image"); throw std::runtime_error("failed to create image");
} }
vk::MemoryRequirements memoryRequirements = image = rawImage;
this->device->getImageMemoryRequirements(image);
vk::MemoryAllocateInfo allocateInfo{};
allocateInfo.allocationSize = memoryRequirements.size;
allocateInfo.memoryTypeIndex =
findMemoryType(memoryRequirements.memoryTypeBits, properties);
try {
vk::Result result =
this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory);
if (result != vk::Result::eSuccess) {
throw vk::SystemError(result);
}
} catch (const vk::SystemError& error) {
log::fatal("failed to allocate image memory. Error: ", error.what());
throw std::runtime_error("failed to allocate image memory");
}
try {
this->device->bindImageMemory(image, imageMemory, 0);
} catch (const vk::SystemError& error) {
log::fatal("failed to bind image memory. Error: ", error.what());
throw std::runtime_error("failed to bind image memory");
}
} }
void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) { void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) {
@@ -514,6 +471,20 @@ void Device::createLogicalDevice() {
} }
} }
void Device::createAllocator() {
VmaAllocatorCreateInfo createInfo = {};
createInfo.vulkanApiVersion = SOPHIA_VULKAN_API_VERSION;
createInfo.instance = this->instance.get();
createInfo.physicalDevice = this->physicalDevice;
createInfo.device = this->device.get();
if (vmaCreateAllocator(&createInfo, &this->allocator) != VK_SUCCESS) {
log::fatal("failed to create VmaAllocator");
throw std::runtime_error("failed to create VmaAllocator");
}
log::trace("created VmaAllocator");
}
SwapchainSupportDetails Device::querySwapchainSupport( SwapchainSupportDetails Device::querySwapchainSupport(
vk::PhysicalDevice device) { vk::PhysicalDevice device) {
SwapchainSupportDetails details; SwapchainSupportDetails details;
+7 -4
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <imgui_impl_vulkan.h> #include <imgui_impl_vulkan.h>
#include <vk_mem_alloc.h>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -47,6 +48,7 @@ class Device {
void waitIdle() { this->device->waitIdle(); } void waitIdle() { this->device->waitIdle(); }
VmaAllocator getAllocator() const { return this->allocator; }
vk::CommandPool getCommandPool() const { return this->commandPool; } vk::CommandPool getCommandPool() const { return this->commandPool; }
vk::SurfaceKHR getSurface() const { return surface; } vk::SurfaceKHR getSurface() const { return surface; }
vk::Queue getGraphicsQueue() const { return graphicsQueue; } vk::Queue getGraphicsQueue() const { return graphicsQueue; }
@@ -60,15 +62,13 @@ class Device {
return querySwapchainSupport(this->physicalDevice); return querySwapchainSupport(this->physicalDevice);
} }
u32 findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties);
vk::Format findSupportedFormat(const std::vector<vk::Format>& candidates, vk::Format findSupportedFormat(const std::vector<vk::Format>& candidates,
vk::ImageTiling tiling, vk::ImageTiling tiling,
vk::FormatFeatureFlags features); vk::FormatFeatureFlags features);
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags properties, vk::Buffer& buffer, vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
vk::DeviceMemory& bufferMemory); VmaAllocation& allocation);
vk::CommandBuffer beginSingleTimeCommands(); vk::CommandBuffer beginSingleTimeCommands();
@@ -79,7 +79,7 @@ class Device {
void createImageWithInfo(const vk::ImageCreateInfo& imageInfo, void createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
vk::MemoryPropertyFlags properties, vk::Image& image, vk::MemoryPropertyFlags properties, vk::Image& image,
vk::DeviceMemory& imageMemory); VmaAllocation& allocation);
void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo); void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo);
@@ -100,6 +100,7 @@ class Device {
SwapchainSupportDetails querySwapchainSupport(vk::PhysicalDevice device); SwapchainSupportDetails querySwapchainSupport(vk::PhysicalDevice device);
void createLogicalDevice(); void createLogicalDevice();
void createAllocator();
void createCommandPool(); void createCommandPool();
vk::UniqueInstance instance; vk::UniqueInstance instance;
@@ -111,6 +112,8 @@ class Device {
vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE; vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE;
vk::UniqueDevice device; vk::UniqueDevice device;
VmaAllocator allocator = VK_NULL_HANDLE;
vk::Queue graphicsQueue; vk::Queue graphicsQueue;
vk::Queue presentQueue; vk::Queue presentQueue;
+34 -20
View File
@@ -3,11 +3,35 @@
#include <cstring> #include <cstring>
#include <sophia/util/logger.hpp> #include <sophia/util/logger.hpp>
#include <stdexcept> #include <stdexcept>
#include <utility>
#include "vulkan/device.hpp" #include "vulkan/device.hpp"
namespace sophia { namespace sophia {
Image::Image(Image&& other) noexcept
: device{other.device},
externalImage{other.externalImage},
image{std::exchange(other.image, nullptr)},
allocation{std::exchange(other.allocation, VK_NULL_HANDLE)},
view{std::move(other.view)},
sampler{std::move(other.sampler)},
format{other.format},
extent{other.extent},
aspectMask{other.aspectMask},
mipLevels{other.mipLevels},
arrayLayers{other.arrayLayers},
layout{other.layout} {}
Image::~Image() {
this->sampler.reset();
this->view.reset();
if (this->image) {
vmaDestroyImage(this->device.getAllocator(), this->image, this->allocation);
}
}
vk::ImageSubresourceRange Image::getSubresourceRange() const { vk::ImageSubresourceRange Image::getSubresourceRange() const {
return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0, return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0,
this->arrayLayers}; this->arrayLayers};
@@ -194,35 +218,26 @@ std::unique_ptr<Image> Image::Builder::build() {
imageInfo.sharingMode = vk::SharingMode::eExclusive; imageInfo.sharingMode = vk::SharingMode::eExclusive;
imageInfo.samples = this->samples; imageInfo.samples = this->samples;
result->image = this->device.get()->createImageUnique(imageInfo); this->device.createImageWithInfo(imageInfo, this->memoryProperties,
result->image, result->allocation);
vk::MemoryRequirements memoryRequirements =
this->device.get()->getImageMemoryRequirements(result->image.get());
vk::MemoryAllocateInfo allocInfo{};
allocInfo.allocationSize = memoryRequirements.size;
allocInfo.memoryTypeIndex = this->device.findMemoryType(
memoryRequirements.memoryTypeBits, this->memoryProperties);
result->memory = this->device.get()->allocateMemoryUnique(allocInfo);
this->device.get()->bindImageMemory(result->image.get(),
result->memory.get(), 0);
} }
if (!this->isExternalImage && this->initialData != nullptr) { if (!this->isExternalImage && this->initialData != nullptr) {
VmaAllocator allocator = this->device.getAllocator();
vk::Buffer stagingBuffer; vk::Buffer stagingBuffer;
vk::DeviceMemory stagingMemory; VmaAllocation stagingAllocation;
this->device.createBuffer(this->initialDataSize, this->device.createBuffer(this->initialDataSize,
vk::BufferUsageFlagBits::eTransferSrc, vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent, vk::MemoryPropertyFlagBits::eHostCoherent,
stagingBuffer, stagingMemory); stagingBuffer, stagingAllocation);
void* mapped = void* mapped = nullptr;
this->device.get()->mapMemory(stagingMemory, 0, this->initialDataSize); vmaMapMemory(allocator, stagingAllocation, &mapped);
memcpy(mapped, this->initialData, memcpy(mapped, this->initialData,
static_cast<size_t>(this->initialDataSize)); static_cast<size_t>(this->initialDataSize));
this->device.get()->unmapMemory(stagingMemory); vmaUnmapMemory(allocator, stagingAllocation);
vk::BufferImageCopy region{}; vk::BufferImageCopy region{};
region.bufferOffset = 0; region.bufferOffset = 0;
@@ -247,8 +262,7 @@ std::unique_ptr<Image> Image::Builder::build() {
} }
this->device.endSingleTimeCommands(commandBuffer); this->device.endSingleTimeCommands(commandBuffer);
this->device.get()->destroyBuffer(stagingBuffer); vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation);
this->device.get()->freeMemory(stagingMemory);
} }
if (this->createView) { if (this->createView) {
+7 -4
View File
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <vk_mem_alloc.h>
#include <memory> #include <memory>
#include <sophia/types.hpp> #include <sophia/types.hpp>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
@@ -16,11 +18,12 @@ class Image {
Image(const Image&) = delete; Image(const Image&) = delete;
Image& operator=(const Image&) = delete; Image& operator=(const Image&) = delete;
Image(Image&&) = default; Image(Image&& other) noexcept;
Image& operator=(Image&&) = delete; Image& operator=(Image&&) = delete;
~Image();
vk::Image get() const { vk::Image get() const {
return this->image ? this->image.get() : this->externalImage; return this->image ? this->image : this->externalImage;
} }
vk::ImageView getView() const { return this->view.get(); } vk::ImageView getView() const { return this->view.get(); }
vk::Sampler getSampler() const { return this->sampler.get(); } vk::Sampler getSampler() const { return this->sampler.get(); }
@@ -56,8 +59,8 @@ class Image {
Device& device; Device& device;
vk::Image externalImage; vk::Image externalImage;
vk::UniqueImage image; vk::Image image;
vk::UniqueDeviceMemory memory; VmaAllocation allocation = VK_NULL_HANDLE;
vk::UniqueImageView view; vk::UniqueImageView view;
vk::UniqueSampler sampler; vk::UniqueSampler sampler;
+2
View File
@@ -0,0 +1,2 @@
#define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>
+5
View File
@@ -17,6 +17,11 @@ target_link_libraries(vulkan INTERFACE
Vulkan::shaderc_combined Vulkan::shaderc_combined
) )
# 2. VMA (header-only, ships with the Vulkan SDK)
add_library(vma INTERFACE)
target_include_directories(vma INTERFACE ${Vulkan_INCLUDE_DIRS}/vma)
target_link_libraries(vma INTERFACE vulkan)
# 3. GLFW (static) # 3. GLFW (static)
option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF) option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF)
option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF) option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF)