361 lines
13 KiB
C++
361 lines
13 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "BLI_set.hh"
|
|
|
|
#include "vk_common.hh"
|
|
#include "vk_render_graph_node.hh"
|
|
#include "vk_scheduler.hh"
|
|
|
|
namespace blender::gpu::render_graph {
|
|
class VKRenderGraph;
|
|
|
|
struct LayeredImageBinding {
|
|
VkImage vk_image;
|
|
VkImageLayout vk_image_layout;
|
|
uint32_t layer;
|
|
uint32_t layer_count;
|
|
};
|
|
|
|
/**
|
|
* Build the command buffer for sending to the device queue.
|
|
*
|
|
* Determine which nodes needs to be scheduled, Then for each node generate the needed pipeline
|
|
* barriers and commands.
|
|
*/
|
|
class VKCommandBuilder {
|
|
private:
|
|
/**
|
|
* List of all extracted VkBufferMemoryBarriers. These barriers will be referenced by
|
|
* Barrier::buffer_memory_barriers.
|
|
*/
|
|
Vector<VkBufferMemoryBarrier> vk_buffer_memory_barriers_;
|
|
/**
|
|
* List of all extracted VkImageMemoryBarriers. These barriers will be referenced by
|
|
* Barrier::image_memory_barriers.
|
|
*/
|
|
Vector<VkImageMemoryBarrier> vk_image_memory_barriers_;
|
|
|
|
struct Barrier {
|
|
/** Index range into `VKCommandBuilder::vk_buffer_memory_barriers_` */
|
|
IndexRange buffer_memory_barriers;
|
|
/** Index range into `VKCommandBuilder::vk_image_memory_barriers_` */
|
|
IndexRange image_memory_barriers;
|
|
|
|
VkPipelineStageFlags src_stage_mask = VK_PIPELINE_STAGE_NONE;
|
|
VkPipelineStageFlags dst_stage_mask = VK_PIPELINE_STAGE_NONE;
|
|
|
|
bool is_empty() const
|
|
{
|
|
return buffer_memory_barriers.is_empty() && image_memory_barriers.is_empty();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An image can consist out of layers. In some cases each layer can get different
|
|
* VkImageLayouts. To not complicate the global state tracker we track layered
|
|
* resources only when building the barriers.
|
|
*/
|
|
struct LayeredImageTracker {
|
|
/**
|
|
* Local reference to the active command builder.
|
|
*
|
|
* The reference is used to add VkImageMemoryBarrier's to the command builder.
|
|
*/
|
|
VKCommandBuilder &command_builder;
|
|
LayeredImageTracker(VKCommandBuilder &command_builder) : command_builder(command_builder) {}
|
|
|
|
/**
|
|
* All layered attachments of the last rendering scope (VKNodeType::BEGIN_RENDERING).
|
|
*
|
|
* When binding layer from these images we expect that they aren't used as attachment and can
|
|
* be transitioned into a different image layout. These image layouts are stored in
|
|
* `layered_bindings`.
|
|
*/
|
|
Set<VkImage> layered_attachments;
|
|
|
|
struct TrackedImage {
|
|
VkImage vk_image;
|
|
VkImageLayout vk_image_layout;
|
|
uint32_t layer;
|
|
uint32_t layer_count;
|
|
};
|
|
Vector<TrackedImage> layered_bindings;
|
|
|
|
/**
|
|
* Update the layered attachments list when beginning a new render scope.
|
|
*
|
|
* node_handle should be a handle that points to a VKNodeType::BEGIN_RENDERING.
|
|
* Any attachments that are layered will be added to the `layered_attachments` list.
|
|
*/
|
|
void begin(const VKRenderGraph &render_graph, NodeHandle node_handle);
|
|
|
|
/**
|
|
* Is layered tracking enabled for the given vk_image.
|
|
*/
|
|
inline bool contains(VkImage vk_image) const
|
|
{
|
|
return layered_attachments.contains(vk_image);
|
|
}
|
|
|
|
/**
|
|
* Ensure the layout of a layer.
|
|
*
|
|
* - `old_layout` should be the expected layout of the full image.
|
|
*/
|
|
void update(VkImage vk_image,
|
|
uint32_t layer,
|
|
uint32_t layer_count,
|
|
VkImageLayout old_layout,
|
|
VkImageLayout new_layout,
|
|
Barrier &r_barrier);
|
|
|
|
/**
|
|
* End layer tracking.
|
|
*
|
|
* All modified layers (layer_tracking_update) will be changed back to the image layout of
|
|
* the texture (most likely a `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`).
|
|
*
|
|
* Render suspension/resuming will not work after calling this method.
|
|
*/
|
|
void end(Barrier &r_barrier, bool use_local_read);
|
|
|
|
/**
|
|
* Suspend layer tracking
|
|
*
|
|
* Temporarily suspend layer tracking. This transits all modified layers back to its original
|
|
* layout.
|
|
* NOTE: Only call this method when you the rendering will be resumed, otherwise use
|
|
* `layer_tracking_end`.
|
|
*/
|
|
void suspend(Barrier &r_barrier, bool use_local_read);
|
|
|
|
/**
|
|
* Resume suspended layer tracking.
|
|
*
|
|
* Resume suspended layer tracking. This transits all registered layers back to its modified
|
|
* state.
|
|
*/
|
|
void resume(Barrier &r_barrier, bool use_local_read);
|
|
};
|
|
|
|
/**
|
|
* Index range of the nodes of a group.
|
|
*
|
|
* The indexes are to `VKScheduler::result_` that is passed along `Span<NodeHandle> nodes` of
|
|
* `build_nodes`.
|
|
*/
|
|
using GroupNodes = IndexRange;
|
|
|
|
/**
|
|
* Index range into barrier_list_;
|
|
*/
|
|
using Barriers = IndexRange;
|
|
using BarrierIndex = int64_t;
|
|
|
|
/** Per group store the indices of the nodes. */
|
|
Vector<GroupNodes> group_nodes_;
|
|
/** Barriers that will be recorded just before the commands of a group are recorded. */
|
|
Vector<Barriers> group_pre_barriers_;
|
|
/** Barriers that will be recorded after a group is recorded. */
|
|
Vector<Barriers> group_post_barriers_;
|
|
/**
|
|
* Barriers that will be recorded just before the commands of a specific node are recorded. The
|
|
* barriers are stored per NodeHandle and in the same order as `Span<NodeHandle> nodes`.
|
|
*/
|
|
Vector<Barriers> node_pre_barriers_;
|
|
|
|
/** List of all generated barriers. */
|
|
Vector<Barrier> barrier_list_;
|
|
|
|
public:
|
|
/**
|
|
* Build the commands of the nodes provided by the `node_handles` parameter. The commands are
|
|
* recorded into the given `command_buffer`.
|
|
*
|
|
* Pre-condition:
|
|
* - `command_buffer` must not be in initial state according to
|
|
* https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html#commandbuffers-lifecycle
|
|
*
|
|
* Post-condition:
|
|
* - `command_buffer` will be in executable state according to
|
|
* https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html#commandbuffers-lifecycle
|
|
*/
|
|
void build_nodes(VKRenderGraph &render_graph,
|
|
VKCommandBufferInterface &command_buffer,
|
|
Span<NodeHandle> node_handles);
|
|
|
|
private:
|
|
/**
|
|
* Split the node_handles in logical groups.
|
|
*
|
|
* A new group is created when the next node is switching from data/compute to graphics and each
|
|
* data/compute is also put in its own group.
|
|
*/
|
|
void groups_init(const VKRenderGraph &render_graph, Span<NodeHandle> node_handles);
|
|
|
|
/**
|
|
* Extract the memory/buffer/image barriers from the command groups and add them to the pre/post
|
|
* barriers.
|
|
*
|
|
* This process is single threaded as resource states change during the extraction process. The
|
|
* result of this function would allow the sub builders to be built in parallel.
|
|
*/
|
|
void groups_extract_barriers(VKRenderGraph &render_graph,
|
|
Span<NodeHandle> node_handles,
|
|
bool supports_local_read);
|
|
|
|
/**
|
|
* Record all the commands for all the groups to the command buffer.
|
|
*/
|
|
void groups_build_commands(VKRenderGraph &render_graph,
|
|
VKCommandBufferInterface &command_buffer,
|
|
Span<NodeHandle> node_handles);
|
|
|
|
/**
|
|
* Build the pipeline barriers that should be recorded before any other commands of the node
|
|
* group the given node is part of is being recorded.
|
|
*/
|
|
void build_pipeline_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags pipeline_stage,
|
|
LayeredImageTracker &layered_tracker,
|
|
Barrier &r_barrier,
|
|
bool within_rendering = false);
|
|
void reset_barriers(Barrier &r_barrier);
|
|
void send_pipeline_barriers(VKCommandBufferInterface &command_buffer,
|
|
const Barrier &barrier,
|
|
bool within_rendering);
|
|
|
|
void add_buffer_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags node_stages,
|
|
Barrier &r_barrier);
|
|
void add_buffer_barrier(VkBuffer vk_buffer,
|
|
Barrier &r_barrier,
|
|
VkAccessFlags src_access_mask,
|
|
VkAccessFlags dst_access_mask);
|
|
void add_buffer_read_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags node_stages,
|
|
Barrier &r_barrier);
|
|
void add_buffer_write_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags node_stages,
|
|
Barrier &r_barrier);
|
|
|
|
void add_image_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags node_stages,
|
|
LayeredImageTracker &layered_tracker,
|
|
Barrier &r_barrier,
|
|
bool within_rendering);
|
|
void add_image_barrier(VkImage vk_image,
|
|
Barrier &r_barrier,
|
|
VkAccessFlags src_access_mask,
|
|
VkAccessFlags dst_access_mask,
|
|
VkImageLayout old_image_layout,
|
|
VkImageLayout new_image_layout,
|
|
VkImageAspectFlags aspect_mask,
|
|
uint32_t layer_base = 0,
|
|
uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS);
|
|
void add_image_read_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags node_stages,
|
|
LayeredImageTracker &layered_tracker,
|
|
Barrier &r_barrier,
|
|
bool within_rendering);
|
|
void add_image_write_barriers(VKRenderGraph &render_graph,
|
|
NodeHandle node_handle,
|
|
VkPipelineStageFlags node_stages,
|
|
LayeredImageTracker &layered_tracker,
|
|
Barrier &r_barrier,
|
|
bool within_rendering);
|
|
|
|
struct DebugGroups {
|
|
/**
|
|
* Index of the active debug_group. Points to an element in
|
|
* `VKRenderGraph.debug_.used_groups`.
|
|
*/
|
|
int64_t active_debug_group_id = -1;
|
|
/** Current level of debug groups. (number of nested debug groups). */
|
|
int debug_level = 0;
|
|
};
|
|
|
|
/**
|
|
* Ensure that the debug group associated with the given node_handle is activated.
|
|
*
|
|
* When activating it determines how to walk from the current debug group to the to be activated
|
|
* debug group by performing end/begin commands on the command buffer.
|
|
*
|
|
* This ensures that when nodes are reordered that they still appear in the right debug group.
|
|
*/
|
|
void activate_debug_group(VKRenderGraph &render_graph,
|
|
VKCommandBufferInterface &command_buffer,
|
|
DebugGroups &debug_groups,
|
|
NodeHandle node_handle);
|
|
|
|
/**
|
|
* Make sure no debugging groups are active anymore.
|
|
*/
|
|
void finish_debug_groups(VKCommandBufferInterface &command_buffer, DebugGroups &debug_groups);
|
|
|
|
private:
|
|
/**
|
|
* Update the layered attachments list when beginning a new render scope.
|
|
*/
|
|
void layer_tracking_begin(const VKRenderGraph &render_graph, NodeHandle node_handle);
|
|
|
|
/**
|
|
* Ensure the layout of a layer.
|
|
*
|
|
* - `old_layout` should be the expected layout of the full image.
|
|
*/
|
|
void layer_tracking_update(VkImage vk_image,
|
|
uint32_t layer,
|
|
uint32_t layer_count,
|
|
VkImageLayout old_layout,
|
|
VkImageLayout new_layout);
|
|
|
|
/**
|
|
* End layer tracking.
|
|
*
|
|
* All modified layers (layer_tracking_update) will be changed back to the image layout of
|
|
* the texture (most likely a `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL`).
|
|
*
|
|
* Render suspension/resuming will not work after calling this method.
|
|
*/
|
|
void layer_tracking_end(VKCommandBufferInterface &command_buffer);
|
|
|
|
/**
|
|
* Suspend layer tracking
|
|
*
|
|
* Temporarily suspend layer tracking. This transits all modified layers back to its original
|
|
* layout.
|
|
* NOTE: Only call this method when you the rendering will be resumed, otherwise use
|
|
* `layer_tracking_end`.
|
|
*/
|
|
void layer_tracking_suspend(VKCommandBufferInterface &command_buffer);
|
|
|
|
/**
|
|
* Resume suspended layer tracking.
|
|
*
|
|
* Resume suspended layer tracking. This transits all registered layers back to its modified
|
|
* state.
|
|
*/
|
|
void layer_tracking_resume(VKCommandBufferInterface &command_buffer);
|
|
|
|
bool node_has_input_attachments(const VKRenderGraph &render_graph, NodeHandle node);
|
|
|
|
std::string to_string_barrier(const Barrier &barrier);
|
|
};
|
|
|
|
} // namespace blender::gpu::render_graph
|