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