From 6d2e2a0bb28496a8c25b441009bdd2a1a1e72d81 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 19 Dec 2021 13:39:08 +0200 Subject: [PATCH] Multiplex streaming buffer contents on Vulkan This ensures that the data stays valid until the draw commands using it have finished executing. --- source/backends/opengl/buffer_backend.cpp | 2 +- source/backends/opengl/buffer_backend.h | 2 ++ source/backends/vulkan/buffer_backend.cpp | 8 ++++- source/backends/vulkan/buffer_backend.h | 2 ++ source/backends/vulkan/commands_backend.cpp | 17 ++++++----- source/backends/vulkan/commands_backend.h | 2 +- .../backends/vulkan/pipelinestate_backend.cpp | 20 +++++++++++-- .../backends/vulkan/pipelinestate_backend.h | 2 +- source/backends/vulkan/program_backend.cpp | 2 +- source/core/buffer.cpp | 2 +- source/core/buffer.h | 2 ++ source/core/bufferable.cpp | 29 ++++++++++++++----- source/core/bufferable.h | 2 +- source/render/programdata.cpp | 5 ++-- source/render/programdata.h | 2 +- source/render/renderer.cpp | 10 +++---- 16 files changed, 76 insertions(+), 33 deletions(-) diff --git a/source/backends/opengl/buffer_backend.cpp b/source/backends/opengl/buffer_backend.cpp index 13944a86..6417633b 100644 --- a/source/backends/opengl/buffer_backend.cpp +++ b/source/backends/opengl/buffer_backend.cpp @@ -39,7 +39,7 @@ OpenGLBuffer::~OpenGLBuffer() void OpenGLBuffer::allocate() { - size_t size = static_cast(this)->size; + size_t size = static_cast(this)->get_total_size(); if(ARB_buffer_storage) { diff --git a/source/backends/opengl/buffer_backend.h b/source/backends/opengl/buffer_backend.h index 3d76c01a..06c128af 100644 --- a/source/backends/opengl/buffer_backend.h +++ b/source/backends/opengl/buffer_backend.h @@ -22,6 +22,8 @@ protected: void allocate(); void sub_data(size_t, size_t, const void *); + unsigned get_multiplicity() const { return 1; } + bool can_map() const { return true; } void *map(); bool unmap(); diff --git a/source/backends/vulkan/buffer_backend.cpp b/source/backends/vulkan/buffer_backend.cpp index 548f0a51..5439548f 100644 --- a/source/backends/vulkan/buffer_backend.cpp +++ b/source/backends/vulkan/buffer_backend.cpp @@ -37,7 +37,7 @@ void VulkanBuffer::allocate() VkBufferCreateInfo buffer_info = { }; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buffer_info.size = self.size; + buffer_info.size = self.get_total_size(); buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT|VK_BUFFER_USAGE_TRANSFER_DST_BIT|VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT| VK_BUFFER_USAGE_INDEX_BUFFER_BIT|VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; @@ -72,6 +72,12 @@ void VulkanBuffer::sub_data(size_t off, size_t sz, const void *d) tq.finalize_transfer(staging); } +unsigned VulkanBuffer::get_multiplicity() const +{ + BufferUsage usage = static_cast(this)->usage; + return (usage==STREAMING ? device.get_n_frames_in_flight() : 1); +} + bool VulkanBuffer::can_map() const { return static_cast(this)->usage==STREAMING; diff --git a/source/backends/vulkan/buffer_backend.h b/source/backends/vulkan/buffer_backend.h index 3fcb2176..5902704f 100644 --- a/source/backends/vulkan/buffer_backend.h +++ b/source/backends/vulkan/buffer_backend.h @@ -29,6 +29,8 @@ protected: void allocate(); void sub_data(std::size_t, std::size_t, const void *); + unsigned get_multiplicity() const; + bool can_map() const; void *map(); bool unmap(); diff --git a/source/backends/vulkan/commands_backend.cpp b/source/backends/vulkan/commands_backend.cpp index b7a342de..bf8ee3f1 100644 --- a/source/backends/vulkan/commands_backend.cpp +++ b/source/backends/vulkan/commands_backend.cpp @@ -31,11 +31,12 @@ VulkanCommands::~VulkanCommands() void VulkanCommands::begin_buffer(VkRenderPass render_pass) { - if(!current_pool) + if(frame_index>=command_pools.size()) throw invalid_operation("VulkanCommands::begin_buffer"); const VulkanFunctions &vk = device.get_functions(); + CommandPool *current_pool = &command_pools[frame_index]; if(!current_pool->in_use) { current_pool->fence.reset(); @@ -176,15 +177,15 @@ void VulkanCommands::begin_frame(unsigned index) { const VulkanFunctions &vk = device.get_functions(); - unsigned pool_index = index%device.get_n_frames_in_flight(); - if(pool_index>=command_pools.size()) + frame_index = index%device.get_n_frames_in_flight(); + if(frame_index>=command_pools.size()) { - command_pools.reserve(pool_index+1); - for(unsigned i=command_pools.size(); iin_use) { current_pool->fence.wait(); @@ -220,7 +221,7 @@ void VulkanCommands::submit_frame(Semaphore *wait_sem, Semaphore *signal_sem) submit_info.signalSemaphoreCount = (signal_sem ? 1 : 0); submit_info.pSignalSemaphores = &vk_signal_sem; - vk.QueueSubmit(1, &submit_info, current_pool->fence.handle); + vk.QueueSubmit(1, &submit_info, command_pools[frame_index].fence.handle); primary_buffer = 0; } @@ -258,7 +259,7 @@ void VulkanCommands::draw_instanced(const Batch &batch, unsigned count) begin_render_pass(false, 0); pipeline_state->refresh(); - pipeline_state->apply(pass_buffer, fb_is_swapchain); + pipeline_state->apply(pass_buffer, frame_index, fb_is_swapchain); unsigned first_index = batch.get_offset()/batch.get_index_size(); vk.CmdDrawIndexed(pass_buffer, batch.size(), count, first_index, 0, 0); } diff --git a/source/backends/vulkan/commands_backend.h b/source/backends/vulkan/commands_backend.h index 3f2c3be9..5fcae882 100644 --- a/source/backends/vulkan/commands_backend.h +++ b/source/backends/vulkan/commands_backend.h @@ -43,7 +43,7 @@ protected: Device &device; std::vector command_pools; - CommandPool *current_pool = 0; + unsigned frame_index = 0; VkCommandBuffer primary_buffer = 0; VkCommandBuffer pass_buffer = 0; const PipelineState *pipeline_state = 0; diff --git a/source/backends/vulkan/pipelinestate_backend.cpp b/source/backends/vulkan/pipelinestate_backend.cpp index a6c9d8ce..172386d6 100644 --- a/source/backends/vulkan/pipelinestate_backend.cpp +++ b/source/backends/vulkan/pipelinestate_backend.cpp @@ -327,7 +327,7 @@ unsigned VulkanPipelineState::fill_descriptor_writes(unsigned index, vectorsType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write_ptr->dstBinding = u.binding&0xFFFFF; write_ptr->descriptorCount = 1; - write_ptr->descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write_ptr->descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; write_ptr->pBufferInfo = buffer_ptr; ++buffer_ptr; @@ -357,7 +357,7 @@ unsigned VulkanPipelineState::fill_descriptor_writes(unsigned index, vector(this); const VulkanFunctions &vk = device.get_functions(); @@ -382,7 +382,21 @@ void VulkanPipelineState::apply(VkCommandBuffer command_buffer, bool negative_vi } if(!descriptor_set_handles.empty()) - vk.CmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, self.shprog->layout_handle, 0, descriptor_set_handles.size(), descriptor_set_handles.data(), 0, 0); + { + vector dynamic_offsets; + dynamic_offsets.reserve(self.uniform_blocks.size()); + for(const PipelineState::BoundUniformBlock &u: self.uniform_blocks) + if(u.used && u.binding>=0) + { + if(u.buffer->get_usage()==STREAMING) + dynamic_offsets.push_back(frame*u.buffer->get_size()); + else + dynamic_offsets.push_back(0); + } + + vk.CmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, self.shprog->layout_handle, + 0, descriptor_set_handles.size(), descriptor_set_handles.data(), dynamic_offsets.size(), dynamic_offsets.data()); + } VkViewport viewport = { }; if(self.viewport) diff --git a/source/backends/vulkan/pipelinestate_backend.h b/source/backends/vulkan/pipelinestate_backend.h index 19f7f902..82f664c1 100644 --- a/source/backends/vulkan/pipelinestate_backend.h +++ b/source/backends/vulkan/pipelinestate_backend.h @@ -33,7 +33,7 @@ protected: VkDescriptorSetLayout get_descriptor_set_layout(unsigned) const; unsigned fill_descriptor_writes(unsigned, std::vector &) const; - void apply(VkCommandBuffer, bool) const; + void apply(VkCommandBuffer, unsigned, bool) const; }; using PipelineStateBackend = VulkanPipelineState; diff --git a/source/backends/vulkan/program_backend.cpp b/source/backends/vulkan/program_backend.cpp index 60e96698..50d77263 100644 --- a/source/backends/vulkan/program_backend.cpp +++ b/source/backends/vulkan/program_backend.cpp @@ -119,7 +119,7 @@ void VulkanProgram::finalize_uniforms() bindings.emplace_back(); VkDescriptorSetLayoutBinding &binding = bindings.back(); binding.binding = b.bind_point; - binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; binding.descriptorCount = 1; binding.stageFlags = VK_SHADER_STAGE_ALL; binding.pImmutableSamplers = 0; diff --git a/source/core/buffer.cpp b/source/core/buffer.cpp index 4a36937e..ed0c5523 100644 --- a/source/core/buffer.cpp +++ b/source/core/buffer.cpp @@ -34,7 +34,7 @@ void Buffer::sub_data(size_t off, size_t sz, const void *d) { if(size==0) throw invalid_operation("Buffer::sub_data"); - if(off>size || off+sz>size) + if(off>get_total_size() || off%size+sz>size) throw out_of_range("Buffer::sub_data"); BufferBackend::sub_data(off, sz, d); diff --git a/source/core/buffer.h b/source/core/buffer.h index 17678fd4..8aba95b1 100644 --- a/source/core/buffer.h +++ b/source/core/buffer.h @@ -58,6 +58,8 @@ public: void sub_data(std::size_t, std::size_t, const void *); std::size_t get_size() const { return size; } + using BufferBackend::get_multiplicity; + std::size_t get_total_size() const { return size*get_multiplicity(); } BufferUsage get_usage() const { return usage; } void require_size(std::size_t) const; diff --git a/source/core/bufferable.cpp b/source/core/bufferable.cpp index 502c270c..69588ddd 100644 --- a/source/core/bufferable.cpp +++ b/source/core/bufferable.cpp @@ -128,14 +128,20 @@ void Bufferable::update_offset() void Bufferable::mark_dirty() { - dirty = true; + dirty = 0xFF; } -void Bufferable::upload_data(char *target) const +void Bufferable::upload_data(unsigned frame, char *target) const { if(!buffer) throw invalid_operation("Bufferable::upload_data"); + unsigned multi_buf = buffer->get_multiplicity(); + frame %= multi_buf; + uint8_t mask = 1<sub_data(offset, data_size, get_data_pointer()); - dirty = false; + buffer->sub_data(frame*buffer->get_size()+offset, data_size, get_data_pointer()); + dirty &= ~mask; + if(!(dirty&((1<get_multiplicity()>1) + throw invalid_operation("Bufferable::create_async_updater"); + return new AsyncUpdater(*this); } @@ -168,14 +183,14 @@ Bufferable::AsyncUpdater::~AsyncUpdater() void Bufferable::AsyncUpdater::upload_data() { - bufferable.upload_data(mapped_address+bufferable.offset); + bufferable.upload_data(0, mapped_address+bufferable.offset); // Update all bufferables in the same buffer at once for(const Bufferable *b=bufferable.prev_in_buffer; b; b=b->prev_in_buffer) if(b->dirty) - b->upload_data(mapped_address+b->offset); + b->upload_data(0, mapped_address+b->offset); for(const Bufferable *b=bufferable.next_in_buffer; b; b=b->next_in_buffer) if(b->dirty) - b->upload_data(mapped_address+b->offset); + b->upload_data(0, mapped_address+b->offset); } } // namespace GL diff --git a/source/core/bufferable.h b/source/core/bufferable.h index 25d29fd8..b2b25765 100644 --- a/source/core/bufferable.h +++ b/source/core/bufferable.h @@ -63,7 +63,7 @@ public: std::size_t get_required_buffer_size(bool = false) const; /** Uploads new data into the buffer if necessary. */ - void refresh(unsigned f) const { if(dirty&(1<::const_iterator ProgramData::prepare_program(c return prog_begin; } -void ProgramData::apply(const Program &prog, PipelineState &state) const +void ProgramData::apply(const Program &prog, PipelineState &state, unsigned frame) const { auto prog_begin = prepare_program(prog); ReflectData::LayoutHash prog_hash = prog_begin->prog_hash; + for(auto i=prog_begin+1; (i!=programs.end() && i->prog_hash==prog_hash); ++i) if(i->block) { state.set_uniform_block(i->bind_point, i->block); if(i->bind_point>=0) - i->block->refresh(); + i->block->refresh(frame); } } diff --git a/source/render/programdata.h b/source/render/programdata.h index f1c5996e..5d674a38 100644 --- a/source/render/programdata.h +++ b/source/render/programdata.h @@ -258,7 +258,7 @@ private: public: /** Creates or updates UniformBlocks for a specific program if necessary, then sets them to the PipelineState. */ - void apply(const Program &, PipelineState &) const; + void apply(const Program &, PipelineState &, unsigned) const; void set_debug_name(const std::string &); }; diff --git a/source/render/renderer.cpp b/source/render/renderer.cpp index 946f0100..d3124f0d 100644 --- a/source/render/renderer.cpp +++ b/source/render/renderer.cpp @@ -247,7 +247,7 @@ void Renderer::clear(const ClearValue *values) void Renderer::draw(const Batch &batch) { apply_state(); - batch.refresh(); + batch.refresh(frame_index); pipeline_state.set_primitive_type(batch.get_type()); commands.use_pipeline(&pipeline_state); commands.draw(batch); @@ -256,7 +256,7 @@ void Renderer::draw(const Batch &batch) void Renderer::draw_instanced(const Batch &batch, unsigned count) { apply_state(); - batch.refresh(); + batch.refresh(frame_index); pipeline_state.set_primitive_type(batch.get_type()); commands.use_pipeline(&pipeline_state); commands.draw_instanced(batch, count); @@ -323,7 +323,7 @@ void Renderer::apply_state() shdata_stack.erase(shdata_stack.begin()+state.shdata_count, shdata_stack.end()); for(const BoundProgramData &d: shdata_stack) { - d.shdata->apply(*state.shprog, pipeline_state); + d.shdata->apply(*state.shprog, pipeline_state, frame_index); d.generation = d.shdata->get_generation(); } changed &= ~SHADER_DATA; @@ -332,9 +332,9 @@ void Renderer::apply_state() if(state.vertex_setup) { if(const VertexArray *array = state.vertex_setup->get_vertex_array()) - array->refresh(); + array->refresh(frame_index); if(const VertexArray *array = state.vertex_setup->get_instance_array()) - array->refresh(); + array->refresh(frame_index); } pipeline_state.set_vertex_setup(state.vertex_setup); -- 2.43.0