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:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
@@ -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 {
|
||||
|
Reference in New Issue
Block a user