/* 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 vk_buffer_memory_barriers_; /** * List of all extracted VkImageMemoryBarriers. These barriers will be referenced by * Barrier::image_memory_barriers. */ Vector 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 layered_attachments; struct TrackedImage { VkImage vk_image; VkImageLayout vk_image_layout; uint32_t layer; uint32_t layer_count; }; Vector 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 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 group_nodes_; /** Barriers that will be recorded just before the commands of a group are recorded. */ Vector group_pre_barriers_; /** Barriers that will be recorded after a group is recorded. */ Vector 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 nodes`. */ Vector node_pre_barriers_; /** List of all generated barriers. */ Vector barrier_list_; public: /** * Build execution groups and barriers. * This method should be performed when the resources are locked. */ void build_nodes(VKRenderGraph &render_graph, VKCommandBufferInterface &command_buffer, Span node_handles); /** * Record commands of the nodes provided by the `node_handles` parameter. The commands are * recorded into the given `command_buffer`. * * `build_nodes` needs to be called before calling with exact the same parameters. */ void record_commands(VKRenderGraph &render_graph, VKCommandBufferInterface &command_buffer, Span 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 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 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 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