Vulkan: Add node to update buffers

This change adds the option to update a buffer via the render
graph via `vkCmdUpdateBuffer`. This is only enabled for
uniform buffers as they are small and aligned/sized correctly.

Pull Request: https://projects.blender.org/blender/blender/pulls/128416
This commit is contained in:
Jeroen Bakker
2024-10-01 14:22:56 +02:00
parent 01e96b3422
commit 4d8581c7ee
15 changed files with 144 additions and 16 deletions

View File

@@ -294,6 +294,7 @@ set(VULKAN_SRC
vulkan/render_graph/nodes/vk_pipeline_data.hh
vulkan/render_graph/nodes/vk_reset_query_pool_node.hh
vulkan/render_graph/nodes/vk_synchronization_node.hh
vulkan/render_graph/nodes/vk_update_buffer_node.hh
vulkan/render_graph/nodes/vk_update_mipmaps_node.hh
vulkan/render_graph/vk_command_buffer_wrapper.hh
vulkan/render_graph/vk_command_builder.hh

View File

@@ -42,6 +42,7 @@ enum class VKNodeType {
FILL_BUFFER,
RESET_QUERY_POOL,
SYNCHRONIZATION,
UPDATE_BUFFER,
UPDATE_MIPMAPS,
};
@@ -114,6 +115,9 @@ BLI_INLINE std::ostream &operator<<(std::ostream &os, const VKNodeType node_type
case VKNodeType::SYNCHRONIZATION:
os << "SYNCHRONIZATION";
break;
case VKNodeType::UPDATE_BUFFER:
os << "UPDATE_BUFFER";
break;
case VKNodeType::UPDATE_MIPMAPS:
os << "UPDATE_MIPMAPS";
break;

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKUpdateBufferData {
VkBuffer dst_buffer;
VkDeviceSize dst_offset;
VkDeviceSize data_size;
void *data;
};
class VKUpdateBufferNode : public VKNodeInfo<VKNodeType::UPDATE_BUFFER,
VKUpdateBufferData,
VKUpdateBufferData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::BUFFER> {
public:
/**
* Update the node data with the data inside create_info.
*
* Has been implemented as a template to ensure all node specific data
* (`VK*Data`/`VK*CreateInfo`) types can be included in the same header file as the logic. The
* actual node data (`VKRenderGraphNode` includes all header files.)
*/
template<typename Node> static void set_node_data(Node &node, const CreateInfo &create_info)
{
node.update_buffer = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphNodeLinks &node_links,
const CreateInfo &create_info) override
{
ResourceWithStamp dst_resource = resources.get_buffer_and_increase_stamp(
create_info.dst_buffer);
node_links.outputs.append({dst_resource, VK_ACCESS_TRANSFER_WRITE_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.update_buffer(data.dst_buffer, data.dst_offset, data.data_size, data.data);
}
void free_data(Data &data)
{
MEM_freeN(data.data);
data.data = nullptr;
}
};
} // namespace blender::gpu::render_graph

View File

@@ -189,6 +189,21 @@ class CommandBufferLog : public VKCommandBufferInterface {
log_.append(ss.str());
}
void update_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize data_size,
const void * /*p_data*/) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "update_buffer(";
ss << "dst_buffer=" << to_string(dst_buffer);
ss << ", dst_offset=" << dst_offset;
ss << ", data_size=" << data_size;
ss << ")";
ss << std::endl;
log_.append(ss.str());
}
void copy_buffer(VkBuffer src_buffer,
VkBuffer dst_buffer,
uint32_t region_count,

View File

@@ -189,6 +189,14 @@ void VKCommandBufferWrapper::dispatch_indirect(VkBuffer buffer, VkDeviceSize off
vkCmdDispatchIndirect(vk_command_buffer_, buffer, offset);
}
void VKCommandBufferWrapper::update_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize data_size,
const void *p_data)
{
vkCmdUpdateBuffer(vk_command_buffer_, dst_buffer, dst_offset, data_size, p_data);
}
void VKCommandBufferWrapper::copy_buffer(VkBuffer src_buffer,
VkBuffer dst_buffer,
uint32_t region_count,

View File

@@ -55,6 +55,10 @@ class VKCommandBufferInterface {
uint32_t group_count_y,
uint32_t group_count_z) = 0;
virtual void dispatch_indirect(VkBuffer buffer, VkDeviceSize offset) = 0;
virtual void update_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize data_size,
const void *p_data) = 0;
virtual void copy_buffer(VkBuffer src_buffer,
VkBuffer dst_buffer,
uint32_t region_count,
@@ -183,6 +187,10 @@ class VKCommandBufferWrapper : public VKCommandBufferInterface {
uint32_t stride) override;
void dispatch(uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) override;
void dispatch_indirect(VkBuffer buffer, VkDeviceSize offset) override;
void update_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize data_size,
const void *p_data) override;
void copy_buffer(VkBuffer src_buffer,
VkBuffer dst_buffer,
uint32_t region_count,

View File

@@ -197,6 +197,7 @@ class VKRenderGraph : public NonCopyable {
ADD_NODE(VKDrawIndexedIndirectNode)
ADD_NODE(VKDrawIndirectNode)
ADD_NODE(VKResetQueryPoolNode)
ADD_NODE(VKUpdateBufferNode)
ADD_NODE(VKUpdateMipmapsNode)
#undef ADD_NODE

View File

@@ -29,6 +29,7 @@
#include "nodes/vk_fill_buffer_node.hh"
#include "nodes/vk_reset_query_pool_node.hh"
#include "nodes/vk_synchronization_node.hh"
#include "nodes/vk_update_buffer_node.hh"
#include "nodes/vk_update_mipmaps_node.hh"
namespace blender::gpu::render_graph {
@@ -69,6 +70,7 @@ struct VKRenderGraphNode {
VKFillBufferNode::Data fill_buffer;
VKResetQueryPoolNode::Data reset_query_pool;
VKSynchronizationNode::Data synchronization;
VKUpdateBufferNode::Data update_buffer;
VKUpdateMipmapsNode::Data update_mipmaps;
};
@@ -158,6 +160,8 @@ struct VKRenderGraphNode {
return VKResetQueryPoolNode::pipeline_stage;
case VKNodeType::SYNCHRONIZATION:
return VKSynchronizationNode::pipeline_stage;
case VKNodeType::UPDATE_BUFFER:
return VKUpdateBufferNode::pipeline_stage;
case VKNodeType::UPDATE_MIPMAPS:
return VKUpdateMipmapsNode::pipeline_stage;
}
@@ -195,6 +199,7 @@ struct VKRenderGraphNode {
BUILD_COMMANDS(VKNodeType::END_QUERY, VKEndQueryNode, end_query)
BUILD_COMMANDS(VKNodeType::END_RENDERING, VKEndRenderingNode, end_rendering)
BUILD_COMMANDS(VKNodeType::FILL_BUFFER, VKFillBufferNode, fill_buffer)
BUILD_COMMANDS(VKNodeType::UPDATE_BUFFER, VKUpdateBufferNode, update_buffer)
BUILD_COMMANDS(VKNodeType::COPY_BUFFER, VKCopyBufferNode, copy_buffer)
BUILD_COMMANDS(
VKNodeType::COPY_BUFFER_TO_IMAGE, VKCopyBufferToImageNode, copy_buffer_to_image)
@@ -237,6 +242,7 @@ struct VKRenderGraphNode {
FREE_DATA(
VKNodeType::DRAW_INDEXED_INDIRECT, VKDrawIndexedIndirectNode, draw_indexed_indirect)
FREE_DATA(VKNodeType::DRAW_INDIRECT, VKDrawIndirectNode, draw_indirect)
FREE_DATA(VKNodeType::UPDATE_BUFFER, VKUpdateBufferNode, update_buffer)
#undef FREE_DATA
case VKNodeType::UNUSED:

View File

@@ -100,13 +100,23 @@ bool VKBuffer::create(size_t size_in_bytes,
return true;
}
void VKBuffer::update(const void *data) const
void VKBuffer::update_immediately(const void *data) const
{
BLI_assert_msg(is_mapped(), "Cannot update a non-mapped buffer.");
memcpy(mapped_memory_, data, size_in_bytes_);
flush();
}
void VKBuffer::update_render_graph(VKContext &context, void *data) const
{
BLI_assert(size_in_bytes_ <= 65536 && size_in_bytes_ % 4 == 0);
render_graph::VKUpdateBufferNode::CreateInfo update_buffer = {};
update_buffer.dst_buffer = vk_buffer_;
update_buffer.data_size = size_in_bytes_;
update_buffer.data = data;
context.render_graph.add_node(update_buffer);
}
void VKBuffer::flush() const
{
const VKDevice &device = VKBackend::get().device;

View File

@@ -38,7 +38,13 @@ class VKBuffer : public NonCopyable {
VkBufferUsageFlags buffer_usage,
bool is_host_visible = true);
void clear(VKContext &context, uint32_t clear_value);
void update(const void *data) const;
void update_immediately(const void *data) const;
/**
* Update the buffer as part of the render graph evaluation. The ownership of data will be
* transferred to the render graph and should have been allocated using guarded alloc.
*/
void update_render_graph(VKContext &context, void *data) const;
void flush() const;
void read(VKContext &context, void *data) const;

View File

@@ -201,7 +201,7 @@ void VKDevice::init_dummy_buffer()
/* Default dummy buffer. Set the 4th element to 1 to fix missing orcos. */
float data[16] = {
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
dummy_buffer.update(static_cast<void *>(data));
dummy_buffer.update_immediately(static_cast<void *>(data));
}
void VKDevice::init_glsl_patch()

View File

@@ -31,7 +31,7 @@ void VKIndexBuffer::ensure_updated()
VKContext &context = *VKContext::get();
VKStagingBuffer staging_buffer(buffer_, VKStagingBuffer::Direction::HostToDevice);
staging_buffer.host_buffer_get().update(data_);
staging_buffer.host_buffer_get().update_immediately(data_);
staging_buffer.copy_to_device(context);
MEM_SAFE_FREE(data_);
}

View File

@@ -25,7 +25,7 @@ void VKStorageBuffer::update(const void *data)
VKContext &context = *VKContext::get();
ensure_allocated();
VKStagingBuffer staging_buffer(buffer_, VKStagingBuffer::Direction::HostToDevice);
staging_buffer.host_buffer_get().update(data);
staging_buffer.host_buffer_get().update_immediately(data);
staging_buffer.copy_to_device(context);
}

View File

@@ -20,15 +20,12 @@ void VKUniformBuffer::update(const void *data)
if (!buffer_.is_allocated()) {
allocate();
}
/* TODO: when buffer is mapped and newly created we should use `buffer_.update_immediately`. */
void *data_copy = MEM_mallocN(size_in_bytes_, __func__);
memcpy(data_copy, data, size_in_bytes_);
VKContext &context = *VKContext::get();
if (buffer_.is_mapped()) {
buffer_.update(data);
}
else {
VKStagingBuffer staging_buffer(buffer_, VKStagingBuffer::Direction::HostToDevice);
staging_buffer.host_buffer_get().update(data);
staging_buffer.copy_to_device(context);
}
buffer_.update_render_graph(context, data_copy);
}
void VKUniformBuffer::allocate()
@@ -58,8 +55,10 @@ void VKUniformBuffer::ensure_updated()
/* Upload attached data, during bind time. */
if (data_) {
update(data_);
MEM_SAFE_FREE(data_);
/* TODO: when buffer is mapped and newly created we should use `buffer_.update_immediately`. */
VKContext &context = *VKContext::get();
buffer_.update_render_graph(context, std::move(data_));
data_ = nullptr;
}
}

View File

@@ -130,7 +130,7 @@ void VKVertexBuffer::upload_data_direct(const VKBuffer &host_buffer)
host_buffer.flush();
}
else {
host_buffer.update(data_);
host_buffer.update_immediately(data_);
}
}