renderer_vulkan: General cleanups

* Add a staging buffer to each texture. This is suboptimal since it
causes many small allocations, will only be here until I implement
a global staging buffer with memory tracking

* Make the task scheduler accept generic functions which allows for more
powerful resource control
This commit is contained in:
GPUCode
2022-05-05 16:55:33 +03:00
parent f6988d4a8e
commit ab9b84f721
7 changed files with 100 additions and 63 deletions

View File

@@ -11,49 +11,58 @@
namespace Vulkan { namespace Vulkan {
VKBuffer::~VKBuffer() VKBuffer::~VKBuffer() {
{
if (memory != nullptr) { if (memory != nullptr) {
g_vk_instace->GetDevice().unmapMemory(buffer_memory.get()); g_vk_instace->GetDevice().unmapMemory(buffer_memory);
} }
auto deleter = [this]() {
if (buffer) {
auto& device = g_vk_instace->GetDevice();
device.destroyBuffer(buffer);
device.freeMemory(buffer_memory);
device.destroyBufferView(buffer_view);
}
};
g_vk_task_scheduler->Schedule(deleter);
} }
void VKBuffer::Create(uint32_t byte_count, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage, vk::Format view_format) void VKBuffer::Create(u32 byte_count, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage,
{ vk::Format view_format) {
auto& device = g_vk_instace->GetDevice(); auto& device = g_vk_instace->GetDevice();
size = byte_count; size = byte_count;
vk::BufferCreateInfo bufferInfo({}, byte_count, usage); vk::BufferCreateInfo bufferInfo({}, byte_count, usage);
buffer = device.createBufferUnique(bufferInfo); buffer = device.createBuffer(bufferInfo);
auto mem_requirements = device.getBufferMemoryRequirements(buffer.get()); auto mem_requirements = device.getBufferMemoryRequirements(buffer);
auto memory_type_index = FindMemoryType(mem_requirements.memoryTypeBits, properties); auto memory_type_index = FindMemoryType(mem_requirements.memoryTypeBits, properties);
vk::MemoryAllocateInfo alloc_info(mem_requirements.size, memory_type_index); vk::MemoryAllocateInfo alloc_info(mem_requirements.size, memory_type_index);
buffer_memory = device.allocateMemoryUnique(alloc_info); buffer_memory = device.allocateMemory(alloc_info);
device.bindBufferMemory(buffer.get(), buffer_memory.get(), 0); device.bindBufferMemory(buffer, buffer_memory, 0);
// Optionally map the buffer to CPU memory // Optionally map the buffer to CPU memory
if (properties & vk::MemoryPropertyFlagBits::eHostVisible) if (properties & vk::MemoryPropertyFlagBits::eHostVisible) {
memory = device.mapMemory(buffer_memory.get(), 0, byte_count); memory = device.mapMemory(buffer_memory, 0, byte_count);
}
// Create buffer view for texel buffers // Create buffer view for texel buffers
if (usage & vk::BufferUsageFlagBits::eStorageTexelBuffer || usage & vk::BufferUsageFlagBits::eUniformTexelBuffer) if (usage & vk::BufferUsageFlagBits::eStorageTexelBuffer ||
{ usage & vk::BufferUsageFlagBits::eUniformTexelBuffer) {
vk::BufferViewCreateInfo view_info({}, buffer.get(), view_format, 0, byte_count); vk::BufferViewCreateInfo view_info({}, buffer, view_format, 0, byte_count);
buffer_view = device.createBufferViewUnique(view_info); buffer_view = device.createBufferView(view_info);
} }
} }
void VKBuffer::CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region) void VKBuffer::CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region) {
{
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer(); auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.copyBuffer(src_buffer.buffer.get(), dst_buffer.buffer.get(), region); command_buffer.copyBuffer(src_buffer.buffer, dst_buffer.buffer, region);
} }
uint32_t VKBuffer::FindMemoryType(uint32_t type_filter, vk::MemoryPropertyFlags properties) u32 VKBuffer::FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties) {
{
vk::PhysicalDeviceMemoryProperties mem_properties = g_vk_instace->GetPhysicalDevice().getMemoryProperties(); vk::PhysicalDeviceMemoryProperties mem_properties = g_vk_instace->GetPhysicalDevice().getMemoryProperties();
for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++)
@@ -67,4 +76,19 @@ uint32_t VKBuffer::FindMemoryType(uint32_t type_filter, vk::MemoryPropertyFlags
UNREACHABLE(); UNREACHABLE();
} }
void StagingBuffer::Create(u32 size) {
buffer.Create(size, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
vk::BufferUsageFlagBits::eTransferSrc);
}
u8* StagingBuffer::Request(u32 bytes) {
// Check if there is enough space left
if (buffer.GetSize() - end_offset >= bytes) {
u8* ptr = buffer.GetHostPointer() + end_offset;
end_offset += bytes;
// Schedule the memory to be freed
}
}
} }

View File

@@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <deque>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
#include "common/common_types.h" #include "common/common_types.h"
@@ -19,21 +20,23 @@ public:
~VKBuffer(); ~VKBuffer();
/// Create a generic Vulkan buffer object /// Create a generic Vulkan buffer object
void Create(uint32_t size, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage, vk::Format view_format = vk::Format::eUndefined); void Create(u32 size, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage,
vk::Format view_format = vk::Format::eUndefined);
/// Global utility functions used by other objects /// Global utility functions used by other objects
static uint32_t FindMemoryType(uint32_t type_filter, vk::MemoryPropertyFlags properties); static u32 FindMemoryType(u32 type_filter, vk::MemoryPropertyFlags properties);
static void CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region); static void CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region);
/// Return a pointer to the mapped memory if the buffer is host mapped /// Return a pointer to the mapped memory if the buffer is host mapped
u8* GetHostPointer() { return reinterpret_cast<u8*>(memory); } u8* GetHostPointer() { return reinterpret_cast<u8*>(memory); }
vk::Buffer& GetBuffer() { return buffer.get(); } vk::Buffer& GetBuffer() { return buffer; }
u32 GetSize() const { return size; }
private: private:
void* memory = nullptr; void* memory = nullptr;
vk::UniqueBuffer buffer; vk::Buffer buffer;
vk::UniqueDeviceMemory buffer_memory; vk::DeviceMemory buffer_memory;
vk::UniqueBufferView buffer_view; vk::BufferView buffer_view;
uint32_t size = 0; uint32_t size = 0;
}; };

View File

@@ -31,16 +31,11 @@ public:
void Shutdown(); void Shutdown();
// Public interface. // Public interface.
VKBuffer& GetTextureUploadBuffer() { return texture_upload_buffer; }
vk::Sampler GetSampler(const SamplerInfo& info); vk::Sampler GetSampler(const SamplerInfo& info);
vk::RenderPass GetRenderPass(vk::Format color_format, vk::Format depth_format, u32 multisamples, vk::AttachmentLoadOp load_op); vk::RenderPass GetRenderPass(vk::Format color_format, vk::Format depth_format, u32 multisamples, vk::AttachmentLoadOp load_op);
vk::PipelineCache GetPipelineCache() const { return pipeline_cache.get(); } vk::PipelineCache GetPipelineCache() const { return pipeline_cache.get(); }
private: private:
// Dummy image for samplers that are unbound
VKTexture dummy_texture;
VKBuffer texture_upload_buffer;
// Descriptor sets // Descriptor sets
std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_LAYOUT_COUNT> descriptor_layouts; std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_LAYOUT_COUNT> descriptor_layouts;
vk::UniquePipelineLayout pipeline_layout; vk::UniquePipelineLayout pipeline_layout;

View File

@@ -98,8 +98,9 @@ void VKTaskScheduler::Submit(bool present, bool wait_completion) {
// When the task completes the timeline will increment to the task id // When the task completes the timeline will increment to the task id
vk::TimelineSemaphoreSubmitInfo timeline_info({}, task.task_id); vk::TimelineSemaphoreSubmitInfo timeline_info({}, task.task_id);
std::array<vk::Semaphore, 2> signal_semaphores = { timeline.get(), present_semaphore.get() };
vk::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput; vk::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, present_semaphore.get(), &timeline_info); vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, signal_semaphores, &timeline_info);
// Wait for new swapchain image // Wait for new swapchain image
if (present) { if (present) {
@@ -108,7 +109,6 @@ void VKTaskScheduler::Submit(bool present, bool wait_completion) {
} }
// Submit the command buffer // Submit the command buffer
submit_info.setSignalSemaphores(timeline.get());
g_vk_instace->GetGraphicsQueue().submit(submit_info); g_vk_instace->GetGraphicsQueue().submit(submit_info);
// Present the image when rendering has finished // Present the image when rendering has finished
@@ -125,14 +125,12 @@ void VKTaskScheduler::Submit(bool present, bool wait_completion) {
BeginTask(); BeginTask();
} }
void VKTaskScheduler::ScheduleDestroy(auto object) { void VKTaskScheduler::Schedule(std::function<void()> func) {
auto& resources = tasks[current_task]; auto& task = tasks[current_task];
auto deleter = [object]() { g_vk_instace->GetDevice().destroy(object); }; task.cleanups.push_back(func);
resources.cleanups.push_back(deleter);
} }
void VKTaskScheduler::BeginTask() void VKTaskScheduler::BeginTask() {
{
// Move to the next command buffer. // Move to the next command buffer.
u32 next_task_index = (current_task + 1) % CONCURRENT_TASK_COUNT; u32 next_task_index = (current_task + 1) % CONCURRENT_TASK_COUNT;
auto& task = tasks[next_task_index]; auto& task = tasks[next_task_index];
@@ -151,6 +149,7 @@ void VKTaskScheduler::BeginTask()
// Reset upload command buffer state // Reset upload command buffer state
current_task = next_task_index; current_task = next_task_index;
task.task_id = current_task_id++;
} }
std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler; std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;

View File

@@ -42,7 +42,7 @@ public:
vk::CommandBuffer GetCommandBuffer(); vk::CommandBuffer GetCommandBuffer();
/// Returns the task id that the CPU is recording /// Returns the task id that the CPU is recording
u64 GetCPUTick() const { return tasks[current_task].task_id; } u64 GetCPUTick() const { return current_task_id; }
/// Returns the last known task id to have completed execution in the GPU /// Returns the last known task id to have completed execution in the GPU
u64 GetGPUTick() const { return g_vk_instace->GetDevice().getSemaphoreCounterValue(timeline.get()); } u64 GetGPUTick() const { return g_vk_instace->GetDevice().getSemaphoreCounterValue(timeline.get()); }
@@ -52,7 +52,7 @@ public:
void SyncToGPU(u64 task_index); void SyncToGPU(u64 task_index);
/// Schedule a vulkan object for destruction when the GPU no longer uses it /// Schedule a vulkan object for destruction when the GPU no longer uses it
void ScheduleDestroy(auto object); void Schedule(std::function<void()> func);
/// Submit the current work batch and move to the next frame /// Submit the current work batch and move to the next frame
void Submit(bool present = true, bool wait_completion = false); void Submit(bool present = true, bool wait_completion = false);
@@ -69,7 +69,7 @@ private:
vk::UniqueSemaphore timeline; vk::UniqueSemaphore timeline;
vk::UniqueCommandPool command_pool; vk::UniqueCommandPool command_pool;
u64 last_completed_task_id = 0; u64 current_task_id = 1;
// Each task contains unique resources // Each task contains unique resources
std::array<Task, CONCURRENT_TASK_COUNT> tasks; std::array<Task, CONCURRENT_TASK_COUNT> tasks;

View File

@@ -15,26 +15,25 @@ VKTexture::~VKTexture() {
// Make sure to unbind the texture before destroying it // Make sure to unbind the texture before destroying it
g_vk_state->UnbindTexture(this); g_vk_state->UnbindTexture(this);
if (cleanup_image && texture) { auto deleter = [this]() {
g_vk_task_scheduler->ScheduleDestroy(texture); auto& device = g_vk_instace->GetDevice();
}
if (texture) {
if (cleanup_image) {
device.destroyImage(texture);
}
device.destroyImageView(texture_view);
device.freeMemory(texture_memory);
}
};
// Schedule deletion of the texture after it's no longer used // Schedule deletion of the texture after it's no longer used
// by the GPU // by the GPU
if (texture_view) { g_vk_task_scheduler->Schedule(deleter);
g_vk_task_scheduler->ScheduleDestroy(texture_view);
}
if (texture_memory) {
g_vk_task_scheduler->ScheduleDestroy(texture_memory);
}
if (texture_view) {
g_vk_task_scheduler->ScheduleDestroy(texture_view);
}
} }
void VKTexture::Create(const Info& info) { void VKTexture::Create(const Info& info, bool make_staging) {
auto& device = g_vk_instace->GetDevice(); auto& device = g_vk_instace->GetDevice();
texture_info = info; texture_info = info;
@@ -87,6 +86,12 @@ void VKTexture::Create(const Info& info) {
vk::ImageViewCreateInfo view_info({}, texture, info.view_type, texture_info.format, {}, vk::ImageViewCreateInfo view_info({}, texture, info.view_type, texture_info.format, {},
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
texture_view = device.createImageView(view_info); texture_view = device.createImageView(view_info);
// Create staging buffer
if (make_staging) {
staging.Create(image_size, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
vk::BufferUsageFlagBits::eTransferSrc);
}
} }
void VKTexture::Adopt(vk::Image image, vk::ImageViewCreateInfo view_info) { void VKTexture::Adopt(vk::Image image, vk::ImageViewCreateInfo view_info) {
@@ -181,10 +186,14 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer&
} }
void VKTexture::CopyPixels(std::span<u32> new_pixels) { void VKTexture::CopyPixels(std::span<u32> new_pixels) {
if (!staging.GetHostPointer()) {
LOG_ERROR(Render_Vulkan, "Cannot copy pixels without staging buffer!");
}
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer(); auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Copy pixels to staging buffer // Copy pixels to staging buffer
std::memcpy(g_vk_res_cache->GetTextureUploadBuffer().GetHostPointer(), std::memcpy(staging.GetHostPointer(),
new_pixels.data(), new_pixels.size() * channels); new_pixels.data(), new_pixels.size() * channels);
vk::BufferImageCopy region(0, 0, 0, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), 0, vk::BufferImageCopy region(0, 0, 0, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), 0,
@@ -194,7 +203,6 @@ void VKTexture::CopyPixels(std::span<u32> new_pixels) {
// Transition image to transfer format // Transition image to transfer format
TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer); TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
auto& staging = g_vk_res_cache->GetTextureUploadBuffer();
command_buffer.copyBufferToImage(staging.GetBuffer(), texture, vk::ImageLayout::eTransferDstOptimal, regions); command_buffer.copyBufferToImage(staging.GetBuffer(), texture, vk::ImageLayout::eTransferDstOptimal, regions);
// Prepare for shader reads // Prepare for shader reads
@@ -250,9 +258,14 @@ void VKTexture::Fill(Common::Rectangle<u32> region, glm::vec2 depth_stencil) {
} }
VKFramebuffer::~VKFramebuffer() { VKFramebuffer::~VKFramebuffer() {
if (framebuffer) { auto deleter = [this]() {
g_vk_task_scheduler->ScheduleDestroy(framebuffer); if (framebuffer) {
} auto& device = g_vk_instace->GetDevice();
device.destroyFramebuffer(framebuffer);
}
};
g_vk_task_scheduler->Schedule(deleter);
} }
void VKFramebuffer::Create(const Info& info) { void VKFramebuffer::Create(const Info& info) {

View File

@@ -42,7 +42,7 @@ public:
~VKTexture(); ~VKTexture();
/// Create a new Vulkan texture object /// Create a new Vulkan texture object
void Create(const Info& info); void Create(const Info& info, bool staging = false);
/// Create a non-owning texture object, usefull for image object /// Create a non-owning texture object, usefull for image object
/// from the swapchain that are managed by another object /// from the swapchain that are managed by another object
@@ -79,6 +79,9 @@ private:
vk::ImageView texture_view; vk::ImageView texture_view;
vk::DeviceMemory texture_memory; vk::DeviceMemory texture_memory;
u32 channels; u32 channels;
// TODO: Make a global staging buffer
VKBuffer staging;
}; };
enum Attachments { enum Attachments {