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 {
VKBuffer::~VKBuffer()
{
VKBuffer::~VKBuffer() {
if (memory != nullptr) {
g_vk_instace->GetDevice().unmapMemory(buffer_memory.get());
}
g_vk_instace->GetDevice().unmapMemory(buffer_memory);
}
void VKBuffer::Create(uint32_t byte_count, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage, vk::Format view_format)
{
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(u32 byte_count, vk::MemoryPropertyFlags properties, vk::BufferUsageFlags usage,
vk::Format view_format) {
auto& device = g_vk_instace->GetDevice();
size = byte_count;
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);
vk::MemoryAllocateInfo alloc_info(mem_requirements.size, memory_type_index);
buffer_memory = device.allocateMemoryUnique(alloc_info);
device.bindBufferMemory(buffer.get(), buffer_memory.get(), 0);
buffer_memory = device.allocateMemory(alloc_info);
device.bindBufferMemory(buffer, buffer_memory, 0);
// Optionally map the buffer to CPU memory
if (properties & vk::MemoryPropertyFlagBits::eHostVisible)
memory = device.mapMemory(buffer_memory.get(), 0, byte_count);
if (properties & vk::MemoryPropertyFlagBits::eHostVisible) {
memory = device.mapMemory(buffer_memory, 0, byte_count);
}
// Create buffer view for texel buffers
if (usage & vk::BufferUsageFlagBits::eStorageTexelBuffer || usage & vk::BufferUsageFlagBits::eUniformTexelBuffer)
{
vk::BufferViewCreateInfo view_info({}, buffer.get(), view_format, 0, byte_count);
buffer_view = device.createBufferViewUnique(view_info);
if (usage & vk::BufferUsageFlagBits::eStorageTexelBuffer ||
usage & vk::BufferUsageFlagBits::eUniformTexelBuffer) {
vk::BufferViewCreateInfo view_info({}, buffer, view_format, 0, byte_count);
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();
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();
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();
}
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 <vector>
#include <deque>
#include <vulkan/vulkan.hpp>
#include "common/common_types.h"
@@ -19,21 +20,23 @@ public:
~VKBuffer();
/// 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
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);
/// Return a pointer to the mapped memory if the buffer is host mapped
u8* GetHostPointer() { return reinterpret_cast<u8*>(memory); }
vk::Buffer& GetBuffer() { return buffer.get(); }
vk::Buffer& GetBuffer() { return buffer; }
u32 GetSize() const { return size; }
private:
void* memory = nullptr;
vk::UniqueBuffer buffer;
vk::UniqueDeviceMemory buffer_memory;
vk::UniqueBufferView buffer_view;
vk::Buffer buffer;
vk::DeviceMemory buffer_memory;
vk::BufferView buffer_view;
uint32_t size = 0;
};

View File

@@ -31,16 +31,11 @@ public:
void Shutdown();
// Public interface.
VKBuffer& GetTextureUploadBuffer() { return texture_upload_buffer; }
vk::Sampler GetSampler(const SamplerInfo& info);
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(); }
private:
// Dummy image for samplers that are unbound
VKTexture dummy_texture;
VKBuffer texture_upload_buffer;
// Descriptor sets
std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_LAYOUT_COUNT> descriptor_layouts;
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
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::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
if (present) {
@@ -108,7 +109,6 @@ void VKTaskScheduler::Submit(bool present, bool wait_completion) {
}
// Submit the command buffer
submit_info.setSignalSemaphores(timeline.get());
g_vk_instace->GetGraphicsQueue().submit(submit_info);
// Present the image when rendering has finished
@@ -125,14 +125,12 @@ void VKTaskScheduler::Submit(bool present, bool wait_completion) {
BeginTask();
}
void VKTaskScheduler::ScheduleDestroy(auto object) {
auto& resources = tasks[current_task];
auto deleter = [object]() { g_vk_instace->GetDevice().destroy(object); };
resources.cleanups.push_back(deleter);
void VKTaskScheduler::Schedule(std::function<void()> func) {
auto& task = tasks[current_task];
task.cleanups.push_back(func);
}
void VKTaskScheduler::BeginTask()
{
void VKTaskScheduler::BeginTask() {
// Move to the next command buffer.
u32 next_task_index = (current_task + 1) % CONCURRENT_TASK_COUNT;
auto& task = tasks[next_task_index];
@@ -151,6 +149,7 @@ void VKTaskScheduler::BeginTask()
// Reset upload command buffer state
current_task = next_task_index;
task.task_id = current_task_id++;
}
std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;

View File

@@ -42,7 +42,7 @@ public:
vk::CommandBuffer GetCommandBuffer();
/// 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
u64 GetGPUTick() const { return g_vk_instace->GetDevice().getSemaphoreCounterValue(timeline.get()); }
@@ -52,7 +52,7 @@ public:
void SyncToGPU(u64 task_index);
/// 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
void Submit(bool present = true, bool wait_completion = false);
@@ -69,7 +69,7 @@ private:
vk::UniqueSemaphore timeline;
vk::UniqueCommandPool command_pool;
u64 last_completed_task_id = 0;
u64 current_task_id = 1;
// Each task contains unique resources
std::array<Task, CONCURRENT_TASK_COUNT> tasks;

View File

@@ -15,26 +15,25 @@ VKTexture::~VKTexture() {
// Make sure to unbind the texture before destroying it
g_vk_state->UnbindTexture(this);
if (cleanup_image && texture) {
g_vk_task_scheduler->ScheduleDestroy(texture);
auto deleter = [this]() {
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
// by the GPU
if (texture_view) {
g_vk_task_scheduler->ScheduleDestroy(texture_view);
g_vk_task_scheduler->Schedule(deleter);
}
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();
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::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
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) {
@@ -181,10 +186,14 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer&
}
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();
// Copy pixels to staging buffer
std::memcpy(g_vk_res_cache->GetTextureUploadBuffer().GetHostPointer(),
std::memcpy(staging.GetHostPointer(),
new_pixels.data(), new_pixels.size() * channels);
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
TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
auto& staging = g_vk_res_cache->GetTextureUploadBuffer();
command_buffer.copyBufferToImage(staging.GetBuffer(), texture, vk::ImageLayout::eTransferDstOptimal, regions);
// Prepare for shader reads
@@ -250,9 +258,14 @@ void VKTexture::Fill(Common::Rectangle<u32> region, glm::vec2 depth_stencil) {
}
VKFramebuffer::~VKFramebuffer() {
auto deleter = [this]() {
if (framebuffer) {
g_vk_task_scheduler->ScheduleDestroy(framebuffer);
auto& device = g_vk_instace->GetDevice();
device.destroyFramebuffer(framebuffer);
}
};
g_vk_task_scheduler->Schedule(deleter);
}
void VKFramebuffer::Create(const Info& info) {

View File

@@ -42,7 +42,7 @@ public:
~VKTexture();
/// 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
/// from the swapchain that are managed by another object
@@ -79,6 +79,9 @@ private:
vk::ImageView texture_view;
vk::DeviceMemory texture_memory;
u32 channels;
// TODO: Make a global staging buffer
VKBuffer staging;
};
enum Attachments {