Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

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 VKBeginQueryData {
VkQueryPool vk_query_pool;
uint32_t query_index;
VkQueryControlFlags vk_query_control_flags;
};
/**
* Begin query
*
* - Contains logic to copy relevant data to the VKRenderGraphNode.
* - Determine read/write resource dependencies.
* - Add commands to a command builder.
*/
class VKBeginQueryNode : public VKNodeInfo<VKNodeType::BEGIN_QUERY,
VKBeginQueryData,
VKBeginQueryData,
VK_PIPELINE_STAGE_NONE,
VKResourceType::NONE> {
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, typename Storage>
void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.begin_query = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker & /*resources*/,
VKRenderGraphLinks & /*links*/,
const CreateInfo & /*create_info*/) override
{
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.begin_query(data.vk_query_pool, data.query_index, data.vk_query_control_flags);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,141 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKBeginRenderingData {
VkRenderingAttachmentInfo color_attachments[8];
VkRenderingAttachmentInfo depth_attachment;
VkRenderingAttachmentInfo stencil_attachment;
VkRenderingInfoKHR vk_rendering_info;
};
struct VKBeginRenderingCreateInfo {
VKBeginRenderingData node_data = {};
const VKResourceAccessInfo &resources;
VKBeginRenderingCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
/**
* Begin rendering node
*
* - Contains logic to copy relevant data to the VKRenderGraphNode.
* - Determine read/write resource dependencies.
* - Add commands to a command builder.
*/
class VKBeginRenderingNode : public VKNodeInfo<VKNodeType::BEGIN_RENDERING,
VKBeginRenderingCreateInfo,
VKBeginRenderingData,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
BLI_assert_msg(ELEM(create_info.node_data.vk_rendering_info.pColorAttachments,
nullptr,
create_info.node_data.color_attachments),
"When create_info.node_data.vk_rendering_info.pColorAttachments points to "
"something, it should point to create_info.node_data.color_attachments.");
BLI_assert_msg(ELEM(create_info.node_data.vk_rendering_info.pDepthAttachment,
nullptr,
&create_info.node_data.depth_attachment),
"When create_info.node_data.vk_rendering_info.pDepthAttachment points to "
"something, it should point to create_info.node_data.depth_attachment.");
BLI_assert_msg(ELEM(create_info.node_data.vk_rendering_info.pStencilAttachment,
nullptr,
&create_info.node_data.stencil_attachment),
"When create_info.node_data.vk_rendering_info.pStencilAttachment points to "
"something, it should point to create_info.node_data.stencil_attachment.");
node.storage_index = storage.begin_rendering.append_and_get_index(create_info.node_data);
/* NOTE: pointers in vk_rendering_info will be set to the correct location just before sending
* to the command buffer. In the meantime these pointers are invalid.
* VKRenderingAttachmentInfo's should be used instead. */
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
/* Localize pointers just before sending to the command buffer. Pointer can (and will) change
* as they are stored in a union which is stored in a vector. When the vector reallocates,
* the pointers will become invalid. */
if (data.vk_rendering_info.pColorAttachments) {
data.vk_rendering_info.pColorAttachments = data.color_attachments;
}
if (data.vk_rendering_info.pDepthAttachment) {
data.vk_rendering_info.pDepthAttachment = &data.depth_attachment;
}
if (data.vk_rendering_info.pStencilAttachment) {
data.vk_rendering_info.pStencilAttachment = &data.stencil_attachment;
}
command_buffer.begin_rendering(&data.vk_rendering_info);
}
/**
* Reconfigure the vk_rendering_info to be restarted.
*
* When a render scope is restarted the clear/load ops needs to load in the previous stored
* results.
*/
static void reconfigure_for_restart(VKBeginRenderingData &begin_rendering_data)
{
auto reconfigure_attachment = [](VkRenderingAttachmentInfo &rendering_attachment) {
if (ELEM(rendering_attachment.loadOp,
VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_LOAD_OP_DONT_CARE))
{
rendering_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
}
};
if (begin_rendering_data.vk_rendering_info.pStencilAttachment != nullptr) {
reconfigure_attachment(begin_rendering_data.stencil_attachment);
}
if (begin_rendering_data.vk_rendering_info.pDepthAttachment != nullptr) {
reconfigure_attachment(begin_rendering_data.depth_attachment);
}
for (VkRenderingAttachmentInfo &color_attachment : MutableSpan<VkRenderingAttachmentInfo>(
begin_rendering_data.color_attachments,
begin_rendering_data.vk_rendering_info.colorAttachmentCount))
{
reconfigure_attachment(color_attachment);
}
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,85 @@
/* 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 VKBlitImageData {
VkImage src_image;
VkImage dst_image;
VkImageBlit region;
VkFilter filter;
};
/**
* Blit Image Node
*
* - Contains logic to copy relevant data to the VKRenderGraphNode.
* - Determine read/write resource dependencies.
* - Add commands to a command builder.
*/
class VKBlitImageNode : public VKNodeInfo<VKNodeType::BLIT_IMAGE,
VKBlitImageData,
VKBlitImageData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.blit_image.append_and_get_index(create_info);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp src_resource = resources.get_image(create_info.src_image);
ResourceWithStamp dst_resource = resources.get_image_and_increase_stamp(create_info.dst_image);
links.images.append({{src_resource, VK_ACCESS_TRANSFER_READ_BIT},
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_ASPECT_COLOR_BIT});
links.images.append({{dst_resource, VK_ACCESS_TRANSFER_WRITE_BIT},
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_ASPECT_COLOR_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.blit_image(data.src_image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
data.dst_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&data.region,
data.filter);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,67 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKClearAttachmentsData {
uint32_t attachment_count;
VkClearAttachment attachments[8];
VkClearRect vk_clear_rect;
};
class VKClearAttachmentsNode : public VKNodeInfo<VKNodeType::CLEAR_ATTACHMENTS,
VKClearAttachmentsData,
VKClearAttachmentsData,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.clear_attachments.append_and_get_index(create_info);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker & /*resources*/,
VKRenderGraphLinks & /*links*/,
const CreateInfo & /*create_info*/) override
{
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.clear_attachments(
data.attachment_count, data.attachments, 1, &data.vk_clear_rect);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,71 @@
/* 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 VKClearColorImageData {
VkImage vk_image;
VkClearColorValue vk_clear_color_value;
VkImageSubresourceRange vk_image_subresource_range;
};
class VKClearColorImageNode : public VKNodeInfo<VKNodeType::CLEAR_COLOR_IMAGE,
VKClearColorImageData,
VKClearColorImageData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.clear_color_image = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp resource = resources.get_image_and_increase_stamp(create_info.vk_image);
links.images.append({{resource, VK_ACCESS_TRANSFER_WRITE_BIT},
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_ASPECT_COLOR_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.clear_color_image(data.vk_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
&data.vk_clear_color_value,
1,
&data.vk_image_subresource_range);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,83 @@
/* 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 VKClearDepthStencilImageData {
VkImage vk_image;
VkClearDepthStencilValue vk_clear_depth_stencil_value;
VkImageSubresourceRange vk_image_subresource_range;
};
struct VKClearDepthStencilImageCreateInfo {
VKClearDepthStencilImageData node_data;
/**
* All image aspects of the image that will be cleared.
*
* Used during the pipeline barriers as the full image aspect needs to be known for changing the
* layout. Even when only one aspect is eventually cleared. */
VkImageAspectFlags vk_image_aspects;
};
class VKClearDepthStencilImageNode : public VKNodeInfo<VKNodeType::CLEAR_DEPTH_STENCIL_IMAGE,
VKClearDepthStencilImageCreateInfo,
VKClearDepthStencilImageData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.clear_depth_stencil_image = create_info.node_data;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp resource = resources.get_image_and_increase_stamp(
create_info.node_data.vk_image);
links.images.append({{resource, VK_ACCESS_TRANSFER_WRITE_BIT},
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
create_info.vk_image_aspects});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.clear_depth_stencil_image(data.vk_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
&data.vk_clear_depth_stencil_value,
1,
&data.vk_image_subresource_range);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,67 @@
/* 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 VKCopyBufferData {
VkBuffer src_buffer;
VkBuffer dst_buffer;
VkBufferCopy region;
};
class VKCopyBufferNode : public VKNodeInfo<VKNodeType::COPY_BUFFER,
VKCopyBufferData,
VKCopyBufferData,
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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.copy_buffer = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp src_resource = resources.get_buffer(create_info.src_buffer);
ResourceWithStamp dst_resource = resources.get_buffer_and_increase_stamp(
create_info.dst_buffer);
links.buffers.append({src_resource, VK_ACCESS_TRANSFER_READ_BIT});
links.buffers.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,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.copy_buffer(data.src_buffer, data.dst_buffer, 1, &data.region);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,75 @@
/* 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 VKCopyBufferToImageData {
VkBuffer src_buffer;
VkImage dst_image;
VkBufferImageCopy region;
};
struct VKCopyBufferToImageCreateInfo {
VKCopyBufferToImageData node_data;
VkImageAspectFlags vk_image_aspects;
};
class VKCopyBufferToImageNode : public VKNodeInfo<VKNodeType::COPY_BUFFER_TO_IMAGE,
VKCopyBufferToImageCreateInfo,
VKCopyBufferToImageData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.copy_buffer_to_image.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp src_resource = resources.get_buffer(create_info.node_data.src_buffer);
ResourceWithStamp dst_resource = resources.get_image_and_increase_stamp(
create_info.node_data.dst_image);
links.buffers.append({src_resource, VK_ACCESS_TRANSFER_READ_BIT});
links.images.append({{dst_resource, VK_ACCESS_TRANSFER_WRITE_BIT},
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
create_info.vk_image_aspects});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.copy_buffer_to_image(
data.src_buffer, data.dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &data.region);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "../vk_command_buffer_wrapper.hh"
#include "../vk_render_graph_links.hh"
#include "../vk_resource_state_tracker.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKCopyImageData {
VkImage src_image;
VkImage dst_image;
VkImageCopy region;
/**
* \brief The number of mip-levels to copy.
*
* Additional regions are added just before sending to the command buffer.
*/
int32_t mip_levels;
};
struct VKCopyImageCreateInfo {
VKCopyImageData node_data;
VkImageAspectFlags vk_image_aspect;
};
class VKCopyImageNode : public VKNodeInfo<VKNodeType::COPY_IMAGE,
VKCopyImageCreateInfo,
VKCopyImageData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
BLI_assert(create_info.node_data.mip_levels > 0);
node.storage_index = storage.copy_image.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp src_resource = resources.get_image(create_info.node_data.src_image);
ResourceWithStamp dst_resource = resources.get_image_and_increase_stamp(
create_info.node_data.dst_image);
links.images.append({{src_resource, VK_ACCESS_TRANSFER_READ_BIT},
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
create_info.vk_image_aspect});
links.images.append({{dst_resource, VK_ACCESS_TRANSFER_WRITE_BIT},
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
create_info.vk_image_aspect});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
/* Expand the regions to contain all requested mipmap levels. */
Array<VkImageCopy> regions(data.mip_levels);
regions.fill(data.region);
const VkImageCopy &src_region = regions[0];
for (int index : regions.index_range().drop_front(1)) {
regions[index].extent.width = std::max(src_region.extent.width >> index, 1u);
regions[index].extent.height = std::max(src_region.extent.height >> index, 1u);
regions[index].extent.depth = std::max(src_region.extent.depth >> index, 1u);
regions[index].srcSubresource.mipLevel = src_region.srcSubresource.mipLevel + index;
regions[index].dstSubresource.mipLevel = src_region.dstSubresource.mipLevel + index;
}
command_buffer.copy_image(data.src_image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
data.dst_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
uint32_t(regions.size()),
regions.data());
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "../vk_command_buffer_wrapper.hh"
#include "../vk_render_graph_links.hh"
#include "../vk_resource_state_tracker.hh"
#include "vk_common.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKCopyImageToBufferData {
VkImage src_image;
VkBuffer dst_buffer;
VkBufferImageCopy region;
};
struct VKCopyImageToBufferCreateInfo {
VKCopyImageToBufferData node_data;
VkImageAspectFlags vk_image_aspects;
};
class VKCopyImageToBufferNode : public VKNodeInfo<VKNodeType::COPY_IMAGE_TO_BUFFER,
VKCopyImageToBufferCreateInfo,
VKCopyImageToBufferData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.copy_image_to_buffer.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp src_resource = resources.get_image(create_info.node_data.src_image);
ResourceWithStamp dst_resource = resources.get_buffer_and_increase_stamp(
create_info.node_data.dst_buffer);
links.images.append({{src_resource, VK_ACCESS_TRANSFER_READ_BIT},
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
create_info.vk_image_aspects});
links.buffers.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,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.copy_image_to_buffer(
data.src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, data.dst_buffer, 1, &data.region);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_render_graph_links.hh"
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDispatchIndirectData {
VKPipelineData pipeline_data;
VkBuffer buffer;
VkDeviceSize offset;
};
/**
* Information needed to add a node to the render graph.
*/
struct VKDispatchIndirectCreateInfo : NonCopyable {
VKDispatchIndirectData dispatch_indirect_node = {};
const VKResourceAccessInfo &resources;
VKDispatchIndirectCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
/* Although confusing the spec mentions that VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT should also be
* used for dispatches.
* (https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPipelineStageFlagBits.html)
*/
class VKDispatchIndirectNode
: public VKNodeInfo<VKNodeType::DISPATCH_INDIRECT,
VKDispatchIndirectCreateInfo,
VKDispatchIndirectData,
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.dispatch_indirect = create_info.dispatch_indirect_node;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
ResourceWithStamp buffer_resource = resources.get_buffer(
create_info.dispatch_indirect_node.buffer);
links.buffers.append({buffer_resource, VK_ACCESS_INDIRECT_COMMAND_READ_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_data_build_commands(command_buffer,
data.pipeline_data,
storage_push_constants,
r_bound_pipelines.compute,
VK_PIPELINE_BIND_POINT_COMPUTE,
VK_SHADER_STAGE_COMPUTE_BIT);
command_buffer.dispatch_indirect(data.buffer, data.offset);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_render_graph_links.hh"
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDispatchData {
VKPipelineData pipeline_data;
uint32_t group_count_x;
uint32_t group_count_y;
uint32_t group_count_z;
};
/**
* Information needed to add a node to the render graph.
*/
struct VKDispatchCreateInfo : NonCopyable {
VKDispatchData dispatch_node = {};
const VKResourceAccessInfo &resources;
VKDispatchCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDispatchNode : public VKNodeInfo<VKNodeType::DISPATCH,
VKDispatchCreateInfo,
VKDispatchData,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.dispatch = create_info.dispatch_node;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_data_build_commands(command_buffer,
data.pipeline_data,
storage_push_constants,
r_bound_pipelines.compute,
VK_PIPELINE_BIND_POINT_COMPUTE,
VK_SHADER_STAGE_COMPUTE_BIT);
command_buffer.dispatch(data.group_count_x, data.group_count_y, data.group_count_z);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDrawIndexedIndirectData {
VKPipelineDataGraphics graphics;
VKIndexBufferBinding index_buffer;
VKVertexBufferBindings vertex_buffers;
VkBuffer indirect_buffer;
VkDeviceSize offset;
uint32_t draw_count;
uint32_t stride;
};
struct VKDrawIndexedIndirectCreateInfo {
VKDrawIndexedIndirectData node_data = {};
const VKResourceAccessInfo &resources;
VKDrawIndexedIndirectCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDrawIndexedIndirectNode
: public VKNodeInfo<VKNodeType::DRAW_INDEXED_INDIRECT,
VKDrawIndexedIndirectCreateInfo,
VKDrawIndexedIndirectData,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.draw_indexed_indirect.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
if (create_info.node_data.index_buffer.buffer != VK_NULL_HANDLE) {
vk_index_buffer_binding_build_links(resources, links, create_info.node_data.index_buffer);
}
vk_vertex_buffer_bindings_build_links(resources, links, create_info.node_data.vertex_buffers);
ResourceWithStamp buffer_resource = resources.get_buffer(
create_info.node_data.indirect_buffer);
links.buffers.append({buffer_resource, VK_ACCESS_INDIRECT_COMMAND_READ_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_dynamic_graphics_build_commands(command_buffer, data.graphics, r_bound_pipelines);
vk_pipeline_data_build_commands(command_buffer,
data.graphics.pipeline_data,
storage_push_constants,
r_bound_pipelines.graphics.pipeline,
VK_PIPELINE_BIND_POINT_GRAPHICS,
VK_SHADER_STAGE_ALL_GRAPHICS);
vk_index_buffer_binding_build_commands(
command_buffer, data.index_buffer, r_bound_pipelines.graphics.index_buffer);
vk_vertex_buffer_bindings_build_commands(
command_buffer, data.vertex_buffers, r_bound_pipelines.graphics.vertex_buffers);
command_buffer.draw_indexed_indirect(
data.indirect_buffer, data.offset, data.draw_count, data.stride);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDrawIndexedData {
VKPipelineDataGraphics graphics;
VKIndexBufferBinding index_buffer;
VKVertexBufferBindings vertex_buffers;
uint32_t index_count;
uint32_t instance_count;
uint32_t first_index;
uint32_t vertex_offset;
uint32_t first_instance;
};
struct VKDrawIndexedCreateInfo {
VKDrawIndexedData node_data = {};
const VKResourceAccessInfo &resources;
VKDrawIndexedCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDrawIndexedNode : public VKNodeInfo<VKNodeType::DRAW_INDEXED,
VKDrawIndexedCreateInfo,
VKDrawIndexedData,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.draw_indexed.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
if (create_info.node_data.index_buffer.buffer != VK_NULL_HANDLE) {
vk_index_buffer_binding_build_links(resources, links, create_info.node_data.index_buffer);
}
vk_vertex_buffer_bindings_build_links(resources, links, create_info.node_data.vertex_buffers);
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_dynamic_graphics_build_commands(command_buffer, data.graphics, r_bound_pipelines);
vk_pipeline_data_build_commands(command_buffer,
data.graphics.pipeline_data,
storage_push_constants,
r_bound_pipelines.graphics.pipeline,
VK_PIPELINE_BIND_POINT_GRAPHICS,
VK_SHADER_STAGE_ALL_GRAPHICS);
vk_index_buffer_binding_build_commands(
command_buffer, data.index_buffer, r_bound_pipelines.graphics.index_buffer);
vk_vertex_buffer_bindings_build_commands(
command_buffer, data.vertex_buffers, r_bound_pipelines.graphics.vertex_buffers);
command_buffer.draw_indexed(data.index_count,
data.instance_count,
data.first_index,
data.vertex_offset,
data.first_instance);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDrawIndirectData {
VKPipelineDataGraphics graphics;
VKVertexBufferBindings vertex_buffers;
VkBuffer indirect_buffer;
VkDeviceSize offset;
uint32_t draw_count;
uint32_t stride;
};
struct VKDrawIndirectCreateInfo {
VKDrawIndirectData node_data = {};
const VKResourceAccessInfo &resources;
VKDrawIndirectCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDrawIndirectNode : public VKNodeInfo<VKNodeType::DRAW_INDIRECT,
VKDrawIndirectCreateInfo,
VKDrawIndirectData,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.draw_indirect.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
vk_vertex_buffer_bindings_build_links(resources, links, create_info.node_data.vertex_buffers);
ResourceWithStamp buffer_resource = resources.get_buffer(
create_info.node_data.indirect_buffer);
links.buffers.append({buffer_resource, VK_ACCESS_INDIRECT_COMMAND_READ_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_dynamic_graphics_build_commands(command_buffer, data.graphics, r_bound_pipelines);
vk_pipeline_data_build_commands(command_buffer,
data.graphics.pipeline_data,
storage_push_constants,
r_bound_pipelines.graphics.pipeline,
VK_PIPELINE_BIND_POINT_GRAPHICS,
VK_SHADER_STAGE_ALL_GRAPHICS);
vk_vertex_buffer_bindings_build_commands(
command_buffer, data.vertex_buffers, r_bound_pipelines.graphics.vertex_buffers);
command_buffer.draw_indirect(data.indirect_buffer, data.offset, data.draw_count, data.stride);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/nodes/vk_pipeline_data.hh"
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDrawData {
VKPipelineDataGraphics graphics;
VKVertexBufferBindings vertex_buffers;
uint32_t vertex_count;
uint32_t instance_count;
uint32_t first_vertex;
uint32_t first_instance;
};
struct VKDrawCreateInfo {
VKDrawData node_data = {};
const VKResourceAccessInfo &resources;
VKDrawCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDrawNode : public VKNodeInfo<VKNodeType::DRAW,
VKDrawCreateInfo,
VKDrawData,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info)
{
node.storage_index = storage.draw.append_and_get_index(create_info.node_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, links);
vk_vertex_buffer_bindings_build_links(resources, links, create_info.node_data.vertex_buffers);
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_dynamic_graphics_build_commands(command_buffer, data.graphics, r_bound_pipelines);
vk_pipeline_data_build_commands(command_buffer,
data.graphics.pipeline_data,
storage_push_constants,
r_bound_pipelines.graphics.pipeline,
VK_PIPELINE_BIND_POINT_GRAPHICS,
VK_SHADER_STAGE_ALL_GRAPHICS);
vk_vertex_buffer_bindings_build_commands(
command_buffer, data.vertex_buffers, r_bound_pipelines.graphics.vertex_buffers);
command_buffer.draw(
data.vertex_count, data.instance_count, data.first_vertex, data.first_instance);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,69 @@
/* 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 VKEndQueryData {
VkQueryPool vk_query_pool;
uint32_t query_index;
};
/**
* End Query Node
*
* - Contains logic to copy relevant data to the VKRenderGraphNode.
* - Determine read/write resource dependencies.
* - Add commands to a command builder.
*/
class VKEndQueryNode : public VKNodeInfo<VKNodeType::END_QUERY,
VKEndQueryData,
VKEndQueryData,
VK_PIPELINE_STAGE_NONE,
VKResourceType::IMAGE> {
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, typename Storage>
void set_node_data(Node &node, Storage & /*storage*/, const CreateInfo &create_info)
{
node.end_query = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker & /*resources*/,
VKRenderGraphLinks & /*links*/,
const CreateInfo & /*create_info*/) override
{
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.end_query(data.vk_query_pool, data.query_index);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,66 @@
/* 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 VKEndRenderingData {};
/**
* End rendering node
*
* - Contains logic to copy relevant data to the VKRenderGraphNode.
* - Determine read/write resource dependencies.
* - Add commands to a command builder.
*/
class VKEndRenderingNode : public VKNodeInfo<VKNodeType::END_RENDERING,
VKEndRenderingData,
VKEndRenderingData,
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
VKResourceType::NONE> {
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, typename Storage>
void set_node_data(Node &node, Storage & /*storage*/, const CreateInfo &create_info)
{
node.end_rendering = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker & /*resources*/,
VKRenderGraphLinks & /*links*/,
const CreateInfo & /*create_info*/) override
{
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data & /*data*/,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.end_rendering();
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,64 @@
/* 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 VKFillBufferData {
VkBuffer vk_buffer;
VkDeviceSize size;
uint32_t data;
};
class VKFillBufferNode : public VKNodeInfo<VKNodeType::FILL_BUFFER,
VKFillBufferData,
VKFillBufferData,
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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
node.fill_buffer = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp resource = resources.get_buffer_and_increase_stamp(create_info.vk_buffer);
links.buffers.append({resource, VK_ACCESS_TRANSFER_WRITE_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.fill_buffer(data.vk_buffer, 0, data.size, data.data);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,215 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_command_buffer_wrapper.hh"
#include "render_graph/vk_render_graph_links.hh"
#include "render_graph/vk_resource_state_tracker.hh"
#include "vk_common.hh"
#include "vk_pipeline_data.hh"
namespace blender::gpu::render_graph {
/**
* Type of nodes of the render graph.
*/
enum class VKNodeType {
UNUSED,
BEGIN_QUERY,
BEGIN_RENDERING,
BLIT_IMAGE,
CLEAR_ATTACHMENTS,
CLEAR_COLOR_IMAGE,
CLEAR_DEPTH_STENCIL_IMAGE,
COPY_BUFFER,
COPY_IMAGE,
COPY_IMAGE_TO_BUFFER,
COPY_BUFFER_TO_IMAGE,
DISPATCH,
DISPATCH_INDIRECT,
DRAW,
DRAW_INDEXED,
DRAW_INDEXED_INDIRECT,
DRAW_INDIRECT,
END_QUERY,
END_RENDERING,
FILL_BUFFER,
RESET_QUERY_POOL,
SYNCHRONIZATION,
UPDATE_BUFFER,
UPDATE_MIPMAPS,
};
BLI_INLINE std::ostream &operator<<(std::ostream &os, const VKNodeType node_type)
{
switch (node_type) {
case VKNodeType::UNUSED:
os << "UNUSED";
break;
case VKNodeType::BEGIN_QUERY:
os << "BEGIN_QUERY";
break;
case VKNodeType::BEGIN_RENDERING:
os << "BEGIN_RENDERING";
break;
case VKNodeType::END_QUERY:
os << "END_QUERY";
break;
case VKNodeType::END_RENDERING:
os << "END_RENDERING";
break;
case VKNodeType::CLEAR_ATTACHMENTS:
os << "CLEAR_ATTACHMENTS";
break;
case VKNodeType::CLEAR_COLOR_IMAGE:
os << "CLEAR_COLOR_IMAGE";
break;
case VKNodeType::CLEAR_DEPTH_STENCIL_IMAGE:
os << "CLEAR_DEPTH_STENCIL_IMAGE";
break;
case VKNodeType::FILL_BUFFER:
os << "FILL_BUFFER";
break;
case VKNodeType::COPY_BUFFER:
os << "COPY_BUFFER";
break;
case VKNodeType::COPY_IMAGE:
os << "COPY_IMAGE";
break;
case VKNodeType::COPY_IMAGE_TO_BUFFER:
os << "COPY_IMAGE_TO_BUFFER";
break;
case VKNodeType::COPY_BUFFER_TO_IMAGE:
os << "COPY_BUFFER_TO_IMAGE";
break;
case VKNodeType::BLIT_IMAGE:
os << "BLIT_IMAGE";
break;
case VKNodeType::DISPATCH:
os << "DISPATCH";
break;
case VKNodeType::DISPATCH_INDIRECT:
os << "DISPATCH_INDIRECT";
break;
case VKNodeType::DRAW:
os << "DRAW";
break;
case VKNodeType::DRAW_INDEXED:
os << "DRAW_INDEXED";
break;
case VKNodeType::DRAW_INDEXED_INDIRECT:
os << "DRAW_INDEXED_INDIRECT";
break;
case VKNodeType::DRAW_INDIRECT:
os << "DRAW_INDIRECT";
break;
case VKNodeType::RESET_QUERY_POOL:
os << "RESET_QUERY_POOL";
break;
case VKNodeType::SYNCHRONIZATION:
os << "SYNCHRONIZATION";
break;
case VKNodeType::UPDATE_BUFFER:
os << "UPDATE_BUFFER";
break;
case VKNodeType::UPDATE_MIPMAPS:
os << "UPDATE_MIPMAPS";
break;
}
return os;
}
BLI_INLINE bool node_type_is_within_rendering(VKNodeType node_type)
{
return ELEM(node_type,
VKNodeType::CLEAR_ATTACHMENTS,
VKNodeType::DRAW,
VKNodeType::DRAW_INDEXED,
VKNodeType::DRAW_INDEXED_INDIRECT,
VKNodeType::DRAW_INDIRECT);
}
BLI_INLINE bool node_type_is_rendering(VKNodeType node_type)
{
return ELEM(node_type, VKNodeType::BEGIN_RENDERING, VKNodeType::END_RENDERING) ||
node_type_is_within_rendering(node_type);
}
/**
* Info class for a node type.
*
* Nodes can be created using `NodeCreateInfo`. When a node is created the `VKNodeInfo.node_type`
* and `VKNodeInfo.set_node_data` are used to fill a VKRenderGraphNode instance. The
* VKRenderGraphNode is stored sequentially in the render graph. When the node is created the
* dependencies are extracted by calling `VKNodeInfo.build_links`.
*
* Eventually when a node is recorded to a command buffer `VKNodeInfo.build_commands` is invoked.
*/
template<VKNodeType NodeType,
typename NodeCreateInfo,
typename NodeData,
VkPipelineStageFlags PipelineStage,
VKResourceType ResourceUsages>
class VKNodeInfo : public NonCopyable {
public:
using CreateInfo = NodeCreateInfo;
using Data = NodeData;
/**
* Node type of this class.
*
* The node type used to link VKRenderGraphNode instance to a VKNodeInfo.
*/
static constexpr VKNodeType node_type = NodeType;
/**
* Which pipeline stage does this command belongs to. The pipeline stage is used when generating
* pipeline barriers.
*/
static constexpr VkPipelineStageFlags pipeline_stage = PipelineStage;
/**
* Which resource types are relevant. Some code can be skipped when a node can only depend on
* resources of a single type.
*/
static constexpr VKResourceType resource_usages = ResourceUsages;
/**
* Update the node data with the data inside create_info.
*
* Has been implemented as a template to ensure all node specific data
* (`Data`/`CreateInfo`) types can be included in the same header file as the logic. The
* actual node data (`VKRenderGraphNode` includes all header files.)
*
* This function must be implemented by all node classes. But due to cyclic inclusion of header
* files it is implemented as a template function.
*/
template<typename Node, typename Storage>
static void set_node_data(Node &node, Storage &storage, const CreateInfo &create_info);
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
virtual void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) = 0;
/**
* Build the commands and add them to the command_buffer.
*
* The command buffer is passed as an interface as this is replaced by a logger when running test
* cases. The test cases will validate the log to find out if the correct commands where added.
*/
virtual void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> storage_push_constants,
VKBoundPipelines &r_bound_pipelines) = 0;
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,136 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "render_graph/nodes/vk_pipeline_data.hh"
#include "render_graph/vk_command_buffer_wrapper.hh"
#include "render_graph/vk_render_graph_links.hh"
#include "vk_backend.hh"
namespace blender::gpu::render_graph {
void vk_pipeline_dynamic_graphics_build_commands(VKCommandBufferInterface &command_buffer,
const VKPipelineDataGraphics &graphics,
VKBoundPipelines &r_bound_pipelines)
{
if (assign_if_different(r_bound_pipelines.graphics.viewport_state, graphics.viewport)) {
command_buffer.set_viewport(graphics.viewport.viewports);
command_buffer.set_scissor(graphics.viewport.scissors);
}
if (assign_if_different(r_bound_pipelines.graphics.line_width, graphics.line_width)) {
if (graphics.line_width.has_value()) {
command_buffer.set_line_width(*graphics.line_width);
}
}
if (assign_if_different(r_bound_pipelines.graphics.stencil_state, graphics.stencil_state)) {
if (graphics.stencil_state.has_value()) {
const StencilState &stencil_state = *graphics.stencil_state;
command_buffer.set_stencil_compare_mask(stencil_state.compare_mask);
command_buffer.set_stencil_write_mask(stencil_state.write_mask);
command_buffer.set_stencil_reference(stencil_state.reference);
}
}
if (assign_if_different(r_bound_pipelines.graphics.front_face, graphics.front_face)) {
if (graphics.front_face.has_value()) {
command_buffer.set_front_face(*graphics.front_face);
}
}
if (assign_if_different(r_bound_pipelines.graphics.vertex_input_description,
graphics.vertex_input_description))
{
if (graphics.vertex_input_description.has_value()) {
VKDevice &device = VKBackend::get().device;
const VKVertexInputDescription &description = device.vertex_input_descriptions.get(
*graphics.vertex_input_description);
command_buffer.set_vertex_input(description.bindings, description.attributes);
}
}
}
void vk_pipeline_data_build_commands(VKCommandBufferInterface &command_buffer,
const VKPipelineData &pipeline_data,
Span<uint8_t> storage_push_constants,
VKBoundPipeline &r_bound_pipeline,
VkPipelineBindPoint vk_pipeline_bind_point,
VkShaderStageFlags vk_shader_stage_flags)
{
if (assign_if_different(r_bound_pipeline.vk_pipeline, pipeline_data.vk_pipeline)) {
command_buffer.bind_pipeline(vk_pipeline_bind_point, r_bound_pipeline.vk_pipeline);
}
const bool descriptor_set_changed = assign_if_different(r_bound_pipeline.vk_descriptor_set,
pipeline_data.vk_descriptor_set);
const bool layout_changed = assign_if_different(r_bound_pipeline.vk_pipeline_layout,
pipeline_data.vk_pipeline_layout);
if ((descriptor_set_changed || layout_changed) &&
r_bound_pipeline.vk_descriptor_set != VK_NULL_HANDLE)
{
command_buffer.bind_descriptor_sets(vk_pipeline_bind_point,
pipeline_data.vk_pipeline_layout,
0,
1,
&r_bound_pipeline.vk_descriptor_set,
0,
nullptr);
}
if (!pipeline_data.push_constants_range.is_empty()) {
Span<uint8_t> pipeline_push_constants = storage_push_constants.slice(
pipeline_data.push_constants_range);
command_buffer.push_constants(pipeline_data.vk_pipeline_layout,
vk_shader_stage_flags,
0,
pipeline_push_constants.size(),
pipeline_push_constants.data());
}
}
void vk_index_buffer_binding_build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const VKIndexBufferBinding &index_buffer_binding)
{
ResourceWithStamp resource = resources.get_buffer(index_buffer_binding.buffer);
links.buffers.append({resource, VK_ACCESS_INDEX_READ_BIT});
}
void vk_index_buffer_binding_build_commands(VKCommandBufferInterface &command_buffer,
const VKIndexBufferBinding &index_buffer_binding,
VKIndexBufferBinding &r_bound_index_buffer)
{
if (assign_if_different(r_bound_index_buffer, index_buffer_binding)) {
command_buffer.bind_index_buffer(
r_bound_index_buffer.buffer, 0, r_bound_index_buffer.index_type);
}
}
void vk_vertex_buffer_bindings_build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const VKVertexBufferBindings &vertex_buffers)
{
for (const VkBuffer vk_buffer :
Span<VkBuffer>(vertex_buffers.buffer, vertex_buffers.buffer_count))
{
ResourceWithStamp resource = resources.get_buffer(vk_buffer);
links.buffers.append({resource, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT});
}
}
void vk_vertex_buffer_bindings_build_commands(VKCommandBufferInterface &command_buffer,
const VKVertexBufferBindings &vertex_buffer_bindings,
VKVertexBufferBindings &r_bound_vertex_buffers)
{
if (assign_if_different(r_bound_vertex_buffers, vertex_buffer_bindings) &&
r_bound_vertex_buffers.buffer_count)
{
command_buffer.bind_vertex_buffers(0,
r_bound_vertex_buffers.buffer_count,
r_bound_vertex_buffers.buffer,
r_bound_vertex_buffers.offset);
}
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,201 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_vertex_input_description.hh"
namespace blender::gpu::render_graph {
class VKCommandBufferInterface;
struct VKRenderGraphLinks;
class VKResourceStateTracker;
/**
* Stencil dynamic data: op + compare/write masks + reference.
* Moved here so it can be shared between pipeline code and command-buffer wrappers.
*/
struct StencilState {
uint8_t compare_mask;
uint8_t reference;
uint8_t write_mask;
bool operator==(const StencilState &other) const noexcept
{
return compare_mask == other.compare_mask && reference == other.reference &&
write_mask == other.write_mask;
}
bool operator!=(const StencilState &other) const noexcept
{
return !(*this == other);
}
};
/**
* Container for storing shader descriptor set and push constants.
*
* Compute and graphic shaders use the same structure to setup the pipeline for execution.
*/
struct VKPipelineData {
VkPipeline vk_pipeline;
VkPipelineLayout vk_pipeline_layout;
VkDescriptorSet vk_descriptor_set;
/** Range where the push constants are stored in the render graph storage */
IndexRange push_constants_range;
};
/**
* Container for storing viewport and scissor data used for
* draw nodes.
*/
struct VKViewportData {
Vector<VkViewport> viewports;
Vector<VkRect2D> scissors;
bool operator==(const VKViewportData &other) const
{
if (viewports.size() != other.viewports.size() && scissors.size() != other.scissors.size()) {
return false;
}
if (memcmp(viewports.data(), other.viewports.data(), viewports.size() * sizeof(VkViewport)) !=
0)
{
return false;
}
if (memcmp(scissors.data(), other.scissors.data(), scissors.size() * sizeof(VkRect2D)) != 0) {
return false;
}
return true;
}
bool operator!=(const VKViewportData &other) const
{
return !(*this == other);
}
};
struct VKPipelineDataGraphics {
VKPipelineData pipeline_data;
VKViewportData viewport;
std::optional<VKVertexInputDescriptionPool::Key> vertex_input_description;
std::optional<float> line_width;
std::optional<StencilState> stencil_state;
std::optional<VkFrontFace> front_face;
};
/** Resources bound for a compute/graphics pipeline. */
struct VKBoundPipeline {
VkPipeline vk_pipeline;
VkDescriptorSet vk_descriptor_set;
VkPipelineLayout vk_pipeline_layout;
};
struct VKIndexBufferBinding {
VkBuffer buffer;
VkIndexType index_type;
bool operator==(const VKIndexBufferBinding &other) const
{
return buffer == other.buffer && index_type == other.index_type;
}
bool operator!=(const VKIndexBufferBinding &other) const
{
return !(*this == other);
}
};
struct VKVertexBufferBindings {
uint32_t buffer_count;
VkBuffer buffer[16];
VkDeviceSize offset[16];
bool operator==(const VKVertexBufferBindings &other) const
{
return buffer_count == other.buffer_count &&
Span<VkBuffer>(buffer, buffer_count) ==
Span<VkBuffer>(other.buffer, other.buffer_count) &&
Span<VkDeviceSize>(offset, buffer_count) ==
Span<VkDeviceSize>(other.offset, buffer_count);
}
bool operator!=(const VKVertexBufferBindings &other) const
{
return !(*this == other);
}
};
/**
* Vulkan keeps track of bound resources for graphics separate from compute.
* This struct store last bound resources for both bind points.
*/
struct VKBoundPipelines {
/** Last bound resources for compute pipeline. */
VKBoundPipeline compute;
/** Last bound resources for graphics pipeline. */
struct {
VKBoundPipeline pipeline;
VKIndexBufferBinding index_buffer;
VKVertexBufferBindings vertex_buffers;
VKViewportData viewport_state;
std::optional<float> line_width;
std::optional<StencilState> stencil_state;
std::optional<VkFrontFace> front_face;
std::optional<VKVertexInputDescriptionPool::Key> vertex_input_description;
} graphics;
};
/**
* Record commands that update the dynamic state.
*
* - viewports
* - scissors
* - line width
* - stencil op + compare/write masks + reference
* - front face (when VK_EXT_extended_dynamic_state is available)
* - vertex input (when VK_EXT_vertex_input_dynamic_state is available)
*/
void vk_pipeline_dynamic_graphics_build_commands(VKCommandBufferInterface &command_buffer,
const VKPipelineDataGraphics &graphics,
VKBoundPipelines &r_bound_pipelines);
/**
* Record the commands to the given command buffer to bind the descriptor set, pipeline and push
* constants.
*
* Descriptor set and pipeline are only bound, when they are different than the last bound. The
* r_bound_pipelines are checked to identify if they are the last bound. Descriptor set and
* pipeline are bound at the given pipeline bind point.
*
* storage_push_constants contains all the push constants of the render graph. The
* pipeline_data.push_constants_range contains the valid range that needs to be bound. Any
* available push constants in the pipeline data always update the shader stages provided by
* `vk_shader_stage_flags`.
*/
void vk_pipeline_data_build_commands(VKCommandBufferInterface &command_buffer,
const VKPipelineData &pipeline_data,
Span<uint8_t> storage_push_constants,
VKBoundPipeline &r_bound_pipeline,
VkPipelineBindPoint vk_pipeline_bind_point,
VkShaderStageFlags vk_shader_stage_flags);
void vk_index_buffer_binding_build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const VKIndexBufferBinding &index_buffer_binding);
void vk_index_buffer_binding_build_commands(VKCommandBufferInterface &command_buffer,
const VKIndexBufferBinding &index_buffer_binding,
VKIndexBufferBinding &r_bound_index_buffer);
void vk_vertex_buffer_bindings_build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const VKVertexBufferBindings &vertex_buffers);
void vk_vertex_buffer_bindings_build_commands(VKCommandBufferInterface &command_buffer,
const VKVertexBufferBindings &vertex_buffer_bindings,
VKVertexBufferBindings &r_bound_vertex_buffers);
} // namespace blender::gpu::render_graph

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 VKResetQueryPoolData {
VkQueryPool vk_query_pool;
uint32_t first_query;
uint32_t query_count;
};
/**
* Reset query pool.
*
* - Contains logic to copy relevant data to the VKRenderGraphNode.
* - Determine read/write resource dependencies.
* - Add commands to a command builder.
*/
class VKResetQueryPoolNode : public VKNodeInfo<VKNodeType::RESET_QUERY_POOL,
VKResetQueryPoolData,
VKResetQueryPoolData,
VK_PIPELINE_STAGE_NONE,
VKResourceType::NONE> {
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, typename Storage>
void set_node_data(Node &node, Storage & /*storage*/, const CreateInfo &create_info)
{
node.reset_query_pool = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker & /*resources*/,
VKRenderGraphLinks & /*links*/,
const CreateInfo & /*create_info*/) override
{
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
command_buffer.reset_query_pool(data.vk_query_pool, data.first_query, data.query_count);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,74 @@
/* 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 VKSynchronizationData {};
/**
* Information needed to add a node to the render graph.
*/
struct VKSynchronizationCreateInfo {
VkImage vk_image;
VkImageLayout vk_image_layout;
VkImageAspectFlags vk_image_aspect;
};
class VKSynchronizationNode : public VKNodeInfo<VKNodeType::SYNCHRONIZATION,
VKSynchronizationCreateInfo,
VKSynchronizationData,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VKResourceType::IMAGE | 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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, const CreateInfo &create_info)
{
UNUSED_VARS(create_info);
node.synchronization = {};
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp resource = resources.get_image_and_increase_stamp(create_info.vk_image);
links.images.append(
{{resource, VK_ACCESS_NONE}, create_info.vk_image_layout, create_info.vk_image_aspect});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
UNUSED_VARS(command_buffer, data);
/* Intentionally left empty: A pipeline barrier has already been send to the command buffer.
*/
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,72 @@
/* 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, typename Storage>
static void set_node_data(Node &node, Storage & /* storage */, 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,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp dst_resource = resources.get_buffer_and_increase_stamp(
create_info.dst_buffer);
links.buffers.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,
Span<uint8_t> /*storage_push_constants*/,
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_delete_void(data.data);
data.data = nullptr;
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_math_vector.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKUpdateMipmapsData {
VkImage vk_image;
VkImageAspectFlags vk_image_aspect;
int mipmaps;
int layer_count;
int3 l0_size;
};
/**
* Update mipmaps Node
*/
class VKUpdateMipmapsNode : public VKNodeInfo<VKNodeType::UPDATE_MIPMAPS,
VKUpdateMipmapsData,
VKUpdateMipmapsData,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VKResourceType::IMAGE> {
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, typename Storage>
void set_node_data(Node &node, Storage & /*storage*/, const CreateInfo &create_info)
{
node.update_mipmaps = create_info;
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const CreateInfo &create_info) override
{
ResourceWithStamp resource = resources.get_image_and_increase_stamp(create_info.vk_image);
links.images.append({{resource, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT},
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
create_info.vk_image_aspect});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
Data &data,
Span<uint8_t> /*storage_push_constants*/,
VKBoundPipelines & /*r_bound_pipelines*/) override
{
VkImageMemoryBarrier image_memory_barrier = {};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = data.vk_image;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
image_memory_barrier.subresourceRange.aspectMask = data.vk_image_aspect;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = data.layer_count;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
VkImageBlit image_blit = {};
image_blit.srcSubresource.aspectMask = data.vk_image_aspect;
image_blit.srcSubresource.layerCount = data.layer_count;
image_blit.srcSubresource.mipLevel = 1;
image_blit.dstSubresource.aspectMask = data.vk_image_aspect;
image_blit.dstSubresource.layerCount = data.layer_count;
image_blit.dstSubresource.mipLevel = 1;
int3 dst_size = data.l0_size;
for (int src_mipmap : IndexRange(data.mipmaps - 1)) {
int dst_mipmap = src_mipmap + 1;
int3 src_size = dst_size;
dst_size = math::max(src_size / 2, int3(1));
/* Update the source mipmap level to be in src optimal layout. */
image_memory_barrier.subresourceRange.baseMipLevel = src_mipmap;
command_buffer.pipeline_barrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_DEPENDENCY_BY_REGION_BIT,
0,
nullptr,
0,
nullptr,
1,
&image_memory_barrier);
/* Blit the source mipmap level into the destination mipmap level. */
image_blit.srcSubresource.mipLevel = src_mipmap;
image_blit.srcOffsets[1] = {src_size.x, src_size.y, src_size.z};
image_blit.dstSubresource.mipLevel = dst_mipmap;
image_blit.dstOffsets[1] = {dst_size.x, dst_size.y, dst_size.z};
command_buffer.blit_image(data.vk_image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
data.vk_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&image_blit,
VK_FILTER_LINEAR);
}
/* Ensure that all mipmap levels are in the VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL layout.
* This is the last known layout that the render graph knows about. */
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = data.mipmaps - 1;
image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
command_buffer.pipeline_barrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_DEPENDENCY_BY_REGION_BIT,
0,
nullptr,
0,
nullptr,
1,
&image_memory_barrier);
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,381 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "vk_render_graph_test_types.hh"
namespace blender::gpu::render_graph {
class VKRenderGraphTestCompute : public VKRenderGraphTest {};
TEST_F(VKRenderGraphTestCompute, dispatch_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkPipeline> pipeline(2u);
VkHandle<VkPipelineLayout> pipeline_layout(3u);
VkHandle<VkDescriptorSet> descriptor_set(4u);
resources.add_buffer(buffer);
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
render_graph->add_node(dispatch_info);
submit(render_graph, command_buffer);
EXPECT_EQ(3, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x2)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x3, "
"p_descriptor_sets=0x4)",
log[1]);
EXPECT_EQ("dispatch(group_count_x=1, group_count_y=1, group_count_z=1)", log[2]);
}
/**
* Test that the descriptor sets are updated once when chaining dispatching.
*/
TEST_F(VKRenderGraphTestCompute, dispatch_dispatch_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkPipeline> pipeline(2u);
VkHandle<VkPipelineLayout> pipeline_layout(3u);
VkHandle<VkDescriptorSet> descriptor_set(4u);
resources.add_buffer(buffer);
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
render_graph->add_node(dispatch_info);
}
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
render_graph->add_node(dispatch_info);
}
submit(render_graph, command_buffer);
EXPECT_EQ(5, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x2)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x3, "
"p_descriptor_sets=0x4)",
log[1]);
EXPECT_EQ("dispatch(group_count_x=1, group_count_y=1, group_count_z=1)", log[2]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_SHADER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[3]);
EXPECT_EQ("dispatch(group_count_x=2, group_count_y=2, group_count_z=2)", log[4]);
}
/**
* Test that the descriptor sets are updated when chaining dispatching with different descriptor
* sets.
*/
TEST_F(VKRenderGraphTestCompute, dispatch_dispatch_read_back_with_changing_descriptor_sets)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkPipeline> pipeline(2u);
VkHandle<VkPipelineLayout> pipeline_layout(3u);
VkHandle<VkDescriptorSet> descriptor_set_a(4u);
VkHandle<VkDescriptorSet> descriptor_set_b(5u);
resources.add_buffer(buffer);
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_a;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
render_graph->add_node(dispatch_info);
}
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_b;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
render_graph->add_node(dispatch_info);
}
submit(render_graph, command_buffer);
EXPECT_EQ(6, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x2)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x3, "
"p_descriptor_sets=0x4)",
log[1]);
EXPECT_EQ("dispatch(group_count_x=1, group_count_y=1, group_count_z=1)", log[2]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_SHADER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[3]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x3, "
"p_descriptor_sets=0x5)",
log[4]);
EXPECT_EQ("dispatch(group_count_x=2, group_count_y=2, group_count_z=2)", log[5]);
}
/**
* Test that the descriptor sets are updated when chaining dispatching with different pipelines.
*/
TEST_F(VKRenderGraphTestCompute, dispatch_dispatch_read_back_with_changing_pipelines)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkPipeline> pipeline_a(2u);
VkHandle<VkPipeline> pipeline_b(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set(5u);
resources.add_buffer(buffer);
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_a;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
render_graph->add_node(dispatch_info);
}
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_b;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
render_graph->add_node(dispatch_info);
}
submit(render_graph, command_buffer);
EXPECT_EQ(6, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x2)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[1]);
EXPECT_EQ("dispatch(group_count_x=1, group_count_y=1, group_count_z=1)", log[2]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_SHADER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[3]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[4]);
EXPECT_EQ("dispatch(group_count_x=2, group_count_y=2, group_count_z=2)", log[5]);
}
/**
* Test that the descriptor sets are updated when chaining dispatching with different pipelines and
* descriptor sets.
*/
TEST_F(VKRenderGraphTestCompute,
dispatch_dispatch_read_back_with_changing_pipelines_descriptor_sets)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkPipeline> pipeline_a(2u);
VkHandle<VkPipeline> pipeline_b(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set_a(5u);
VkHandle<VkDescriptorSet> descriptor_set_b(6u);
resources.add_buffer(buffer);
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_a;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_a;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
render_graph->add_node(dispatch_info);
}
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_b;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_b;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
render_graph->add_node(dispatch_info);
}
submit(render_graph, command_buffer);
EXPECT_EQ(7, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x2)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[1]);
EXPECT_EQ("dispatch(group_count_x=1, group_count_y=1, group_count_z=1)", log[2]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_SHADER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[3]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[4]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x6)",
log[5]);
EXPECT_EQ("dispatch(group_count_x=2, group_count_y=2, group_count_z=2)", log[6]);
}
/**
* Test dispatch indirect
*/
TEST_F(VKRenderGraphTestCompute, dispatch_indirect_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkBuffer> command_buffer(2u);
VkHandle<VkPipeline> pipeline(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set(5u);
resources.add_buffer(buffer);
resources.add_buffer(command_buffer);
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(access_info);
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline = pipeline;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_indirect_info.dispatch_indirect_node.buffer = command_buffer;
dispatch_indirect_info.dispatch_indirect_node.offset = 0;
render_graph->add_node(dispatch_indirect_info);
submit(render_graph, this->command_buffer);
EXPECT_EQ(3, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[1]);
EXPECT_EQ("dispatch_indirect(buffer=0x2, offset=0)", log[2]);
}
TEST_F(VKRenderGraphTestCompute, dispatch_indirect_dispatch_indirect_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkBuffer> command_buffer(2u);
VkHandle<VkPipeline> pipeline(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set(5u);
resources.add_buffer(buffer);
resources.add_buffer(command_buffer);
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(access_info);
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline = pipeline;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline_layout =
pipeline_layout;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_indirect_info.dispatch_indirect_node.buffer = command_buffer;
dispatch_indirect_info.dispatch_indirect_node.offset = 0;
render_graph->add_node(dispatch_indirect_info);
}
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(access_info);
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline = pipeline;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline_layout =
pipeline_layout;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_indirect_info.dispatch_indirect_node.buffer = command_buffer;
dispatch_indirect_info.dispatch_indirect_node.offset = 12;
render_graph->add_node(dispatch_indirect_info);
}
submit(render_graph, this->command_buffer);
EXPECT_EQ(5, log.size());
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[0]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[1]);
EXPECT_EQ("dispatch_indirect(buffer=0x2, offset=0)", log[2]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, "
"VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_SHADER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[3]);
EXPECT_EQ("dispatch_indirect(buffer=0x2, offset=12)", log[4]);
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "vk_render_graph_test_types.hh"
namespace blender::gpu::render_graph {
class VKRenderGraphTestPresent : public VKRenderGraphTest {};
TEST_F(VKRenderGraphTestPresent, transfer_and_present)
{
VkHandle<VkImage> back_buffer(1u);
resources.add_image(back_buffer, false);
{
render_graph::VKSynchronizationNode::CreateInfo synchronization = {};
synchronization.vk_image = back_buffer;
synchronization.vk_image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
synchronization.vk_image_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph->add_node(synchronization);
}
submit(render_graph, command_buffer);
EXPECT_EQ(1, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT" +
endl() +
" - image_barrier(src_access_mask=, dst_access_mask=, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
}
TEST_F(VKRenderGraphTestPresent, clear_and_present)
{
VkHandle<VkImage> back_buffer(1u);
resources.add_image(back_buffer, false);
VKClearColorImageNode::CreateInfo clear_color_image = {};
clear_color_image.vk_image = back_buffer;
render_graph->add_node(clear_color_image);
{
render_graph::VKSynchronizationNode::CreateInfo synchronization = {};
synchronization.vk_image = back_buffer;
synchronization.vk_image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
synchronization.vk_image_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph->add_node(synchronization);
}
submit(render_graph, command_buffer);
EXPECT_EQ(3, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=, dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ("clear_color_image(image=0x1, image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)",
log[1]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[2]);
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,621 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "vk_render_graph_test_types.hh"
namespace blender::gpu::render_graph {
class VKRenderGraphTestRender : public VKRenderGraphTest_P {};
TEST_P(VKRenderGraphTestRender, begin_clear_attachments_end_read_back)
{
VkHandle<VkImage> image(1u);
VkHandle<VkImageView> image_view(2u);
VkHandle<VkBuffer> buffer(3u);
resources.add_image(image, false);
resources.add_buffer(buffer);
{
VKResourceAccessInfo access_info = {};
access_info.images.append(
{image, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT, {}});
VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.color_attachments[0].sType =
VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
begin_rendering.node_data.color_attachments[0].imageLayout = color_attachment_layout();
begin_rendering.node_data.color_attachments[0].imageView = image_view;
begin_rendering.node_data.color_attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
begin_rendering.node_data.color_attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = 1;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
begin_rendering.node_data.vk_rendering_info.pColorAttachments =
begin_rendering.node_data.color_attachments;
render_graph->add_node(begin_rendering);
}
{
VKClearAttachmentsNode::CreateInfo clear_attachments = {};
clear_attachments.attachment_count = 1;
clear_attachments.attachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clear_attachments.attachments[0].clearValue.color.float32[0] = 0.2;
clear_attachments.attachments[0].clearValue.color.float32[1] = 0.4;
clear_attachments.attachments[0].clearValue.color.float32[2] = 0.6;
clear_attachments.attachments[0].clearValue.color.float32[3] = 1.0;
clear_attachments.attachments[0].colorAttachment = 0;
clear_attachments.vk_clear_rect.baseArrayLayer = 0;
clear_attachments.vk_clear_rect.layerCount = 1;
clear_attachments.vk_clear_rect.rect.extent.width = 1920;
clear_attachments.vk_clear_rect.rect.extent.height = 1080;
render_graph->add_node(clear_attachments);
}
{
VKEndRenderingNode::CreateInfo end_rendering = {};
render_graph->add_node(end_rendering);
}
{
VKCopyImageToBufferNode::CreateInfo copy_image_to_buffer = {};
copy_image_to_buffer.node_data.src_image = image;
copy_image_to_buffer.node_data.dst_buffer = buffer;
copy_image_to_buffer.node_data.region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_image_to_buffer.vk_image_aspects = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph->add_node(copy_image_to_buffer);
}
submit(render_graph, command_buffer);
EXPECT_EQ(6, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT" +
endl() +
" - image_barrier(src_access_mask=, "
"dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=" +
color_attachment_layout_str() +
", image=0x1, "
"subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ("begin_rendering(p_rendering_info=flags=, render_area=" + endl() +
" offset=" + endl() + " x=0, y=0 , extent=" + endl() +
" width=0, height=0 , layer_count=1, view_mask=0, color_attachment_count=1, "
"p_color_attachments=" +
endl() + " image_view=0x2, image_layout=" + color_attachment_layout_str() +
", "
"resolve_mode=VK_RESOLVE_MODE_NONE, resolve_image_view=0, "
"resolve_image_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"load_op=VK_ATTACHMENT_LOAD_OP_DONT_CARE, store_op=VK_ATTACHMENT_STORE_OP_STORE" +
endl() + ")",
log[1]);
EXPECT_EQ(
"clear_attachments( - attachment(aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, "
"color_attachment=0)" +
endl() + " - rect(rect=" + endl() + " offset=" + endl() +
" x=0, y=0 , extent=" + endl() +
" width=1920, height=1080 , base_array_layer=0, layer_count=1)" + endl() + ")",
log[2]);
EXPECT_EQ("end_rendering()", log[3]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_READ_BIT, "
"old_layout=" +
color_attachment_layout_str() +
", "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[4]);
EXPECT_EQ(
"copy_image_to_buffer(src_image=0x1, src_image_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, "
"dst_buffer=0x3" +
endl() +
" - region(buffer_offset=0, buffer_row_length=0, buffer_image_height=0, "
"image_subresource=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, mip_level=0, base_array_layer=0, "
"layer_count=0 , image_offset=" +
endl() + " x=0, y=0, z=0 , image_extent=" + endl() +
" width=0, height=0, depth=0 )" + endl() + ")",
log[5]);
}
TEST_P(VKRenderGraphTestRender, begin_draw_end)
{
VkHandle<VkImage> image(1u);
VkHandle<VkImageView> image_view(2u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkPipeline> pipeline(3u);
resources.add_image(image, false);
{
VKResourceAccessInfo access_info = {};
access_info.images.append(
{image, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT, {}});
VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.color_attachments[0].sType =
VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
begin_rendering.node_data.color_attachments[0].imageLayout = color_attachment_layout();
begin_rendering.node_data.color_attachments[0].imageView = image_view;
begin_rendering.node_data.color_attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
begin_rendering.node_data.color_attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = 1;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
begin_rendering.node_data.vk_rendering_info.pColorAttachments =
begin_rendering.node_data.color_attachments;
render_graph->add_node(begin_rendering);
}
{
VKResourceAccessInfo access_info = {};
VKDrawNode::CreateInfo draw(access_info);
draw.node_data.first_instance = 0;
draw.node_data.first_vertex = 0;
draw.node_data.instance_count = 1;
draw.node_data.vertex_count = 4;
draw.node_data.graphics.pipeline_data.push_constants_range = IndexRange(0);
draw.node_data.graphics.pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
draw.node_data.graphics.pipeline_data.vk_pipeline = pipeline;
draw.node_data.graphics.pipeline_data.vk_pipeline_layout = pipeline_layout;
draw.node_data.graphics.viewport.viewports.append(VkViewport{});
draw.node_data.graphics.viewport.scissors.append(VkRect2D{});
render_graph->add_node(draw);
}
{
VKEndRenderingNode::CreateInfo end_rendering = {};
render_graph->add_node(end_rendering);
}
submit(render_graph, command_buffer);
EXPECT_EQ(7, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT" +
endl() +
" - image_barrier(src_access_mask=, "
"dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=" +
color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, "
"level_count=4294967295, base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ("begin_rendering(p_rendering_info=flags=, render_area=" + endl() +
" offset=" + endl() + " x=0, y=0 , extent=" + endl() +
" width=0, height=0 , layer_count=1, view_mask=0, "
"color_attachment_count=1, p_color_attachments=" +
endl() + " image_view=0x2, image_layout=" + color_attachment_layout_str() +
", "
"resolve_mode=VK_RESOLVE_MODE_NONE, resolve_image_view=0, "
"resolve_image_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"load_op=VK_ATTACHMENT_LOAD_OP_DONT_CARE, store_op=VK_ATTACHMENT_STORE_OP_STORE" +
endl() + ")",
log[1]);
EXPECT_EQ("set_viewport(num_viewports=1)", log[2]);
EXPECT_EQ("set_scissor(num_scissors=1)", log[3]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline=0x3)",
log[4]);
EXPECT_EQ("draw(vertex_count=4, instance_count=1, first_vertex=0, first_instance=0)", log[5]);
EXPECT_EQ("end_rendering()", log[6]);
}
TEST_P(VKRenderGraphTestRender, begin_draw_end__layered)
{
VkHandle<VkImage> image(1u);
VkHandle<VkImageView> image_view(2u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkPipeline> pipeline(3u);
resources.add_image(image, true);
{
VKResourceAccessInfo access_info = {};
access_info.images.append({image,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT,
{0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}});
VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.color_attachments[0].sType =
VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
begin_rendering.node_data.color_attachments[0].imageLayout = color_attachment_layout();
begin_rendering.node_data.color_attachments[0].imageView = image_view;
begin_rendering.node_data.color_attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
begin_rendering.node_data.color_attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = 1;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
begin_rendering.node_data.vk_rendering_info.pColorAttachments =
begin_rendering.node_data.color_attachments;
render_graph->add_node(begin_rendering);
}
{
VKResourceAccessInfo access_info = {};
access_info.images.append({image,
VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_ASPECT_COLOR_BIT,
{0, VK_REMAINING_MIP_LEVELS, 1, VK_REMAINING_ARRAY_LAYERS}});
VKDrawNode::CreateInfo draw(access_info);
draw.node_data.first_instance = 0;
draw.node_data.first_vertex = 0;
draw.node_data.instance_count = 1;
draw.node_data.vertex_count = 4;
draw.node_data.graphics.pipeline_data.push_constants_range = IndexRange(0);
draw.node_data.graphics.pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
draw.node_data.graphics.pipeline_data.vk_pipeline = pipeline;
draw.node_data.graphics.pipeline_data.vk_pipeline_layout = pipeline_layout;
draw.node_data.graphics.viewport.viewports.append(VkViewport{});
draw.node_data.graphics.viewport.scissors.append(VkRect2D{});
render_graph->add_node(draw);
}
{
VKEndRenderingNode::CreateInfo end_rendering = {};
render_graph->add_node(end_rendering);
}
submit(render_graph, command_buffer);
EXPECT_EQ(9, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT" +
endl() +
" - image_barrier(src_access_mask=, "
"dst_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=" +
color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, "
"level_count=4294967295, base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, "
"VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, "
"VK_PIPELINE_STAGE_ALL_COMMANDS_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, "
"VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT, "
"VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, "
"VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=" +
color_attachment_layout_str() +
", "
"new_layout=VK_IMAGE_LAYOUT_GENERAL, "
"image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=1, layer_count=4294967295 )" +
endl() + ")",
log[1]);
EXPECT_EQ("begin_rendering(p_rendering_info=flags=, render_area=" + endl() +
" offset=" + endl() + " x=0, y=0 , extent=" + endl() +
" width=0, height=0 , layer_count=1, view_mask=0, "
"color_attachment_count=1, p_color_attachments=" +
endl() + " image_view=0x2, image_layout=" + color_attachment_layout_str() +
", "
"resolve_mode=VK_RESOLVE_MODE_NONE, resolve_image_view=0, "
"resolve_image_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"load_op=VK_ATTACHMENT_LOAD_OP_DONT_CARE, store_op=VK_ATTACHMENT_STORE_OP_STORE" +
endl() + ")",
log[2]);
EXPECT_EQ("set_viewport(num_viewports=1)", log[3]);
EXPECT_EQ("set_scissor(num_scissors=1)", log[4]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline=0x3)",
log[5]);
EXPECT_EQ("draw(vertex_count=4, instance_count=1, first_vertex=0, first_instance=0)", log[6]);
EXPECT_EQ("end_rendering()", log[7]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_COMMANDS_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_SHADER_READ_BIT, "
"VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, "
"VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, "
"VK_ACCESS_TRANSFER_WRITE_BIT, dst_access_mask=VK_ACCESS_SHADER_READ_BIT, "
"VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, "
"VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, "
"VK_ACCESS_TRANSFER_WRITE_BIT, old_layout=VK_IMAGE_LAYOUT_GENERAL, "
"new_layout=" +
color_attachment_layout_str() + ", image=0x1, subresource_range=" + endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=1, layer_count=4294967295 )" +
endl() + ")",
log[8]);
}
/**
* Tests that a synchronization barrier is generated for input attachment reads on layered images
* after a color attachment write. Regression test for #158501.
*/
TEST_P(VKRenderGraphTestRender, begin_draw_begin_draw_end__layered_input_attachment)
{
VkHandle<VkImage> image(1u);
VkHandle<VkImageView> image_view(2u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkPipeline> pipeline(3u);
resources.add_image(image, true);
/* First rendering scope: write to image as color attachment. */
{
VKResourceAccessInfo access_info = {};
access_info.images.append(
{image,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT,
{0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}});
VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.color_attachments[0].sType =
VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
begin_rendering.node_data.color_attachments[0].imageLayout = color_attachment_layout();
begin_rendering.node_data.color_attachments[0].imageView = image_view;
begin_rendering.node_data.color_attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
begin_rendering.node_data.color_attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = 1;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
begin_rendering.node_data.vk_rendering_info.pColorAttachments =
begin_rendering.node_data.color_attachments;
render_graph->add_node(begin_rendering);
}
{
VKResourceAccessInfo empty_access_info = {};
VKDrawNode::CreateInfo draw(empty_access_info);
draw.node_data.first_instance = 0;
draw.node_data.first_vertex = 0;
draw.node_data.instance_count = 1;
draw.node_data.vertex_count = 4;
draw.node_data.graphics.pipeline_data.push_constants_range = IndexRange(0);
draw.node_data.graphics.pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
draw.node_data.graphics.pipeline_data.vk_pipeline = pipeline;
draw.node_data.graphics.pipeline_data.vk_pipeline_layout = pipeline_layout;
draw.node_data.graphics.viewport.viewports.append(VkViewport{});
draw.node_data.graphics.viewport.scissors.append(VkRect2D{});
render_graph->add_node(draw);
}
/* Second rendering scope: restart without explicit END_RENDERING,
* simulating subpass_transition with dynamic_rendering_local_read.
* Image is accessed as input attachment. */
{
VKResourceAccessInfo access_info = {};
access_info.images.append(
{image,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT,
{0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}});
VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.color_attachments[0].sType =
VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
begin_rendering.node_data.color_attachments[0].imageLayout = color_attachment_layout();
begin_rendering.node_data.color_attachments[0].imageView = VK_NULL_HANDLE;
begin_rendering.node_data.color_attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
begin_rendering.node_data.color_attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = 1;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
begin_rendering.node_data.vk_rendering_info.pColorAttachments =
begin_rendering.node_data.color_attachments;
render_graph->add_node(begin_rendering);
}
{
VkAccessFlags input_access = use_dynamic_rendering_local_read ?
VK_ACCESS_INPUT_ATTACHMENT_READ_BIT :
VK_ACCESS_SHADER_READ_BIT;
VKResourceAccessInfo access_info = {};
access_info.images.append({image,
input_access,
VK_IMAGE_ASPECT_COLOR_BIT,
{0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}});
VKDrawNode::CreateInfo draw(access_info);
draw.node_data.first_instance = 0;
draw.node_data.first_vertex = 0;
draw.node_data.instance_count = 1;
draw.node_data.vertex_count = 4;
draw.node_data.graphics.pipeline_data.push_constants_range = IndexRange(0);
draw.node_data.graphics.pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
draw.node_data.graphics.pipeline_data.vk_pipeline = pipeline;
draw.node_data.graphics.pipeline_data.vk_pipeline_layout = pipeline_layout;
draw.node_data.graphics.viewport.viewports.append(VkViewport{});
draw.node_data.graphics.viewport.scissors.append(VkRect2D{});
render_graph->add_node(draw);
}
{
VKEndRenderingNode::CreateInfo end_rendering = {};
render_graph->add_node(end_rendering);
}
submit(render_graph, command_buffer);
/* Verify that a barrier with the read access flag is generated.
* Without the fix for #158501, this barrier would be missing because
* the image tracker skipped access flag synchronization for tracked
* layered color attachments. */
const char *expected_access = use_dynamic_rendering_local_read ?
"VK_ACCESS_INPUT_ATTACHMENT_READ_BIT" :
"VK_ACCESS_SHADER_READ_BIT";
bool has_sync_barrier = false;
for (const std::string &entry : log) {
if (entry.find(expected_access) != std::string::npos) {
has_sync_barrier = true;
break;
}
}
EXPECT_TRUE(has_sync_barrier)
<< "Missing synchronization barrier for input attachment read after color attachment write. "
"See #158501.";
}
TEST_P(VKRenderGraphTestRender, begin_draw_end__layered_depth)
{
VkHandle<VkImage> image(1u);
VkHandle<VkImageView> image_view(2u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkPipeline> pipeline(3u);
resources.add_image(image, true);
{
VKResourceAccessInfo access_info = {};
access_info.images.append({image,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT,
{0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}});
VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.depth_attachment.sType =
VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
begin_rendering.node_data.depth_attachment.imageLayout =
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
begin_rendering.node_data.depth_attachment.imageView = image_view;
begin_rendering.node_data.depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
begin_rendering.node_data.depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = 0;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
begin_rendering.node_data.vk_rendering_info.pDepthAttachment =
&begin_rendering.node_data.depth_attachment;
render_graph->add_node(begin_rendering);
}
{
VKResourceAccessInfo access_info = {};
access_info.images.append({image,
VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT,
{0, VK_REMAINING_MIP_LEVELS, 1, VK_REMAINING_ARRAY_LAYERS}});
VKDrawNode::CreateInfo draw(access_info);
draw.node_data.first_instance = 0;
draw.node_data.first_vertex = 0;
draw.node_data.instance_count = 1;
draw.node_data.vertex_count = 4;
draw.node_data.graphics.pipeline_data.push_constants_range = IndexRange(0);
draw.node_data.graphics.pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
draw.node_data.graphics.pipeline_data.vk_pipeline = pipeline;
draw.node_data.graphics.pipeline_data.vk_pipeline_layout = pipeline_layout;
draw.node_data.graphics.viewport.viewports.append(VkViewport{});
draw.node_data.graphics.viewport.scissors.append(VkRect2D{});
render_graph->add_node(draw);
}
{
VKEndRenderingNode::CreateInfo end_rendering = {};
render_graph->add_node(end_rendering);
}
submit(render_graph, command_buffer);
EXPECT_EQ(9, log.size());
/* Pre-barrier: UNDEFINED -> DEPTH_ATTACHMENT_OPTIMAL */
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT" +
endl() +
" - image_barrier(src_access_mask=, "
"dst_access_mask=VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_DEPTH_BIT, base_mip_level=0, "
"level_count=4294967295, base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
/* Layer transition: DEPTH_ATTACHMENT_OPTIMAL -> GENERAL (shader read of layer 1) */
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, "
"VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, "
"VK_PIPELINE_STAGE_ALL_COMMANDS_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, "
"VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT, "
"VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, "
"VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, "
"VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_GENERAL, "
"image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_DEPTH_BIT, base_mip_level=0, "
"level_count=4294967295, "
"base_array_layer=1, layer_count=4294967295 )" +
endl() + ")",
log[1]);
EXPECT_EQ("begin_rendering(p_rendering_info=flags=, render_area=" + endl() +
" offset=" + endl() + " x=0, y=0 , extent=" + endl() +
" width=0, height=0 , layer_count=1, view_mask=0, "
"color_attachment_count=0, p_color_attachments=" +
endl() + ", p_depth_attachment=" + endl() +
" image_view=0x2, "
"image_layout=VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, "
"resolve_mode=VK_RESOLVE_MODE_NONE, resolve_image_view=0, "
"resolve_image_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"load_op=VK_ATTACHMENT_LOAD_OP_DONT_CARE, "
"store_op=VK_ATTACHMENT_STORE_OP_STORE" +
endl() + ")",
log[2]);
EXPECT_EQ("set_viewport(num_viewports=1)", log[3]);
EXPECT_EQ("set_scissor(num_scissors=1)", log[4]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline=0x3)",
log[5]);
EXPECT_EQ("draw(vertex_count=4, instance_count=1, first_vertex=0, first_instance=0)", log[6]);
EXPECT_EQ("end_rendering()", log[7]);
/* Post-barrier: revert GENERAL -> DEPTH_ATTACHMENT_OPTIMAL */
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_ALL_COMMANDS_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_SHADER_READ_BIT, "
"VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, "
"VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, "
"VK_ACCESS_TRANSFER_WRITE_BIT, dst_access_mask=VK_ACCESS_SHADER_READ_BIT, "
"VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, "
"VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, "
"VK_ACCESS_TRANSFER_WRITE_BIT, old_layout=VK_IMAGE_LAYOUT_GENERAL, "
"new_layout=VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_DEPTH_BIT, base_mip_level=0, "
"level_count=4294967295, "
"base_array_layer=1, layer_count=4294967295 )" +
endl() + ")",
log[8]);
}
INSTANTIATE_TEST_SUITE_P(, VKRenderGraphTestRender, ::testing::Values(true, false));
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,466 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "vk_render_graph_test_types.hh"
namespace blender::gpu::render_graph {
class VKRenderGraphTestTransfer : public VKRenderGraphTest {};
/**
* Fill a single buffer and read it back.
*/
TEST_F(VKRenderGraphTestTransfer, fill_and_read_back)
{
VkHandle<VkBuffer> buffer(1u);
resources.add_buffer(buffer);
VKFillBufferNode::CreateInfo fill_buffer = {buffer, 1024, 42};
render_graph->add_node(fill_buffer);
submit(render_graph, command_buffer);
EXPECT_EQ(1, log.size());
EXPECT_EQ("fill_buffer(dst_buffer=0x1, dst_offset=0, size=1024, data=42)", log[0]);
}
/**
* Fill a single buffer, copy it to a staging buffer and read the staging buffer back.
*/
TEST_F(VKRenderGraphTestTransfer, fill_transfer_and_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkBuffer> staging_buffer(2u);
resources.add_buffer(buffer);
VKFillBufferNode::CreateInfo fill_buffer = {buffer, 1024, 42};
render_graph->add_node(fill_buffer);
resources.add_buffer(staging_buffer);
VKCopyBufferNode::CreateInfo copy_buffer = {};
copy_buffer.src_buffer = buffer;
copy_buffer.dst_buffer = staging_buffer;
copy_buffer.region.srcOffset = 0;
copy_buffer.region.dstOffset = 0;
copy_buffer.region.size = 1024;
render_graph->add_node(copy_buffer);
submit(render_graph, command_buffer);
EXPECT_EQ(3, log.size());
EXPECT_EQ("fill_buffer(dst_buffer=0x1, dst_offset=0, size=1024, data=42)", log[0]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_READ_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[1]);
EXPECT_EQ("copy_buffer(src_buffer=0x1, dst_buffer=0x2" + endl() +
" - region(src_offset=0, dst_offset=0, size=1024)" + endl() + ")",
log[2]);
}
/**
* Fill a buffer twice, before reading back.
*
* Between the two fills a write->write barrier should be created.
*/
TEST_F(VKRenderGraphTestTransfer, fill_fill_read_back)
{
VkHandle<VkBuffer> buffer(1u);
resources.add_buffer(buffer);
VKFillBufferNode::CreateInfo fill_buffer_1 = {buffer, 1024, 0};
render_graph->add_node(fill_buffer_1);
VKFillBufferNode::CreateInfo fill_buffer_2 = {buffer, 1024, 42};
render_graph->add_node(fill_buffer_2);
submit(render_graph, command_buffer);
EXPECT_EQ(3, log.size());
EXPECT_EQ("fill_buffer(dst_buffer=0x1, dst_offset=0, size=1024, data=0)", log[0]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[1]);
EXPECT_EQ("fill_buffer(dst_buffer=0x1, dst_offset=0, size=1024, data=42)", log[2]);
}
/**
* Fill a single buffer, copy it to a staging buffer and read the staging buffer back.
*/
TEST_F(VKRenderGraphTestTransfer, clear_clear_copy_and_read_back)
{
VkHandle<VkImage> src_image(1u);
VkHandle<VkImage> dst_image(2u);
VkHandle<VkBuffer> staging_buffer(3u);
resources.add_image(src_image, false);
resources.add_image(dst_image, false);
resources.add_buffer(staging_buffer);
VkClearColorValue color_white = {};
color_white.float32[0] = 1.0f;
color_white.float32[1] = 1.0f;
color_white.float32[2] = 1.0f;
color_white.float32[3] = 1.0f;
VkClearColorValue color_black = {};
color_black.float32[0] = 0.0f;
color_black.float32[1] = 0.0f;
color_black.float32[2] = 0.0f;
color_black.float32[3] = 1.0f;
VKClearColorImageNode::CreateInfo clear_color_image_src = {};
clear_color_image_src.vk_image = src_image;
clear_color_image_src.vk_clear_color_value = color_white;
VKClearColorImageNode::CreateInfo clear_color_image_dst = {};
clear_color_image_dst.vk_image = dst_image;
clear_color_image_dst.vk_clear_color_value = color_black;
VKCopyImageNode::CreateInfo copy_image = {};
copy_image.node_data.src_image = src_image;
copy_image.node_data.dst_image = dst_image;
copy_image.node_data.mip_levels = 1u;
copy_image.node_data.region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_image.node_data.region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_image.vk_image_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
VKCopyImageToBufferNode::CreateInfo copy_dst_image_to_buffer = {};
copy_dst_image_to_buffer.node_data.src_image = dst_image;
copy_dst_image_to_buffer.node_data.dst_buffer = staging_buffer;
copy_dst_image_to_buffer.node_data.region.imageSubresource.aspectMask =
VK_IMAGE_ASPECT_COLOR_BIT;
copy_dst_image_to_buffer.vk_image_aspects = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph->add_node(clear_color_image_src);
render_graph->add_node(clear_color_image_dst);
render_graph->add_node(copy_image);
render_graph->add_node(copy_dst_image_to_buffer);
submit(render_graph, command_buffer);
EXPECT_EQ(8, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=, dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ("clear_color_image(image=0x1, image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)",
log[1]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=, dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[2]);
EXPECT_EQ("clear_color_image(image=0x2, image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)",
log[3]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_READ_BIT, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[4]);
EXPECT_EQ(
"copy_image(src_image=0x1, src_image_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, "
"dst_image=0x2, dst_image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL" +
endl() + " - region(src_subresource=" + endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, mip_level=0, base_array_layer=0, "
"layer_count=0 , src_offset=" +
endl() + " x=0, y=0, z=0 , dst_subresource=" + endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, mip_level=0, base_array_layer=0, "
"layer_count=0 , dst_offset=" +
endl() + " x=0, y=0, z=0 , extent=" + endl() + " width=0, height=0, depth=0 )" +
endl() + ")",
log[5]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_READ_BIT, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[6]);
EXPECT_EQ(
"copy_image_to_buffer(src_image=0x2, src_image_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, "
"dst_buffer=0x3" +
endl() +
" - region(buffer_offset=0, buffer_row_length=0, buffer_image_height=0, "
"image_subresource=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, mip_level=0, base_array_layer=0, "
"layer_count=0 , image_offset=" +
endl() + " x=0, y=0, z=0 , image_extent=\n width=0, height=0, depth=0 )" +
endl() + ")",
log[7]);
}
/**
* Clear an image, blit it to another image, copy to a staging buffer and read back.
*/
TEST_F(VKRenderGraphTestTransfer, clear_blit_copy_and_read_back)
{
VkHandle<VkImage> src_image(1u);
VkHandle<VkImage> dst_image(2u);
VkHandle<VkBuffer> staging_buffer(3u);
resources.add_image(src_image, false);
resources.add_image(dst_image, false);
resources.add_buffer(staging_buffer);
VkClearColorValue color_black = {};
color_black.float32[0] = 0.0f;
color_black.float32[1] = 0.0f;
color_black.float32[2] = 0.0f;
color_black.float32[3] = 1.0f;
VkImageBlit vk_image_blit = {};
VKClearColorImageNode::CreateInfo clear_color_image_src = {};
clear_color_image_src.vk_image = src_image;
clear_color_image_src.vk_clear_color_value = color_black;
VKCopyImageToBufferNode::CreateInfo copy_dst_image_to_buffer = {};
copy_dst_image_to_buffer.node_data.src_image = dst_image;
copy_dst_image_to_buffer.node_data.dst_buffer = staging_buffer;
copy_dst_image_to_buffer.node_data.region.imageSubresource.aspectMask =
VK_IMAGE_ASPECT_COLOR_BIT;
copy_dst_image_to_buffer.vk_image_aspects = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph->add_node(clear_color_image_src);
VKBlitImageNode::CreateInfo blit_image = {src_image, dst_image, vk_image_blit, VK_FILTER_LINEAR};
render_graph->add_node(blit_image);
render_graph->add_node(copy_dst_image_to_buffer);
submit(render_graph, command_buffer);
EXPECT_EQ(6, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=, dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ("clear_color_image(image=0x1, image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)",
log[1]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_READ_BIT, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image=0x1, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() +
" - image_barrier(src_access_mask=, dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[2]);
EXPECT_EQ(
"blit_image(src_image=0x1, src_image_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, "
"dst_image=0x2, dst_image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"filter=VK_FILTER_LINEAR" +
endl() + " - region(src_subresource=" + endl() +
" aspect_mask=, mip_level=0, base_array_layer=0, layer_count=0 , dst_subresource=" +
endl() + " aspect_mask=, mip_level=0, base_array_layer=0, layer_count=0 )" + endl() +
")",
log[3]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_READ_BIT, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[4]);
EXPECT_EQ(
"copy_image_to_buffer(src_image=0x2, src_image_layout=VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, "
"dst_buffer=0x3" +
endl() +
" - region(buffer_offset=0, buffer_row_length=0, buffer_image_height=0, "
"image_subresource=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, mip_level=0, base_array_layer=0, "
"layer_count=0 , image_offset=" +
endl() + " x=0, y=0, z=0 , image_extent=" + endl() +
" width=0, height=0, depth=0 )" + endl() + ")",
log[5]);
}
/**
* Modify a previous added copy buffer command.
*/
TEST_F(VKRenderGraphTestTransfer, copy_buffer_modify_data)
{
VkHandle<VkBuffer> buffer_src(1u);
VkHandle<VkBuffer> buffer_dst(2u);
resources.add_buffer(buffer_src);
resources.add_buffer(buffer_dst);
VKCopyBufferNode::CreateInfo copy_buffer = {buffer_src, buffer_dst, {0, 0, 32}};
NodeHandle copy_buffer_handle = render_graph->add_node(copy_buffer);
VKCopyBufferNode::Data &copy_buffer_data = render_graph->get_node_data(copy_buffer_handle);
copy_buffer_data.region.size = 64;
submit(render_graph, command_buffer);
EXPECT_EQ(1, log.size());
EXPECT_EQ("copy_buffer(src_buffer=0x1, dst_buffer=0x2" + endl() +
" - region(src_offset=0, dst_offset=0, size=64)" + endl() + ")",
log[0]);
}
/**
* Test that a pipeline barrier between a buffer-to-image copy and a compute dispatch
* has the correct srcAccessMask.
*
* This validates that srcAccessMask is properly updated when adjusting an existing
* barrier in add_image_barrier(), preventing WRITE_AFTER_WRITE hazards.
*
* Reproduces the issue seen with VKTexturePool where a pool texture written by
* a staging buffer upload is subsequently read by a compute shader.
*/
TEST_F(VKRenderGraphTestTransfer, copy_buffer_to_image_dispatch_read_aliased_image)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkImage> image(2u);
VkHandle<VkPipeline> pipeline(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set(5u);
resources.add_buffer(buffer);
resources.add_aliased_image(image, false);
VKCopyBufferToImageNode::CreateInfo copy_buffer_to_image = {};
copy_buffer_to_image.node_data.src_buffer = buffer;
copy_buffer_to_image.node_data.dst_image = image;
copy_buffer_to_image.node_data.region.imageExtent.width = 64;
copy_buffer_to_image.node_data.region.imageExtent.height = 64;
copy_buffer_to_image.node_data.region.imageExtent.depth = 1;
copy_buffer_to_image.node_data.region.imageOffset.x = 0;
copy_buffer_to_image.node_data.region.imageOffset.y = 0;
copy_buffer_to_image.node_data.region.imageOffset.z = 0;
copy_buffer_to_image.node_data.region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_buffer_to_image.node_data.region.imageSubresource.mipLevel = 0;
copy_buffer_to_image.node_data.region.imageSubresource.baseArrayLayer = 0;
copy_buffer_to_image.node_data.region.imageSubresource.layerCount = 1;
copy_buffer_to_image.vk_image_aspects = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph->add_node(copy_buffer_to_image);
VKResourceAccessInfo access_info = {};
access_info.images.append({image, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT, {}});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
render_graph->add_node(dispatch_info);
submit(render_graph, command_buffer);
EXPECT_EQ(6, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_MEMORY_READ_BIT, "
"VK_ACCESS_MEMORY_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"old_layout=VK_IMAGE_LAYOUT_UNDEFINED, new_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[0]);
EXPECT_EQ(
"copy_buffer_to_image(src_buffer=0x1, dst_image=0x2, "
"src_image_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL" +
endl() +
" - region(buffer_offset=0, buffer_row_length=0, buffer_image_height=0, "
"image_subresource=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, mip_level=0, base_array_layer=0, "
"layer_count=1 , image_offset=" +
endl() + " x=0, y=0, z=0 , image_extent=" + endl() +
" width=64, height=64, depth=1 )" + endl() + ")",
log[1]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TRANSFER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - image_barrier(src_access_mask=VK_ACCESS_TRANSFER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_READ_BIT, "
"old_layout=VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, "
"new_layout=VK_IMAGE_LAYOUT_GENERAL, image=0x2, subresource_range=" +
endl() +
" aspect_mask=VK_IMAGE_ASPECT_COLOR_BIT, base_mip_level=0, level_count=4294967295, "
"base_array_layer=0, layer_count=4294967295 )" +
endl() + ")",
log[2]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[3]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[4]);
EXPECT_EQ("dispatch(group_count_x=1, group_count_y=1, group_count_z=1)", log[5]);
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,589 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <locale>
#include <sstream>
#include "render_graph/vk_render_graph.hh"
#include "vk_common.hh"
#include "vk_to_string.hh"
namespace blender::gpu::render_graph {
BLI_INLINE std::string &endl()
{
static std::string endl;
if (endl.empty()) {
std::stringstream ss;
ss << std::endl;
endl = ss.str();
}
return endl;
}
class CommandBufferLog : public VKCommandBufferInterface {
Vector<std::string> &log_;
bool is_recording_ = false;
public:
CommandBufferLog(Vector<std::string> &log, bool use_dynamic_rendering_local_read_ = true)
: log_(log)
{
use_dynamic_rendering_local_read = use_dynamic_rendering_local_read_;
}
virtual ~CommandBufferLog() {}
void begin_recording() override
{
EXPECT_FALSE(is_recording_);
is_recording_ = true;
}
void end_recording() override
{
EXPECT_TRUE(is_recording_);
is_recording_ = false;
}
void bind_pipeline(VkPipelineBindPoint pipeline_bind_point, VkPipeline pipeline) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "bind_pipeline(";
ss << "pipeline_bind_point=" << to_string(pipeline_bind_point);
ss << ", pipeline=" << to_string(pipeline);
ss << ")";
log_.append(ss.str());
}
void bind_descriptor_sets(VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout,
uint32_t first_set,
uint32_t descriptor_set_count,
const VkDescriptorSet *p_descriptor_sets,
uint32_t dynamic_offset_count,
const uint32_t *p_dynamic_offsets) override
{
UNUSED_VARS(pipeline_bind_point,
layout,
first_set,
descriptor_set_count,
p_descriptor_sets,
dynamic_offset_count,
p_dynamic_offsets);
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "bind_descriptor_sets(";
ss << "pipeline_bind_point=" << to_string(pipeline_bind_point);
ss << ", layout=" << to_string(layout);
ss << ", p_descriptor_sets=" << to_string(p_descriptor_sets[0]);
ss << ")";
log_.append(ss.str());
}
void bind_index_buffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType index_type) override
{
UNUSED_VARS(buffer, offset, index_type);
EXPECT_TRUE(is_recording_);
GTEST_FAIL() << __func__ << " not implemented!";
}
void bind_vertex_buffers(uint32_t first_binding,
uint32_t binding_count,
const VkBuffer *p_buffers,
const VkDeviceSize *p_offsets) override
{
UNUSED_VARS(first_binding, binding_count, p_buffers, p_offsets);
EXPECT_TRUE(is_recording_);
GTEST_FAIL() << __func__ << " not implemented!";
}
void draw(uint32_t vertex_count,
uint32_t instance_count,
uint32_t first_vertex,
uint32_t first_instance) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "draw(";
ss << "vertex_count=" << vertex_count;
ss << ", instance_count=" << instance_count;
ss << ", first_vertex=" << first_vertex;
ss << ", first_instance=" << first_instance;
ss << ")";
log_.append(ss.str());
}
void draw_indexed(uint32_t index_count,
uint32_t instance_count,
uint32_t first_index,
int32_t vertex_offset,
uint32_t first_instance) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "draw_indexed(";
ss << "index_count=" << index_count;
ss << ", instance_count=" << instance_count;
ss << ", first_index=" << first_index;
ss << ", vertex_offset=" << vertex_offset;
ss << ", first_instance=" << first_instance;
ss << ")";
log_.append(ss.str());
}
void draw_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride) override
{
UNUSED_VARS(buffer, offset, draw_count, stride);
EXPECT_TRUE(is_recording_);
GTEST_FAIL() << __func__ << " not implemented!";
}
void draw_indexed_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride) override
{
UNUSED_VARS(buffer, offset, draw_count, stride);
EXPECT_TRUE(is_recording_);
GTEST_FAIL() << __func__ << " not implemented!";
}
void dispatch(uint32_t group_count_x, uint32_t group_count_y, uint32_t group_count_z) override
{
UNUSED_VARS(group_count_x, group_count_y, group_count_z);
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "dispatch(";
ss << "group_count_x=" << group_count_x;
ss << ", group_count_y=" << group_count_y;
ss << ", group_count_z=" << group_count_z;
ss << ")";
log_.append(ss.str());
}
void dispatch_indirect(VkBuffer buffer, VkDeviceSize offset) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "dispatch_indirect(";
ss << "buffer=" << to_string(buffer);
ss << ", offset=" << offset;
ss << ")";
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 << ")";
log_.append(ss.str());
}
void copy_buffer(VkBuffer src_buffer,
VkBuffer dst_buffer,
uint32_t region_count,
const VkBufferCopy *p_regions) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "copy_buffer(";
ss << "src_buffer=" << to_string(src_buffer);
ss << ", dst_buffer=" << to_string(dst_buffer);
ss << std::endl;
for (const VkBufferCopy &region : Span<const VkBufferCopy>(p_regions, region_count)) {
ss << " - region(" << to_string(region, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void copy_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageCopy *p_regions) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "copy_image(";
ss << "src_image=" << to_string(src_image);
ss << ", src_image_layout=" << to_string(src_image_layout);
ss << ", dst_image=" << to_string(dst_image);
ss << ", dst_image_layout=" << to_string(dst_image_layout);
ss << std::endl;
for (const VkImageCopy &region : Span<const VkImageCopy>(p_regions, region_count)) {
ss << " - region(" << to_string(region, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void blit_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageBlit *p_regions,
VkFilter filter) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "blit_image(";
ss << "src_image=" << to_string(src_image);
ss << ", src_image_layout=" << to_string(src_image_layout);
ss << ", dst_image=" << to_string(dst_image);
ss << ", dst_image_layout=" << to_string(dst_image_layout);
ss << ", filter=" << to_string(filter);
ss << std::endl;
for (const VkImageBlit &region : Span<const VkImageBlit>(p_regions, region_count)) {
ss << " - region(" << to_string(region, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void copy_buffer_to_image(VkBuffer src_buffer,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkBufferImageCopy *p_regions) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "copy_buffer_to_image(";
ss << "src_buffer=" << to_string(src_buffer);
ss << ", dst_image=" << to_string(dst_image);
ss << ", src_image_layout=" << to_string(dst_image_layout);
ss << std::endl;
for (const VkBufferImageCopy &region : Span<const VkBufferImageCopy>(p_regions, region_count))
{
ss << " - region(" << to_string(region, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void copy_image_to_buffer(VkImage src_image,
VkImageLayout src_image_layout,
VkBuffer dst_buffer,
uint32_t region_count,
const VkBufferImageCopy *p_regions) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "copy_image_to_buffer(";
ss << "src_image=" << to_string(src_image);
ss << ", src_image_layout=" << to_string(src_image_layout);
ss << ", dst_buffer=" << to_string(dst_buffer);
ss << std::endl;
for (const VkBufferImageCopy &region : Span<const VkBufferImageCopy>(p_regions, region_count))
{
ss << " - region(" << to_string(region, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void fill_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize size,
uint32_t data) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "fill_buffer(";
ss << "dst_buffer=" << to_string(dst_buffer);
ss << ", dst_offset=" << dst_offset;
ss << ", size=" << size;
ss << ", data=" << data;
ss << ")";
log_.append(ss.str());
}
void clear_color_image(VkImage image,
VkImageLayout image_layout,
const VkClearColorValue *p_color,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges) override
{
UNUSED_VARS(p_color, range_count, p_ranges);
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "clear_color_image(";
ss << "image=" << to_string(image);
ss << ", image_layout=" << to_string(image_layout);
ss << ")";
log_.append(ss.str());
}
void clear_depth_stencil_image(VkImage image,
VkImageLayout image_layout,
const VkClearDepthStencilValue *p_depth_stencil,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges) override
{
UNUSED_VARS(image, image_layout, p_depth_stencil, range_count, p_ranges);
EXPECT_TRUE(is_recording_);
GTEST_FAIL() << __func__ << " not implemented!";
}
void clear_attachments(uint32_t attachment_count,
const VkClearAttachment *p_attachments,
uint32_t rect_count,
const VkClearRect *p_rects) override
{
UNUSED_VARS(attachment_count, p_attachments, rect_count, p_rects);
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "clear_attachments(";
for (const VkClearAttachment &attachment :
Span<VkClearAttachment>(p_attachments, attachment_count))
{
ss << " - attachment(" << to_string(attachment, 1) << ")" << std::endl;
}
for (const VkClearRect &rect : Span<VkClearRect>(p_rects, rect_count)) {
ss << " - rect(" << to_string(rect, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void pipeline_barrier(VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags,
uint32_t memory_barrier_count,
const VkMemoryBarrier *p_memory_barriers,
uint32_t buffer_memory_barrier_count,
const VkBufferMemoryBarrier *p_buffer_memory_barriers,
uint32_t image_memory_barrier_count,
const VkImageMemoryBarrier *p_image_memory_barriers) override
{
UNUSED_VARS(dependency_flags, memory_barrier_count, p_memory_barriers);
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "pipeline_barrier(";
ss << "src_stage_mask=" << to_string_vk_pipeline_stage_flags(src_stage_mask);
ss << ", dst_stage_mask=" << to_string_vk_pipeline_stage_flags(dst_stage_mask);
ss << std::endl;
for (VkImageMemoryBarrier image_barrier :
Span<VkImageMemoryBarrier>(p_image_memory_barriers, image_memory_barrier_count))
{
ss << " - image_barrier(" << to_string(image_barrier, 1) << ")" << std::endl;
}
for (VkBufferMemoryBarrier buffer_barrier :
Span<VkBufferMemoryBarrier>(p_buffer_memory_barriers, buffer_memory_barrier_count))
{
ss << " - buffer_barrier(" << to_string(buffer_barrier, 1) << ")" << std::endl;
}
ss << ")";
log_.append(ss.str());
}
void push_constants(VkPipelineLayout layout,
VkShaderStageFlags stage_flags,
uint32_t offset,
uint32_t size,
const void *p_values) override
{
UNUSED_VARS(layout, stage_flags, offset, size, p_values);
EXPECT_TRUE(is_recording_);
GTEST_FAIL() << __func__ << " not implemented!";
}
void begin_rendering(const VkRenderingInfo *p_rendering_info) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "begin_rendering(";
ss << "p_rendering_info=" << to_string(*p_rendering_info);
ss << ")";
log_.append(ss.str());
}
void end_rendering() override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "end_rendering()";
log_.append(ss.str());
}
void begin_query(VkQueryPool /*vk_query_pool*/,
uint32_t /*query_index*/,
VkQueryControlFlags /*vk_query_control_flags*/) override
{
}
void end_query(VkQueryPool /*vk_query_pool*/, uint32_t /*query_index*/) override {}
void reset_query_pool(VkQueryPool /*vk_query_pool*/,
uint32_t /*first_query*/,
uint32_t /*query_count*/) override
{
}
void set_viewport(const Vector<VkViewport> viewports) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_viewport(num_viewports=" << viewports.size() << ")";
log_.append(ss.str());
}
void set_scissor(const Vector<VkRect2D> scissors) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_scissor(num_scissors=" << scissors.size() << ")";
log_.append(ss.str());
}
void set_line_width(const float line_width) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_line_width(line_width=" << line_width << ")";
log_.append(ss.str());
}
void set_stencil_compare_mask(const uint32_t compare_mask) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_stencil_compare_mask(mask=" << compare_mask << ")";
log_.append(ss.str());
}
void set_stencil_write_mask(const uint32_t write_mask) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_stencil_write_mask(mask=" << write_mask << ")";
log_.append(ss.str());
}
void set_stencil_reference(const uint32_t reference) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_stencil_reference(reference=" << reference << ")";
log_.append(ss.str());
}
void set_front_face(const VkFrontFace front_face) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_front_face(front_face=" << front_face << ")";
log_.append(ss.str());
}
void set_vertex_input(
Span<VkVertexInputBindingDescription2EXT> vertex_binding_descriptions,
Span<VkVertexInputAttributeDescription2EXT> vertex_attribute_descriptions) override
{
EXPECT_TRUE(is_recording_);
std::stringstream ss;
ss << "set_vertex_input(vertexBindingDescriptionCount=" << vertex_binding_descriptions.size()
<< ", vertexAttributeDescriptionCount=" << vertex_attribute_descriptions.size() << ")";
log_.append(ss.str());
}
void begin_debug_utils_label(const VkDebugUtilsLabelEXT * /*vk_debug_utils_label*/) override {}
void end_debug_utils_label() override {}
};
class VKRenderGraphTest : public ::testing::Test {
public:
VKRenderGraphTest()
{
resources.use_dynamic_rendering_local_read = use_dynamic_rendering_local_read;
render_graph = std::make_unique<VKRenderGraph>(resources);
command_buffer = std::make_unique<CommandBufferLog>(log, use_dynamic_rendering_local_read);
}
protected:
Vector<std::string> log;
VKResourceStateTracker resources;
std::unique_ptr<VKRenderGraph> render_graph;
std::unique_ptr<CommandBufferLog> command_buffer;
bool use_dynamic_rendering_local_read = true;
};
class VKRenderGraphTest_P : public ::testing::TestWithParam<std::tuple<bool>> {
public:
VKRenderGraphTest_P()
{
use_dynamic_rendering_local_read = std::get<0>(GetParam());
resources.use_dynamic_rendering_local_read = use_dynamic_rendering_local_read;
render_graph = std::make_unique<VKRenderGraph>(resources);
command_buffer = std::make_unique<CommandBufferLog>(log, use_dynamic_rendering_local_read);
}
protected:
VkImageLayout color_attachment_layout() const
{
return use_dynamic_rendering_local_read ? VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
std::string color_attachment_layout_str() const
{
return use_dynamic_rendering_local_read ? "VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR" :
"VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL";
}
Vector<std::string> log;
VKResourceStateTracker resources;
std::unique_ptr<VKRenderGraph> render_graph;
std::unique_ptr<CommandBufferLog> command_buffer;
bool use_dynamic_rendering_local_read = true;
};
/**
* Union to create a dummy vulkan handler.
*
* Due to platform differences the actual VKObjectType type can be different (`uint64_t` or
* `VkObjectType_T*`).
*/
template<typename VKObjectType> union VkHandle {
VKObjectType vk_handle;
uint64_t handle;
VkHandle(uint64_t handle) : handle(handle) {}
operator VKObjectType() const
{
return vk_handle;
}
};
static inline void submit(std::unique_ptr<VKRenderGraph> &render_graph,
std::unique_ptr<CommandBufferLog> &command_buffer)
{
VKScheduler scheduler;
VKCommandBuilder command_builder;
Span<render_graph::NodeHandle> node_handles = scheduler.select_nodes(*render_graph);
command_builder.build_nodes(*render_graph, *command_buffer, node_handles);
command_buffer->begin_recording();
command_builder.record_commands(*render_graph, *command_buffer, node_handles);
command_buffer->end_recording();
render_graph->reset();
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,352 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_command_buffer_wrapper.hh"
#include "vk_backend.hh"
#include "vk_device.hh"
namespace blender::gpu::render_graph {
VKCommandBufferWrapper::VKCommandBufferWrapper(VkCommandBuffer vk_command_buffer,
const VKExtensions &extensions)
: vk_command_buffer_(vk_command_buffer)
{
use_dynamic_rendering_local_read = extensions.dynamic_rendering_local_read;
}
void VKCommandBufferWrapper::begin_recording()
{
VkCommandBufferBeginInfo vk_command_buffer_begin_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
nullptr,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
nullptr};
vkBeginCommandBuffer(vk_command_buffer_, &vk_command_buffer_begin_info);
}
void VKCommandBufferWrapper::end_recording()
{
vkEndCommandBuffer(vk_command_buffer_);
}
void VKCommandBufferWrapper::bind_pipeline(VkPipelineBindPoint pipeline_bind_point,
VkPipeline pipeline)
{
vkCmdBindPipeline(vk_command_buffer_, pipeline_bind_point, pipeline);
}
void VKCommandBufferWrapper::bind_descriptor_sets(VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout,
uint32_t first_set,
uint32_t descriptor_set_count,
const VkDescriptorSet *p_descriptor_sets,
uint32_t dynamic_offset_count,
const uint32_t *p_dynamic_offsets)
{
vkCmdBindDescriptorSets(vk_command_buffer_,
pipeline_bind_point,
layout,
first_set,
descriptor_set_count,
p_descriptor_sets,
dynamic_offset_count,
p_dynamic_offsets);
}
void VKCommandBufferWrapper::bind_index_buffer(VkBuffer buffer,
VkDeviceSize offset,
VkIndexType index_type)
{
vkCmdBindIndexBuffer(vk_command_buffer_, buffer, offset, index_type);
}
void VKCommandBufferWrapper::bind_vertex_buffers(uint32_t first_binding,
uint32_t binding_count,
const VkBuffer *p_buffers,
const VkDeviceSize *p_offsets)
{
vkCmdBindVertexBuffers(vk_command_buffer_, first_binding, binding_count, p_buffers, p_offsets);
}
void VKCommandBufferWrapper::draw(uint32_t vertex_count,
uint32_t instance_count,
uint32_t first_vertex,
uint32_t first_instance)
{
vkCmdDraw(vk_command_buffer_, vertex_count, instance_count, first_vertex, first_instance);
}
void VKCommandBufferWrapper::draw_indexed(uint32_t index_count,
uint32_t instance_count,
uint32_t first_index,
int32_t vertex_offset,
uint32_t first_instance)
{
vkCmdDrawIndexed(
vk_command_buffer_, index_count, instance_count, first_index, vertex_offset, first_instance);
}
void VKCommandBufferWrapper::draw_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride)
{
vkCmdDrawIndirect(vk_command_buffer_, buffer, offset, draw_count, stride);
}
void VKCommandBufferWrapper::draw_indexed_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride)
{
vkCmdDrawIndexedIndirect(vk_command_buffer_, buffer, offset, draw_count, stride);
}
void VKCommandBufferWrapper::dispatch(uint32_t group_count_x,
uint32_t group_count_y,
uint32_t group_count_z)
{
vkCmdDispatch(vk_command_buffer_, group_count_x, group_count_y, group_count_z);
}
void VKCommandBufferWrapper::dispatch_indirect(VkBuffer buffer, VkDeviceSize offset)
{
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,
const VkBufferCopy *p_regions)
{
vkCmdCopyBuffer(vk_command_buffer_, src_buffer, dst_buffer, region_count, p_regions);
}
void VKCommandBufferWrapper::copy_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageCopy *p_regions)
{
vkCmdCopyImage(vk_command_buffer_,
src_image,
src_image_layout,
dst_image,
dst_image_layout,
region_count,
p_regions);
}
void VKCommandBufferWrapper::blit_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageBlit *p_regions,
VkFilter filter)
{
vkCmdBlitImage(vk_command_buffer_,
src_image,
src_image_layout,
dst_image,
dst_image_layout,
region_count,
p_regions,
filter);
}
void VKCommandBufferWrapper::copy_buffer_to_image(VkBuffer src_buffer,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkBufferImageCopy *p_regions)
{
vkCmdCopyBufferToImage(
vk_command_buffer_, src_buffer, dst_image, dst_image_layout, region_count, p_regions);
}
void VKCommandBufferWrapper::copy_image_to_buffer(VkImage src_image,
VkImageLayout src_image_layout,
VkBuffer dst_buffer,
uint32_t region_count,
const VkBufferImageCopy *p_regions)
{
vkCmdCopyImageToBuffer(
vk_command_buffer_, src_image, src_image_layout, dst_buffer, region_count, p_regions);
}
void VKCommandBufferWrapper::fill_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize size,
uint32_t data)
{
vkCmdFillBuffer(vk_command_buffer_, dst_buffer, dst_offset, size, data);
}
void VKCommandBufferWrapper::clear_color_image(VkImage image,
VkImageLayout image_layout,
const VkClearColorValue *p_color,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges)
{
vkCmdClearColorImage(vk_command_buffer_, image, image_layout, p_color, range_count, p_ranges);
}
void VKCommandBufferWrapper::clear_depth_stencil_image(
VkImage image,
VkImageLayout image_layout,
const VkClearDepthStencilValue *p_depth_stencil,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges)
{
vkCmdClearDepthStencilImage(
vk_command_buffer_, image, image_layout, p_depth_stencil, range_count, p_ranges);
}
void VKCommandBufferWrapper::clear_attachments(uint32_t attachment_count,
const VkClearAttachment *p_attachments,
uint32_t rect_count,
const VkClearRect *p_rects)
{
vkCmdClearAttachments(vk_command_buffer_, attachment_count, p_attachments, rect_count, p_rects);
}
void VKCommandBufferWrapper::pipeline_barrier(
VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags,
uint32_t memory_barrier_count,
const VkMemoryBarrier *p_memory_barriers,
uint32_t buffer_memory_barrier_count,
const VkBufferMemoryBarrier *p_buffer_memory_barriers,
uint32_t image_memory_barrier_count,
const VkImageMemoryBarrier *p_image_memory_barriers)
{
vkCmdPipelineBarrier(vk_command_buffer_,
src_stage_mask,
dst_stage_mask,
dependency_flags,
memory_barrier_count,
p_memory_barriers,
buffer_memory_barrier_count,
p_buffer_memory_barriers,
image_memory_barrier_count,
p_image_memory_barriers);
}
void VKCommandBufferWrapper::push_constants(VkPipelineLayout layout,
VkShaderStageFlags stage_flags,
uint32_t offset,
uint32_t size,
const void *p_values)
{
vkCmdPushConstants(vk_command_buffer_, layout, stage_flags, offset, size, p_values);
}
void VKCommandBufferWrapper::set_viewport(const Vector<VkViewport> viewports)
{
vkCmdSetViewport(vk_command_buffer_, 0, viewports.size(), viewports.data());
}
void VKCommandBufferWrapper::set_scissor(const Vector<VkRect2D> scissors)
{
vkCmdSetScissor(vk_command_buffer_, 0, scissors.size(), scissors.data());
}
void VKCommandBufferWrapper::set_line_width(const float line_width)
{
vkCmdSetLineWidth(vk_command_buffer_, line_width);
}
void VKCommandBufferWrapper::set_stencil_compare_mask(const uint32_t compare_mask)
{
vkCmdSetStencilCompareMask(vk_command_buffer_, VK_STENCIL_FACE_FRONT_AND_BACK, compare_mask);
}
void VKCommandBufferWrapper::set_stencil_write_mask(const uint32_t write_mask)
{
vkCmdSetStencilWriteMask(vk_command_buffer_, VK_STENCIL_FACE_FRONT_AND_BACK, write_mask);
}
void VKCommandBufferWrapper::set_stencil_reference(const uint32_t reference)
{
vkCmdSetStencilReference(vk_command_buffer_, VK_STENCIL_FACE_FRONT_AND_BACK, reference);
}
void VKCommandBufferWrapper::set_front_face(const VkFrontFace front_face)
{
const VKDevice &device = VKBackend::get().device;
BLI_assert(device.functions.vkCmdSetFrontFace);
device.functions.vkCmdSetFrontFace(vk_command_buffer_, front_face);
}
void VKCommandBufferWrapper::set_vertex_input(
Span<VkVertexInputBindingDescription2EXT> vertex_binding_descriptions,
Span<VkVertexInputAttributeDescription2EXT> vertex_attribute_descriptions)
{
const VKDevice &device = VKBackend::get().device;
BLI_assert(device.functions.vkCmdSetVertexInput);
device.functions.vkCmdSetVertexInput(vk_command_buffer_,
vertex_binding_descriptions.size(),
vertex_binding_descriptions.data(),
vertex_attribute_descriptions.size(),
vertex_attribute_descriptions.data());
}
void VKCommandBufferWrapper::begin_rendering(const VkRenderingInfo *p_rendering_info)
{
const VKDevice &device = VKBackend::get().device;
BLI_assert(device.functions.vkCmdBeginRendering);
device.functions.vkCmdBeginRendering(vk_command_buffer_, p_rendering_info);
}
void VKCommandBufferWrapper::end_rendering()
{
const VKDevice &device = VKBackend::get().device;
BLI_assert(device.functions.vkCmdEndRendering);
device.functions.vkCmdEndRendering(vk_command_buffer_);
}
void VKCommandBufferWrapper::begin_query(VkQueryPool vk_query_pool,
uint32_t query_index,
VkQueryControlFlags vk_query_control_flags)
{
vkCmdBeginQuery(vk_command_buffer_, vk_query_pool, query_index, vk_query_control_flags);
}
void VKCommandBufferWrapper::end_query(VkQueryPool vk_query_pool, uint32_t query_index)
{
vkCmdEndQuery(vk_command_buffer_, vk_query_pool, query_index);
}
void VKCommandBufferWrapper::reset_query_pool(VkQueryPool vk_query_pool,
uint32_t first_query,
uint32_t query_count)
{
vkCmdResetQueryPool(vk_command_buffer_, vk_query_pool, first_query, query_count);
}
void VKCommandBufferWrapper::begin_debug_utils_label(
const VkDebugUtilsLabelEXT *vk_debug_utils_label)
{
const VKDevice &device = VKBackend::get().device;
if (device.functions.vkCmdBeginDebugUtilsLabel) {
device.functions.vkCmdBeginDebugUtilsLabel(vk_command_buffer_, vk_debug_utils_label);
}
}
void VKCommandBufferWrapper::end_debug_utils_label()
{
const VKDevice &device = VKBackend::get().device;
if (device.functions.vkCmdEndDebugUtilsLabel) {
device.functions.vkCmdEndDebugUtilsLabel(vk_command_buffer_);
}
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,284 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_common.hh"
namespace blender {
namespace gpu {
struct VKExtensions;
}
namespace gpu::render_graph {
class VKCommandBufferInterface {
public:
bool use_dynamic_rendering_local_read = true;
VKCommandBufferInterface() {}
virtual ~VKCommandBufferInterface() = default;
virtual void begin_recording() = 0;
virtual void end_recording() = 0;
virtual void bind_pipeline(VkPipelineBindPoint pipeline_bind_point, VkPipeline pipeline) = 0;
virtual void bind_descriptor_sets(VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout,
uint32_t first_set,
uint32_t descriptor_set_count,
const VkDescriptorSet *p_descriptor_sets,
uint32_t dynamic_offset_count,
const uint32_t *p_dynamic_offsets) = 0;
virtual void bind_index_buffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType index_type) = 0;
virtual void bind_vertex_buffers(uint32_t first_binding,
uint32_t binding_count,
const VkBuffer *p_buffers,
const VkDeviceSize *p_offsets) = 0;
virtual void draw(uint32_t vertex_count,
uint32_t instance_count,
uint32_t first_vertex,
uint32_t first_instance) = 0;
virtual void draw_indexed(uint32_t index_count,
uint32_t instance_count,
uint32_t first_index,
int32_t vertex_offset,
uint32_t first_instance) = 0;
virtual void draw_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride) = 0;
virtual void draw_indexed_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride) = 0;
virtual void dispatch(uint32_t group_count_x,
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,
const VkBufferCopy *p_regions) = 0;
virtual void copy_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageCopy *p_regions) = 0;
virtual void blit_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageBlit *p_regions,
VkFilter filter) = 0;
virtual void copy_buffer_to_image(VkBuffer src_buffer,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkBufferImageCopy *p_regions) = 0;
virtual void copy_image_to_buffer(VkImage src_image,
VkImageLayout src_image_layout,
VkBuffer dst_buffer,
uint32_t region_count,
const VkBufferImageCopy *p_regions) = 0;
virtual void fill_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize size,
uint32_t data) = 0;
virtual void clear_color_image(VkImage image,
VkImageLayout image_layout,
const VkClearColorValue *p_color,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges) = 0;
virtual void clear_depth_stencil_image(VkImage image,
VkImageLayout image_layout,
const VkClearDepthStencilValue *p_depth_stencil,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges) = 0;
virtual void clear_attachments(uint32_t attachment_count,
const VkClearAttachment *p_attachments,
uint32_t rect_count,
const VkClearRect *p_rects) = 0;
virtual void pipeline_barrier(VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags,
uint32_t memory_barrier_count,
const VkMemoryBarrier *p_memory_barriers,
uint32_t buffer_memory_barrier_count,
const VkBufferMemoryBarrier *p_buffer_memory_barriers,
uint32_t image_memory_barrier_count,
const VkImageMemoryBarrier *p_image_memory_barriers) = 0;
virtual void push_constants(VkPipelineLayout layout,
VkShaderStageFlags stage_flags,
uint32_t offset,
uint32_t size,
const void *p_values) = 0;
virtual void begin_query(VkQueryPool vk_query_pool,
uint32_t query_index,
VkQueryControlFlags vk_query_control_flags) = 0;
virtual void end_query(VkQueryPool vk_query_pool, uint32_t query_index) = 0;
virtual void reset_query_pool(VkQueryPool vk_query_pool,
uint32_t first_query,
uint32_t query_count) = 0;
/* Dynamic states. */
virtual void set_viewport(const Vector<VkViewport> viewports) = 0;
virtual void set_scissor(const Vector<VkRect2D> scissors) = 0;
virtual void set_line_width(const float line_width) = 0;
virtual void set_stencil_compare_mask(const uint32_t compare_mask) = 0;
virtual void set_stencil_write_mask(const uint32_t write_mask) = 0;
virtual void set_stencil_reference(const uint32_t reference) = 0;
/* VK_EXT_extended_dynamic_state */
virtual void set_front_face(const VkFrontFace front_face) = 0;
/* VK_EXT_vertex_input_dynamic_state */
virtual void set_vertex_input(
Span<VkVertexInputBindingDescription2EXT> vertex_binding_descriptions,
Span<VkVertexInputAttributeDescription2EXT> vertex_attribute_descriptions) = 0;
/* VK_KHR_dynamic_rendering */
virtual void begin_rendering(const VkRenderingInfo *p_rendering_info) = 0;
virtual void end_rendering() = 0;
/* VK_EXT_debug_utils */
virtual void begin_debug_utils_label(const VkDebugUtilsLabelEXT *vk_debug_utils_label) = 0;
virtual void end_debug_utils_label() = 0;
};
class VKCommandBufferWrapper : public VKCommandBufferInterface {
private:
VkCommandBuffer vk_command_buffer_ = VK_NULL_HANDLE;
public:
VKCommandBufferWrapper(VkCommandBuffer vk_command_buffer, const VKExtensions &extensions);
void begin_recording() override;
void end_recording() override;
void bind_pipeline(VkPipelineBindPoint pipeline_bind_point, VkPipeline pipeline) override;
void bind_descriptor_sets(VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout,
uint32_t first_set,
uint32_t descriptor_set_count,
const VkDescriptorSet *p_descriptor_sets,
uint32_t dynamic_offset_count,
const uint32_t *p_dynamic_offsets) override;
void bind_index_buffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType index_type) override;
void bind_vertex_buffers(uint32_t first_binding,
uint32_t binding_count,
const VkBuffer *p_buffers,
const VkDeviceSize *p_offsets) override;
void draw(uint32_t vertex_count,
uint32_t instance_count,
uint32_t first_vertex,
uint32_t first_instance) override;
void draw_indexed(uint32_t index_count,
uint32_t instance_count,
uint32_t first_index,
int32_t vertex_offset,
uint32_t first_instance) override;
void draw_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
uint32_t stride) override;
void draw_indexed_indirect(VkBuffer buffer,
VkDeviceSize offset,
uint32_t draw_count,
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,
const VkBufferCopy *p_regions) override;
void copy_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageCopy *p_regions) override;
void blit_image(VkImage src_image,
VkImageLayout src_image_layout,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkImageBlit *p_regions,
VkFilter filter) override;
void copy_buffer_to_image(VkBuffer src_buffer,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkBufferImageCopy *p_regions) override;
void copy_image_to_buffer(VkImage src_image,
VkImageLayout src_image_layout,
VkBuffer dst_buffer,
uint32_t region_count,
const VkBufferImageCopy *p_regions) override;
void fill_buffer(VkBuffer dst_buffer,
VkDeviceSize dst_offset,
VkDeviceSize size,
uint32_t data) override;
void clear_color_image(VkImage image,
VkImageLayout image_layout,
const VkClearColorValue *p_color,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges) override;
void clear_depth_stencil_image(VkImage image,
VkImageLayout image_layout,
const VkClearDepthStencilValue *p_depth_stencil,
uint32_t range_count,
const VkImageSubresourceRange *p_ranges) override;
void clear_attachments(uint32_t attachment_count,
const VkClearAttachment *p_attachments,
uint32_t rect_count,
const VkClearRect *p_rects) override;
void pipeline_barrier(VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags,
uint32_t memory_barrier_count,
const VkMemoryBarrier *p_memory_barriers,
uint32_t buffer_memory_barrier_count,
const VkBufferMemoryBarrier *p_buffer_memory_barriers,
uint32_t image_memory_barrier_count,
const VkImageMemoryBarrier *p_image_memory_barriers) override;
void push_constants(VkPipelineLayout layout,
VkShaderStageFlags stage_flags,
uint32_t offset,
uint32_t size,
const void *p_values) override;
void set_viewport(const Vector<VkViewport> viewports) override;
void set_scissor(const Vector<VkRect2D> scissors) override;
void set_line_width(const float line_width) override;
void set_front_face(const VkFrontFace front_face) override;
void set_vertex_input(
Span<VkVertexInputBindingDescription2EXT> vertex_binding_descriptions,
Span<VkVertexInputAttributeDescription2EXT> vertex_attribute_descriptions) override;
void set_stencil_compare_mask(const uint32_t compare_mask) override;
void set_stencil_write_mask(const uint32_t write_mask) override;
void set_stencil_reference(const uint32_t reference) override;
void begin_query(VkQueryPool vk_query_pool,
uint32_t query_index,
VkQueryControlFlags vk_query_control_flags) override;
void end_query(VkQueryPool vk_query_pool, uint32_t query_index) override;
void reset_query_pool(VkQueryPool, uint32_t first_query, uint32_t query_count) override;
void begin_rendering(const VkRenderingInfo *p_rendering_info) override;
void end_rendering() override;
void begin_debug_utils_label(const VkDebugUtilsLabelEXT *vk_debug_utils_label) override;
void end_debug_utils_label() override;
};
} // namespace gpu::render_graph
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,376 @@
/* 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 and mipmaps. In some cases each layer/mipmap can get
* different VkImageLayouts. To not complicate the global state tracker we track sub-resources
* only when building the barriers.
*/
struct ImageTracker {
/**
* Local reference to the active command builder.
*
* The reference is used to add VkImageMemoryBarrier's to the command builder.
*/
VKCommandBuilder &command_builder;
ImageTracker(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> tracked_attachments;
/**
* Keep track of the changes to images since the start of the rendering scope. This allows
* reverting and rebuilding the correct state when ending or resuming the render scope.
*/
struct SubImageChange {
VkImage vk_image;
VkImageLayout vk_image_layout;
VkImageAspectFlags vk_image_aspect;
VKSubImageRange subimage;
};
Vector<SubImageChange> changes;
/**
* 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 `tracked_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 tracked_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,
const VKSubImageRange &subimage,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageAspectFlags aspect,
Barrier &r_barrier);
/**
* Ensure the layout of a mipmap level.
*
* - `old_layout` should be the expected layout of the full image.
*/
void update(VkImage vk_image,
uint32_t mipmap_level,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageAspectFlags aspect,
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 execution groups and barriers.
* This method should be performed when the resources are locked.
*/
void build_nodes(VKRenderGraph &render_graph,
VKCommandBufferInterface &command_buffer,
Span<NodeHandle> 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<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 use_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,
ImageTracker &image_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,
ImageTracker &image_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,
const VKSubImageRange &subimage);
void add_image_read_barriers(VKRenderGraph &render_graph,
NodeHandle node_handle,
VkPipelineStageFlags node_stages,
ImageTracker &image_tracker,
Barrier &r_barrier,
bool within_rendering);
void add_image_write_barriers(VKRenderGraph &render_graph,
NodeHandle node_handle,
VkPipelineStageFlags node_stages,
ImageTracker &image_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

View File

@@ -0,0 +1,117 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_render_graph.hh"
#include "gpu_backend.hh"
#include <sstream>
namespace blender::gpu::render_graph {
/* -------------------------------------------------------------------- */
/** \name Render Graph
* \{ */
VKRenderGraph::VKRenderGraph(VKResourceStateTracker &resources) : resources_(resources) {}
void VKRenderGraph::reset()
{
#if 0
memstats();
#endif
links_.clear();
for (VKRenderGraphNode &node : nodes_) {
node.free_data();
}
nodes_.clear();
storage_.reset();
debug_.node_group_map.clear();
debug_.used_groups.clear();
debug_.group_stack.clear();
debug_.groups.clear();
}
void VKRenderGraph::memstats() const
{
std::cout << __func__ << " nodes: (" << nodes_.size() << "/" << nodes_.capacity() << "), "
<< "links(buffer): (" << links_.buffers.size() << "/" << links_.buffers.capacity()
<< "), "
<< "links(images): (" << links_.images.size() << "/" << links_.images.capacity()
<< ")\n";
#define PRINT_STORAGE(name) \
std::cout << " " #name " : (" << storage_.name.size() << " / " << storage_.name.capacity() \
<< ")\n "
PRINT_STORAGE(begin_rendering);
PRINT_STORAGE(clear_attachments);
PRINT_STORAGE(blit_image);
PRINT_STORAGE(copy_buffer_to_image);
PRINT_STORAGE(copy_image);
PRINT_STORAGE(copy_image_to_buffer);
PRINT_STORAGE(draw);
PRINT_STORAGE(draw_indexed);
PRINT_STORAGE(draw_indexed_indirect);
PRINT_STORAGE(draw_indirect);
#undef PRINT_STORAGE
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug
* \{ */
void VKRenderGraph::debug_group_begin(const char *name, const ColorTheme4f &color)
{
ColorTheme4f useColor = color;
if ((color == gpu::debug::GPU_DEBUG_GROUP_COLOR_DEFAULT) && (debug_.group_stack.size() > 0)) {
useColor = debug_.groups[debug_.group_stack.last()].color;
}
DebugGroupNameID name_id = debug_.groups.index_of_or_add({std::string(name), useColor});
debug_.group_stack.append(name_id);
debug_.group_used = false;
}
void VKRenderGraph::debug_group_end()
{
debug_.group_stack.pop_last();
debug_.group_used = false;
}
void VKRenderGraph::debug_print(NodeHandle node_handle) const
{
std::ostream &os = std::cout;
os << "NODE:\n";
const VKRenderGraphNode &node = nodes_[node_handle];
os << " type:" << node.type << "\n";
}
std::string VKRenderGraph::full_debug_group(NodeHandle node_handle) const
{
if ((G.debug & G_DEBUG_GPU) == 0) {
return std::string();
}
DebugGroupID debug_group = debug_.node_group_map[node_handle];
if (debug_group == -1) {
return std::string();
}
std::stringstream ss;
for (const VKRenderGraph::DebugGroupNameID &name_id : debug_.used_groups[debug_group]) {
ss << "/" << debug_.groups[name_id].name;
}
return ss.str();
}
/** \} */
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,298 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* The render graph primarily is a a graph of GPU commands that are then serialized into command
* buffers. The submission order can be altered and barriers are added for resource sync.
*
* # Building render graph
*
* The graph contains nodes that refers to resources it reads from, or modifies.
* The resources that are read from are linked to the node inputs. The resources that are written
* to are linked to the node outputs.
*
* Resources needs to be tracked as usage can alter the content of the resource. For example an
* image can be optimized for data transfer, or optimized for sampling which can use a different
* pixel layout on the device.
*
* When adding a node to the render graph the input and output links are extracted from the
* See `VKNodeInfo::build_links`.
*
* # Executing render graph
*
* Executing a render graph is done by calling `submit_for_read` or `submit_for_present`. When
* called the nodes that are needed to render the resource are determined by a `VKScheduler`. The
* nodes are converted to `vkCmd*` and recorded in the command buffer by `VKCommandBuilder`.
*
* # Thread safety
*
* When the render graph is called the device will be locked. Nodes inside the render graph relies
* on the resources which are device specific. The locked time is tiny when adding new nodes.
* During execution this takes a longer time, but the lock can be released when the commands have
* been queued. So other threads can continue.
*/
#pragma once
#include <mutex>
#include <optional>
#include <pthread.h>
#include "BKE_global.hh"
#include "BLI_color_types.hh"
#include "BLI_map.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "BKE_global.hh"
#include "vk_common.hh"
#include "vk_command_buffer_wrapper.hh"
#include "vk_command_builder.hh"
#include "vk_render_graph_links.hh"
#include "vk_resource_state_tracker.hh"
namespace blender::gpu::render_graph {
class VKScheduler;
class VKRenderGraph : public NonCopyable {
friend class VKCommandBuilder;
friend class VKScheduler;
using DebugGroupNameID = int64_t;
using DebugGroupID = int64_t;
/** All nodes inside the graph indexable via NodeHandle. */
Vector<VKRenderGraphNode, 1024> nodes_;
/**
* Node read/write links to buffer and image resources.
*/
VKRenderGraphLinks links_;
/** Storage for large node datas to improve CPU cache pre-loading. */
VKRenderGraphStorage storage_;
/**
* Not owning pointer to device resources.
*
* To improve testability the render graph doesn't access VKDevice or VKBackend directly.
* resources_ can be replaced by a local variable. This way test cases don't need to create a
* fully working context in order to test something render graph specific. Is marked optional as
* device could
*/
VKResourceStateTracker &resources_;
struct DebugGroup {
std::string name;
ColorTheme4f color;
friend bool operator==(const DebugGroup &a, const DebugGroup &b) = default;
uint64_t hash() const
{
return get_default_hash<std::string, ColorTheme4f>(name, color);
}
};
struct {
VectorSet<DebugGroup> groups;
/** Current stack of debug group names. */
Vector<DebugGroupNameID> group_stack;
/**
* Has a node been added to the current stack? If not the group stack will be added to
* used_groups.
*/
bool group_used = false;
/** All used debug groups. */
Vector<Vector<DebugGroupNameID>> used_groups;
/**
* Map of a node_handle to an index of debug group in used_groups.
*
* \code{.cc}
* int used_group_id = node_group_map[node_handle];
* const Vector<DebugGroupNameID> &used_group = used_groups[used_group_id];
* \endcode
*/
Vector<DebugGroupID> node_group_map;
} debug_;
public:
/**
* Construct a new render graph instance.
*
* To improve testability the command buffer and resources they work on are provided as a
* parameter.
*/
VKRenderGraph(VKResourceStateTracker &resources);
private:
/**
* Add a node to the render graph.
*/
template<typename NodeInfo> NodeHandle add_node(const typename NodeInfo::CreateInfo &create_info)
{
std::scoped_lock lock(resources_.mutex);
static VKRenderGraphNode node_template = {};
NodeHandle node_handle = nodes_.append_and_get_index(node_template);
#if 0
/* Useful during debugging. When a validation error occurs during submission we know the node
* type and node handle, but we don't know when and by who that specific node was added to the
* render graph. By enabling this part of the code and set the correct node_handle and node
* type a debugger can break at the moment the node has been added to the render graph. */
if (node_handle == 267 && NodeInfo::node_type == VKNodeType::DRAW) {
std::cout << "break\n";
}
#endif
VKRenderGraphNode &node = nodes_[node_handle];
node.set_node_data<NodeInfo>(storage_, create_info);
node.build_links<NodeInfo>(resources_, links_, create_info);
if (G.debug & G_DEBUG_GPU) {
if (!debug_.group_used) {
debug_.group_used = true;
debug_.used_groups.append(debug_.group_stack);
}
if (nodes_.size() > debug_.node_group_map.size()) {
debug_.node_group_map.resize(nodes_.size());
}
debug_.node_group_map[node_handle] = debug_.used_groups.size() - 1;
}
return node_handle;
}
public:
#define ADD_NODE(NODE_CLASS) \
NodeHandle add_node(const NODE_CLASS::CreateInfo &create_info) \
{ \
return add_node<NODE_CLASS>(create_info); \
}
ADD_NODE(VKBeginQueryNode)
ADD_NODE(VKBeginRenderingNode)
ADD_NODE(VKEndQueryNode)
ADD_NODE(VKEndRenderingNode)
ADD_NODE(VKClearAttachmentsNode)
ADD_NODE(VKClearColorImageNode)
ADD_NODE(VKClearDepthStencilImageNode)
ADD_NODE(VKFillBufferNode)
ADD_NODE(VKCopyBufferNode)
ADD_NODE(VKCopyBufferToImageNode)
ADD_NODE(VKCopyImageNode)
ADD_NODE(VKCopyImageToBufferNode)
ADD_NODE(VKBlitImageNode)
ADD_NODE(VKDispatchNode)
ADD_NODE(VKDispatchIndirectNode)
ADD_NODE(VKDrawNode)
ADD_NODE(VKDrawIndexedNode)
ADD_NODE(VKDrawIndexedIndirectNode)
ADD_NODE(VKDrawIndirectNode)
ADD_NODE(VKResetQueryPoolNode)
ADD_NODE(VKUpdateBufferNode)
ADD_NODE(VKUpdateMipmapsNode)
ADD_NODE(VKSynchronizationNode)
#undef ADD_NODE
/**
* Get the reference to the node data for a VKCopyBufferNode.
*
* Allows altering a previous added node. Is useful to reduce barriers when a streaming buffer
* requires data that can still fit in the previous copy command.
*/
VKCopyBufferNode::Data &get_node_data(NodeHandle node_handle)
{
VKRenderGraphNode &node = nodes_[node_handle];
BLI_assert(node.type == VKNodeType::COPY_BUFFER);
return node.copy_buffer;
}
/**
* To reduce small allocations the caller can copy push constants inside the render graph.
* The returned index range can than be used by the command builder to retrieve the push
* constants.
*/
IndexRange copy_push_constants(Span<uint8_t> push_constants)
{
int64_t start = storage_.push_constants.size();
storage_.push_constants.extend(push_constants);
return IndexRange::from_begin_size(start, push_constants.size());
}
/**
* Push a new debugging group to the stack with the given name.
*
* New nodes added to the render graph will be associated with this debug group.
*/
void debug_group_begin(const char *name, const ColorTheme4f &color);
/**
* Pop the top of the debugging group stack.
*
* New nodes added to the render graph will be associated with the parent of the current debug
* group.
*/
void debug_group_end();
/**
* Return the full debug group of the given node_handle. Returns an empty string when debug
* groups are not enabled (`--debug-gpu`).
*/
std::string full_debug_group(NodeHandle node_handle) const;
/**
* Utility function that is used during debugging.
*
* When debugging most of the time know the node_handle that is needed after the node has been
* constructed. When haunting a bug it is more useful to query what the next node handle will be
* so you can step through the node building process.
*/
NodeHandle next_node_handle()
{
return nodes_.size();
}
bool is_empty()
{
return nodes_.is_empty();
}
void debug_print(NodeHandle node_handle) const;
/**
* Reset the render graph.
*/
void reset();
void memstats() const;
/** Get the images that are linked by the given node. */
inline Span<VKRenderGraphImage> linked_images(const VKRenderGraphNode &node) const
{
return links_.images.as_span().slice(node.links.images);
}
/** Get the images that are linked by the given node_handle. */
inline Span<VKRenderGraphImage> linked_images(const NodeHandle node_handle) const
{
return linked_images(nodes_[node_handle]);
}
/** Get the buffers that are linked by the given node. */
inline Span<VKRenderGraphBuffer> linked_buffers(const VKRenderGraphNode &node) const
{
return links_.buffers.as_span().slice(node.links.buffers);
}
/** Get the buffers that are linked by the given node_handle. */
inline Span<VKRenderGraphBuffer> linked_buffers(const NodeHandle node_handle) const
{
return linked_buffers(nodes_[node_handle]);
}
private:
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <sstream>
#include "vk_render_graph_links.hh"
#include "vk_to_string.hh"
namespace blender::gpu::render_graph {
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,99 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Nodes inside the render graph are connected via links to the resources they use. These links are
* determined when adding a node to the render graph.
*
* The inputs of the node link to the resources that the node reads from. The outputs of the node
* link to the resources that the node modifies.
*
* All links inside the graph are stored inside `VKResourceDependencies`.
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "vk_common.hh"
#include "vk_resource_state_tracker.hh"
namespace blender::gpu::render_graph {
/** Which access flags are considered for write access. */
static constexpr VkAccessFlags VK_ACCESS_WRITE_MASK =
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
VK_ACCESS_HOST_WRITE_BIT;
struct VKRenderGraphResource {
/**
* Which resource is being accessed.
*/
ResourceWithStamp resource;
/**
* How is the resource being accessed.
*
* When generating pipeline barriers of a resource, the nodes access flags are evaluated to
* create src/dst access masks.
*/
VkAccessFlags vk_access_flags;
bool has_write_access() const
{
return vk_access_flags & VK_ACCESS_WRITE_MASK;
}
};
struct VKRenderGraphBuffer : public VKRenderGraphResource {};
struct VKRenderGraphImage : public VKRenderGraphResource {
/**
* When resource is an image, which layout should the image be using.
*
* When generating the commands this attribute is compared with the actual image layout of the
* the image. Additional pipeline barriers will be added to transit to the layout stored here.
*/
VkImageLayout vk_image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
/**
* Which aspect of the image is being used.
*/
VkImageAspectFlags vk_image_aspect = VK_IMAGE_ASPECT_NONE;
/**
* The layers and mipmap levels to bind.
*
* Used when layer_tracking will be enabled to transit the layout of these layers only.
*/
VKSubImageRange subimage;
};
struct VKRenderGraphLinks {
/**
* VKRenderGraphNode.links.buffers contains indices to #VKRenderGraphLinks.buffers.
*
* This reduces reallocations as otherwise complex data types are needed.
*/
Vector<VKRenderGraphBuffer> buffers;
/**
* VKRenderGraphNode.links.images only contains indices to the #VKRenderGraphLinks.images.
*
* This reduces reallocations as otherwise complex data types are needed.
*/
Vector<VKRenderGraphImage> images;
void clear()
{
buffers.clear();
images.clear();
}
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,341 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "nodes/vk_begin_query_node.hh"
#include "nodes/vk_begin_rendering_node.hh"
#include "nodes/vk_blit_image_node.hh"
#include "nodes/vk_clear_attachments_node.hh"
#include "nodes/vk_clear_color_image_node.hh"
#include "nodes/vk_clear_depth_stencil_image_node.hh"
#include "nodes/vk_copy_buffer_node.hh"
#include "nodes/vk_copy_buffer_to_image_node.hh"
#include "nodes/vk_copy_image_node.hh"
#include "nodes/vk_copy_image_to_buffer_node.hh"
#include "nodes/vk_dispatch_indirect_node.hh"
#include "nodes/vk_dispatch_node.hh"
#include "nodes/vk_draw_indexed_indirect_node.hh"
#include "nodes/vk_draw_indexed_node.hh"
#include "nodes/vk_draw_indirect_node.hh"
#include "nodes/vk_draw_node.hh"
#include "nodes/vk_end_query_node.hh"
#include "nodes/vk_end_rendering_node.hh"
#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 {
/**
* Index of a node inside the render graph.
*/
using NodeHandle = uint64_t;
/**
* Node storage for nodes that uses large data structs.
*
* Some node structs are to large to store them as part of the node. The data are stored as a
* vector of structs. Typically structs that occupy more than one cache line (64 bytes) should be
* considered to be moved here.
*
*/
struct VKRenderGraphStorage {
Vector<VKBeginRenderingNode::Data, 1024> begin_rendering;
Vector<VKClearAttachmentsNode::Data, 1024> clear_attachments;
Vector<VKBlitImageNode::Data, 1024> blit_image;
Vector<VKCopyBufferToImageNode::Data, 1024> copy_buffer_to_image;
Vector<VKCopyImageNode::Data, 1024> copy_image;
Vector<VKCopyImageToBufferNode::Data, 1024> copy_image_to_buffer;
Vector<VKDrawNode::Data, 1024> draw;
Vector<VKDrawIndexedNode::Data, 1024> draw_indexed;
Vector<VKDrawIndexedIndirectNode::Data, 1024> draw_indexed_indirect;
Vector<VKDrawIndirectNode::Data, 1024> draw_indirect;
Vector<uint8_t> push_constants;
void reset()
{
begin_rendering.clear_and_shrink();
clear_attachments.clear_and_shrink();
blit_image.clear_and_shrink();
copy_buffer_to_image.clear_and_shrink();
copy_image.clear_and_shrink();
copy_image_to_buffer.clear_and_shrink();
draw.clear_and_shrink();
draw_indexed.clear_and_shrink();
draw_indexed_indirect.clear_and_shrink();
draw_indirect.clear_and_shrink();
push_constants.clear();
}
};
/**
* Node stored inside a render graph.
*
* Node specific data in the render graph are stored in a vector to ensure that the data can be
* prefetched and removing a level of indirection. A consequence is that we cannot use class based
* nodes.
*/
struct VKRenderGraphNode {
VKNodeType type;
union {
VKBeginQueryNode::Data begin_query;
VKClearColorImageNode::Data clear_color_image;
VKClearDepthStencilImageNode::Data clear_depth_stencil_image;
VKCopyBufferNode::Data copy_buffer;
VKDispatchNode::Data dispatch;
VKDispatchIndirectNode::Data dispatch_indirect;
VKEndQueryNode::Data end_query;
VKEndRenderingNode::Data end_rendering;
VKFillBufferNode::Data fill_buffer;
VKResetQueryPoolNode::Data reset_query_pool;
VKSynchronizationNode::Data synchronization;
VKUpdateBufferNode::Data update_buffer;
VKUpdateMipmapsNode::Data update_mipmaps;
int64_t storage_index = -1;
};
struct {
/** Range where the input/output buffers are stored inside #VKRenderGraph.buffer_links_. */
IndexRange buffers;
/** Range where the input/output images are stored inside #VKRenderGraph.image_links_. */
IndexRange images;
} links;
/**
* Set the data of the node.
*
* Pre-conditions:
* - type of the node should be `VKNodeType::UNUSED`. Memory allocated for nodes are reused
* between consecutive use. Checking for unused node types will ensure that previous usage has
* been reset. Resetting is done as part of `free_data`
*/
template<typename NodeInfo>
void set_node_data(VKRenderGraphStorage &storage,
const typename NodeInfo::CreateInfo &create_info)
{
BLI_assert(type == VKNodeType::UNUSED);
/* Instance of NodeInfo is needed to call virtual methods. CPP doesn't support overloading of
* static methods. */
NodeInfo node_info;
type = NodeInfo::node_type;
node_info.set_node_data(*this, storage, create_info);
}
/**
* Build the input/output links for this.
*
* Newly created links are added to the `links` parameter.
*/
template<typename NodeInfo>
void build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links,
const typename NodeInfo::CreateInfo &create_info)
{
/* Instance of NodeInfo is needed to call virtual methods. CPP doesn't support overloading of
* static methods. */
NodeInfo node_info;
int64_t buffer_index_start = links.buffers.size();
int64_t image_index_start = links.images.size();
node_info.build_links(resources, links, create_info);
this->links.buffers = IndexRange::from_begin_end(buffer_index_start, links.buffers.size());
this->links.images = IndexRange::from_begin_end(image_index_start, links.images.size());
}
/**
* Get the pipeline stage of the node.
*
* Pipeline stage is used to update `src/dst_stage_masks` of the VKCommandBuilder.
*/
VkPipelineStageFlags pipeline_stage_get() const
{
switch (type) {
case VKNodeType::UNUSED:
return VK_PIPELINE_STAGE_NONE;
case VKNodeType::BEGIN_QUERY:
return VKBeginQueryNode::pipeline_stage;
case VKNodeType::BEGIN_RENDERING:
return VKBeginRenderingNode::pipeline_stage;
case VKNodeType::CLEAR_ATTACHMENTS:
return VKClearAttachmentsNode::pipeline_stage;
case VKNodeType::CLEAR_COLOR_IMAGE:
return VKClearColorImageNode::pipeline_stage;
case VKNodeType::CLEAR_DEPTH_STENCIL_IMAGE:
return VKClearDepthStencilImageNode::pipeline_stage;
case VKNodeType::END_QUERY:
return VKEndQueryNode::pipeline_stage;
case VKNodeType::END_RENDERING:
return VKEndRenderingNode::pipeline_stage;
case VKNodeType::FILL_BUFFER:
return VKFillBufferNode::pipeline_stage;
case VKNodeType::COPY_BUFFER:
return VKCopyBufferNode::pipeline_stage;
case VKNodeType::COPY_IMAGE:
return VKCopyImageNode::pipeline_stage;
case VKNodeType::COPY_IMAGE_TO_BUFFER:
return VKCopyImageToBufferNode::pipeline_stage;
case VKNodeType::COPY_BUFFER_TO_IMAGE:
return VKCopyBufferToImageNode::pipeline_stage;
case VKNodeType::BLIT_IMAGE:
return VKBlitImageNode::pipeline_stage;
case VKNodeType::DISPATCH:
return VKDispatchNode::pipeline_stage;
case VKNodeType::DISPATCH_INDIRECT:
return VKDispatchIndirectNode::pipeline_stage;
case VKNodeType::DRAW:
return VKDrawNode::pipeline_stage;
case VKNodeType::DRAW_INDEXED:
return VKDrawIndexedNode::pipeline_stage;
case VKNodeType::DRAW_INDEXED_INDIRECT:
return VKDrawIndexedIndirectNode::pipeline_stage;
case VKNodeType::DRAW_INDIRECT:
return VKDrawIndirectNode::pipeline_stage;
case VKNodeType::RESET_QUERY_POOL:
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;
}
BLI_assert_unreachable();
return VK_PIPELINE_STAGE_NONE;
}
/**
* Build commands for this node and record them in the given command_buffer.
*
* NOTE: Pipeline barriers should already be added. See
* `VKCommandBuilder::build_node` and `VKCommandBuilder::build_pipeline_barriers`.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
VKRenderGraphStorage &storage,
VKBoundPipelines &r_bound_pipelines)
{
switch (type) {
case VKNodeType::UNUSED: {
break;
}
#define BUILD_COMMANDS_STORAGE(NODE_TYPE, NODE_CLASS, ATTRIBUTE_NAME) \
case NODE_TYPE: { \
NODE_CLASS node_info; \
node_info.build_commands(command_buffer, \
storage.ATTRIBUTE_NAME[storage_index], \
storage.push_constants, \
r_bound_pipelines); \
break; \
}
#define BUILD_COMMANDS(NODE_TYPE, NODE_CLASS, ATTRIBUTE_NAME) \
case NODE_TYPE: { \
NODE_CLASS node_info; \
node_info.build_commands( \
command_buffer, ATTRIBUTE_NAME, storage.push_constants, r_bound_pipelines); \
break; \
}
BUILD_COMMANDS(VKNodeType::BEGIN_QUERY, VKBeginQueryNode, begin_query)
BUILD_COMMANDS_STORAGE(VKNodeType::BEGIN_RENDERING, VKBeginRenderingNode, begin_rendering)
BUILD_COMMANDS_STORAGE(
VKNodeType::CLEAR_ATTACHMENTS, VKClearAttachmentsNode, clear_attachments)
BUILD_COMMANDS(VKNodeType::CLEAR_COLOR_IMAGE, VKClearColorImageNode, clear_color_image)
BUILD_COMMANDS(VKNodeType::CLEAR_DEPTH_STENCIL_IMAGE,
VKClearDepthStencilImageNode,
clear_depth_stencil_image)
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_STORAGE(
VKNodeType::COPY_BUFFER_TO_IMAGE, VKCopyBufferToImageNode, copy_buffer_to_image)
BUILD_COMMANDS_STORAGE(VKNodeType::COPY_IMAGE, VKCopyImageNode, copy_image)
BUILD_COMMANDS_STORAGE(
VKNodeType::COPY_IMAGE_TO_BUFFER, VKCopyImageToBufferNode, copy_image_to_buffer)
BUILD_COMMANDS_STORAGE(VKNodeType::BLIT_IMAGE, VKBlitImageNode, blit_image)
BUILD_COMMANDS(VKNodeType::RESET_QUERY_POOL, VKResetQueryPoolNode, reset_query_pool)
BUILD_COMMANDS(VKNodeType::SYNCHRONIZATION, VKSynchronizationNode, synchronization)
BUILD_COMMANDS(VKNodeType::UPDATE_MIPMAPS, VKUpdateMipmapsNode, update_mipmaps)
BUILD_COMMANDS(VKNodeType::DISPATCH, VKDispatchNode, dispatch)
BUILD_COMMANDS(VKNodeType::DISPATCH_INDIRECT, VKDispatchIndirectNode, dispatch_indirect)
BUILD_COMMANDS_STORAGE(VKNodeType::DRAW, VKDrawNode, draw)
BUILD_COMMANDS_STORAGE(VKNodeType::DRAW_INDEXED, VKDrawIndexedNode, draw_indexed)
BUILD_COMMANDS_STORAGE(
VKNodeType::DRAW_INDEXED_INDIRECT, VKDrawIndexedIndirectNode, draw_indexed_indirect)
BUILD_COMMANDS_STORAGE(VKNodeType::DRAW_INDIRECT, VKDrawIndirectNode, draw_indirect)
#undef BUILD_COMMANDS
#undef BUILD_COMMANDS_STORAGE
}
}
/**
* Free data kept by the node
*/
void free_data()
{
switch (type) {
#define FREE_DATA(NODE_TYPE, NODE_CLASS, ATTRIBUTE_NAME) \
case NODE_TYPE: { \
NODE_CLASS node_info; \
node_info.free_data(ATTRIBUTE_NAME); \
break; \
}
FREE_DATA(VKNodeType::UPDATE_BUFFER, VKUpdateBufferNode, update_buffer)
#undef FREE_DATA
case VKNodeType::UNUSED:
case VKNodeType::BEGIN_QUERY:
case VKNodeType::BEGIN_RENDERING:
case VKNodeType::CLEAR_ATTACHMENTS:
case VKNodeType::CLEAR_COLOR_IMAGE:
case VKNodeType::CLEAR_DEPTH_STENCIL_IMAGE:
case VKNodeType::END_QUERY:
case VKNodeType::END_RENDERING:
case VKNodeType::FILL_BUFFER:
case VKNodeType::COPY_BUFFER:
case VKNodeType::COPY_IMAGE:
case VKNodeType::COPY_IMAGE_TO_BUFFER:
case VKNodeType::COPY_BUFFER_TO_IMAGE:
case VKNodeType::BLIT_IMAGE:
case VKNodeType::RESET_QUERY_POOL:
case VKNodeType::SYNCHRONIZATION:
case VKNodeType::UPDATE_MIPMAPS:
case VKNodeType::DISPATCH:
case VKNodeType::DISPATCH_INDIRECT:
case VKNodeType::DRAW:
case VKNodeType::DRAW_INDEXED:
case VKNodeType::DRAW_INDEXED_INDIRECT:
case VKNodeType::DRAW_INDIRECT:
break;
}
}
/**
* Reset nodes.
*
* Nodes are reset so they can be reused in consecutive calls. Data allocated by the node are
* freed. This function dispatches the free_data to the actual node implementation.
*/
void reset()
{
free_data();
type = VKNodeType::UNUSED;
storage_index = -1;
}
};
BLI_STATIC_ASSERT(sizeof(VKRenderGraphNode) <= 96,
"VKRenderGraphNode should be kept small. Consider moving data to the "
"VKRenderGraphStorage class.");
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_resource_access_info.hh"
#include "vk_backend.hh"
#include "vk_render_graph_links.hh"
#include "vk_resource_state_tracker.hh"
namespace blender::gpu::render_graph {
VkImageLayout VKImageAccess::to_vk_image_layout(bool supports_local_read) const
{
if (vk_access_flags & (VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT)) {
/* TODO: when read only use VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL */
return VK_IMAGE_LAYOUT_GENERAL;
}
if (supports_local_read && vk_access_flags & (VK_ACCESS_INPUT_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT))
{
return VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR;
}
if (vk_access_flags &
(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT))
{
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
// TODO: Add ATTACHMENT_READ_ONLY_OPTIMAL
if (vk_access_flags &
(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT))
{
if (vk_image_aspect == VK_IMAGE_ASPECT_DEPTH_BIT) {
return VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
}
if (vk_image_aspect == VK_IMAGE_ASPECT_STENCIL_BIT) {
return VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
}
BLI_assert(vk_image_aspect == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT));
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
}
BLI_assert_unreachable();
return VK_IMAGE_LAYOUT_UNDEFINED;
}
void VKResourceAccessInfo::build_links(VKResourceStateTracker &resources,
VKRenderGraphLinks &links) const
{
for (const VKBufferAccess &buffer_access : buffers) {
const bool writes_to_resource = bool(buffer_access.vk_access_flags & VK_ACCESS_WRITE_MASK);
ResourceWithStamp versioned_resource = writes_to_resource ?
resources.get_buffer_and_increase_stamp(
buffer_access.vk_buffer) :
resources.get_buffer(buffer_access.vk_buffer);
links.buffers.append({versioned_resource, buffer_access.vk_access_flags});
}
const bool supports_local_read = resources.use_dynamic_rendering_local_read;
for (const VKImageAccess &image_access : images) {
VkImageLayout image_layout = image_access.to_vk_image_layout(supports_local_read);
const bool writes_to_resource = bool(image_access.vk_access_flags & VK_ACCESS_WRITE_MASK);
ResourceWithStamp versioned_resource = writes_to_resource ?
resources.get_image_and_increase_stamp(
image_access.vk_image) :
resources.get_image(image_access.vk_image);
links.images.append({{versioned_resource, image_access.vk_access_flags},
image_layout,
image_access.vk_image_aspect,
image_access.subimage});
}
}
void VKResourceAccessInfo::reset()
{
images.clear();
buffers.clear();
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Draw and dispatch commands are shader based and resources needs to be bound. The bound resources
* are stored inside the state manager. Structures and functions inside this file improve code
* re-usage when resources are part of the state manager.
*
* VKResourceAccessInfo: is a structure that can store the access information of a draw/dispatch
* node. This information should be added to the create info of the render graph node
* (`VKNodeInfo::CreateInfo`). When the links of the node is build,
* `VKResourceAccessInfo.build_links` can be called to build the render graph links for these
* resources.
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "vk_common.hh"
#include "vk_render_graph_links.hh"
namespace blender::gpu::render_graph {
class VKResourceStateTracker;
/** Struct describing the access to an image. */
struct VKImageAccess {
VkImage vk_image;
VkAccessFlags vk_access_flags;
VkImageAspectFlags vk_image_aspect;
/* Used for sub-resource tracking within a rendering scope.
*
* By default all layers of images are tracked as a single resource. Only inside a render scope
* we can temporary change a subset of layers, when the image is used as an attachment and a
* image load/store.
*/
VKSubImageRange subimage;
/** Determine the image layout for the vk_access_flags. */
VkImageLayout to_vk_image_layout(bool supports_local_read) const;
};
/** Struct describing the access to a buffer. */
struct VKBufferAccess {
VkBuffer vk_buffer;
VkAccessFlags vk_access_flags;
};
/** Struct describing all resource accesses a draw/dispatch node has. */
struct VKResourceAccessInfo : NonCopyable {
Vector<VKBufferAccess> buffers;
Vector<VKImageAccess> images;
/**
* Extract read/write resource dependencies and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources, VKRenderGraphLinks &links) const;
/**
* Reset the instance for reuse.
*/
void reset();
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,266 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <algorithm>
#include "BKE_global.hh"
#include "BLI_index_range.hh"
#include "vk_resource_state_tracker.hh"
namespace blender::gpu::render_graph {
/* -------------------------------------------------------------------- */
/** \name Adding resources
* \{ */
ResourceHandle VKResourceStateTracker::create_resource_slot()
{
ResourceHandle handle;
static Resource new_resource = {};
if (unused_handles_.is_empty()) {
handle = resources_.append_and_get_index(new_resource);
}
else {
handle = unused_handles_.pop_last();
resources_[handle] = new_resource;
}
return handle;
}
void VKResourceStateTracker::add_image(VkImage vk_image,
bool use_subresource_tracking,
VKResourceBarrierState barrier_state,
const char *name)
{
UNUSED_VARS_NDEBUG(name);
std::scoped_lock lock(mutex);
BLI_assert_msg(!image_resources_.contains(vk_image),
"Image resource is added twice to the render graph.");
ResourceHandle handle = create_resource_slot();
Resource &resource = resources_[handle];
image_resources_.add_new(vk_image, handle);
resource = {
.type = VKResourceType::IMAGE,
.image = {.vk_image = vk_image, .use_subresource_tracking = use_subresource_tracking},
.stamp = 0,
.barrier_state = barrier_state,
};
#ifndef NDEBUG
if (name) {
resource.name = name;
}
#endif
#ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION
validate();
#endif
}
void VKResourceStateTracker::add_image(VkImage vk_image,
bool use_subresource_tracking,
const char *name)
{
add_image(vk_image, use_subresource_tracking, {}, name);
}
void VKResourceStateTracker::add_aliased_image(VkImage vk_image,
bool use_subresource_tracking,
const char *name)
{
add_image(vk_image,
use_subresource_tracking,
{VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_IMAGE_LAYOUT_UNDEFINED},
name);
}
void VKResourceStateTracker::add_swapchain_image(VkImage vk_image, const char *name)
{
add_image(vk_image,
false,
{
VK_ACCESS_NONE,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
},
name);
}
void VKResourceStateTracker::add_buffer(VkBuffer vk_buffer, const char *name)
{
UNUSED_VARS_NDEBUG(name);
std::scoped_lock lock(mutex);
BLI_assert_msg(!buffer_resources_.contains(vk_buffer),
"Buffer resource is added twice to the render graph.");
ResourceHandle handle = create_resource_slot();
Resource &resource = resources_[handle];
buffer_resources_.add_new(vk_buffer, handle);
resource = {
.type = VKResourceType::BUFFER,
.buffer = {.vk_buffer = vk_buffer},
.stamp = 0,
.barrier_state = {},
};
#ifndef NDEBUG
if (name) {
resource.name = name;
}
#endif
#ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION
validate();
#endif
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Image layout
* \{ */
void VKResourceStateTracker::update_image_layout(VkImage vk_image, VkImageLayout vk_image_layout)
{
std::scoped_lock lock(mutex);
ResourceHandle handle = image_resources_.lookup(vk_image);
Resource &resource = get_image_resource(handle);
resource.barrier_state.image_layout = vk_image_layout;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Remove resources
* \{ */
void VKResourceStateTracker::remove_buffer(VkBuffer vk_buffer)
{
std::scoped_lock lock(mutex);
ResourceHandle handle = buffer_resources_.pop(vk_buffer);
resources_[handle] = {
.type = VKResourceType::NONE,
.buffer = {.vk_buffer = VK_NULL_HANDLE},
.stamp = 0,
};
unused_handles_.append(handle);
#ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION
validate();
#endif
}
void VKResourceStateTracker::remove_image(VkImage vk_image)
{
std::scoped_lock lock(mutex);
ResourceHandle handle = image_resources_.pop(vk_image);
resources_[handle] = {
.type = VKResourceType::NONE,
.image = {.vk_image = VK_NULL_HANDLE, .use_subresource_tracking = false},
.stamp = 0,
};
unused_handles_.append(handle);
#ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION
validate();
#endif
}
/** \} */
ResourceWithStamp VKResourceStateTracker::get_stamp(ResourceHandle handle,
const Resource &resource)
{
ResourceWithStamp result;
result.handle = handle;
result.stamp = resource.stamp;
return result;
}
ResourceWithStamp VKResourceStateTracker::get_and_increase_stamp(ResourceHandle handle,
Resource &resource)
{
ResourceWithStamp result = get_stamp(handle, resource);
resource.stamp += 1;
return result;
}
ResourceWithStamp VKResourceStateTracker::get_image_and_increase_stamp(VkImage vk_image)
{
ResourceHandle handle = image_resources_.lookup(vk_image);
Resource &resource = get_image_resource(handle);
return get_and_increase_stamp(handle, resource);
}
ResourceWithStamp VKResourceStateTracker::get_buffer_and_increase_stamp(VkBuffer vk_buffer)
{
ResourceHandle handle = buffer_resources_.lookup(vk_buffer);
Resource &resource = get_buffer_resource(handle);
return get_and_increase_stamp(handle, resource);
}
ResourceWithStamp VKResourceStateTracker::get_buffer(VkBuffer vk_buffer) const
{
ResourceHandle handle = buffer_resources_.lookup(vk_buffer);
const Resource &resource = get_buffer_resource(handle);
return get_stamp(handle, resource);
}
ResourceWithStamp VKResourceStateTracker::get_image(VkImage vk_image) const
{
ResourceHandle handle = image_resources_.lookup(vk_image);
const Resource &resource = get_image_resource(handle);
return get_stamp(handle, resource);
}
#ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION
void VKResourceStateTracker::validate() const
{
for (const Map<VkImage, ResourceHandle>::Item &item : image_resources_.items()) {
for (ResourceHandle buffer_handle : buffer_resources_.values()) {
BLI_assert(item.value != buffer_handle);
}
const Resource &resource = get_image_resource(item.value);
BLI_assert(resource.type == VKResourceType::IMAGE);
}
for (const Map<VkBuffer, ResourceHandle>::Item &item : buffer_resources_.items()) {
for (ResourceHandle image_handle : image_resources_.values()) {
BLI_assert(item.value != image_handle);
}
const Resource &resource = get_buffer_resource(item.value);
BLI_assert(resource.type == VKResourceType::BUFFER);
}
BLI_assert(
image_resources_.size() ==
std::ranges::count_if(resources_.begin(), resources_.end(), [](const Resource &resource) {
return resource.type == VKResourceType::IMAGE;
}));
BLI_assert(
buffer_resources_.size() ==
std::ranges::count_if(resources_.begin(), resources_.end(), [](const Resource &resource) {
return resource.type == VKResourceType::BUFFER;
}));
}
#endif
void VKResourceStateTracker::debug_print() const
{
std::ostream &os = std::cout;
os << "VKResourceStateTracker\n";
os << " resources=(" << resources_.size() << "/" << resources_.capacity() << ")\n";
os << " buffers=(" << buffer_resources_.size() << "/" << buffer_resources_.capacity() << ")\n";
os << " images=(" << image_resources_.size() << "/" << image_resources_.capacity() << ")\n";
os << " unused=(" << unused_handles_.size() << "/" << unused_handles_.capacity() << ")\n";
}
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,334 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* The state of resources needs to be tracked on device level.
*
* The state that are being tracked include:
* - Modification stamps: Each time a resource is modified, this stamp is increased. Inside the
* render graph nodes track the resources including this stamp.
* - Image layouts: The layout of pixels of an image on the GPU depends on the command being
* executed. A certain `vkCmd*` requires the image to be in a certain layout. Using incorrect
* layouts could lead to rendering artifacts.
* - Resource ownership: Resources that are externally managed (swap-chain or external) uses a
* different workflow as its state can be altered externally and needs to be reset.
* - Read/Write access masks: To generate correct and performing pipeline barriers the src/dst
* access masks needs to be accurate and precise. When creating pipeline barriers the resource
* usage up to that point should be known and the resource usage from that point on.
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_map.hh"
#include "BLI_mutex.hh"
#include "BLI_vector.hh"
#include "vk_common.hh"
/**
* Enable VK_RESOURCE_STATE_TRACKER_VALIDATION to perform a consistency check
* on the state. The consistency check is time consuming and should only be
* turned on when needed.
*/
// #define VK_RESOURCE_STATE_TRACKER_VALIDATION
namespace blender::gpu::render_graph {
class VKCommandBuilder;
struct VKRenderGraphLink;
class VKScheduler;
using ResourceHandle = uint64_t;
/**
* ModificationStamp is used to track resource modifications.
*
* When a resource is modified it will generate a new stamp by incrementing the previous stamp
* with 1. Consecutive reads should use this new stamp. The stamp stays active until the next
* modification to the resources is added to any render graph.
*/
using ModificationStamp = uint64_t;
/**
* Resource with a stamp.
*
* This struct represents an image or buffer (handle) and its modification stamp.
*/
struct ResourceWithStamp {
ResourceHandle handle;
ModificationStamp stamp;
};
/**
* Enum containing the different resource types that are being tracked.
*/
enum class VKResourceType { NONE = (0 << 0), IMAGE = (1 << 0), BUFFER = (1 << 1) };
ENUM_OPERATORS(VKResourceType);
/**
* State being tracked for a resource.
*/
struct VKResourceBarrierState {
/** Last used access flags. Will be reset by the last write. Reads will accumulate flags. */
VkAccessFlags vk_access = VK_ACCESS_NONE;
/* Last known pipeline stage. Will be reset by the last write. Reads will accumulate flags. */
VkPipelineStageFlags vk_pipeline_stages = VK_PIPELINE_STAGE_NONE;
/** Last known image layout of an image resource. */
VkImageLayout image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
bool is_new_stamp() const
{
return bool(vk_access &
(VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT));
}
};
/**
* Class to track resources.
*
* Resources are tracked on device level. Their are two kind of resources, namely buffers and
* images. Each resource can have multiple versions; every time a resource is changed (written to)
* a new version is tracked.
*/
class VKResourceStateTracker {
/* When a command buffer is reset the resources are re-synced.
* During the syncing the command builder attributes are resized to reduce reallocations. */
friend class VKCommandBuilder;
friend struct VKRenderGraphLink;
friend class VKScheduler;
/**
* A render resource can be a buffer or an image that needs to be tracked during rendering.
*
* Resources needs to be tracked as usage can alter the content of the resource. For example an
* image can be optimized for data transfer, or optimized for sampling which can use a different
* pixel layout on the device.
*/
struct Resource {
/** Is this resource a buffer or an image. */
VKResourceType type;
union {
struct {
/** VkBuffer handle of the resource being tracked. */
VkBuffer vk_buffer = VK_NULL_HANDLE;
} buffer;
struct {
/** VkImage handle of the resource being tracked. */
VkImage vk_image = VK_NULL_HANDLE;
/** Do we need to track subresources (layers/mipmaps). */
bool use_subresource_tracking = false;
} image;
};
/** Current modification stamp of the resource. */
ModificationStamp stamp = 0;
/**
* State tracking to ensure correct pipeline barriers and command creation.
*/
VKResourceBarrierState barrier_state = {};
#ifndef NDEBUG
std::string name;
#endif
/**
* Check if the given resource handle subresources needs to be tracked.
*
* Returns true when
* - handle is an image with subresource tracking enables.
*
* Returns false when
* - handle isn't an image resource or
* - handle doesn't have subresource tracking enabled.
*/
bool use_subresource_tracking()
{
if (type == VKResourceType::BUFFER) {
return false;
}
return image.use_subresource_tracking;
}
};
Vector<Resource> resources_;
Vector<ResourceHandle> unused_handles_;
Map<VkImage, ResourceHandle> image_resources_;
Map<VkBuffer, ResourceHandle> buffer_resources_;
public:
/**
* Device resource mutex
*
* The mutex is stored in resources due to:
* - It protects resources and their state.
* - Allowing test cases to do testing without setting up a device instance which requires ghost.
* - Device instance isn't accessible in test cases.
*/
Mutex mutex;
/**
* Register a buffer resource.
*
* When a buffer is created in VKBuffer, it needs to be registered in the device resources so the
* resource state can be tracked during its lifetime.
*/
void add_buffer(VkBuffer vk_buffer, const char *name = nullptr);
/**
* Register an image resource.
*
* When an image is created in VKTexture, it needs to be registered in the device resources so
* the resource state can be tracked during its lifetime.
*/
void add_image(VkImage vk_image, bool use_subresource_tracking, const char *name = nullptr);
/**
* \brief Register an image resource that can have aliased memory.
*
* The aliased memory can still be in use and requires the image to wait for all commands to be
* completed, before it can start writing to the aliased memory.
*/
void add_aliased_image(VkImage vk_image,
bool use_subresource_tracking,
const char *name = nullptr);
void add_swapchain_image(VkImage vk_image, const char *name = nullptr);
/**
* \brief Update the layout of an image that has been externally modified.
*
* 'vkTransitionImageLayout' changes the image layout. When used the image layout needs to be
* updated to match the current layout, ensuring correct generation of pipeline barriers.
*
* \name vk_image: VkImage handle to update the image layout for.
* \name vk_image_layout: The layout the resource state tracker should now be using, matching the
* current layout of the image.
*/
void update_image_layout(VkImage vk_image, VkImageLayout vk_image_layout);
/**
* Remove an registered image.
*
* When a image is destroyed by calling `vmaDestroyImage`, a call to `remove_image` is needed to
* unregister the resource from state tracking.
*/
void remove_image(VkImage vk_image);
/**
* Remove an registered buffer.
*
* When a buffer is destroyed by calling `vmaDestroyBuffer`, a call to `remove_buffer` is needed
* to unregister the resource from state tracking.
*/
void remove_buffer(VkBuffer vk_buffer);
/**
* Return the current stamp of the resource, and increase the stamp.
*
* When a node writes to an image, this method is called to increase the stamp of the image.
* The node that writes to the image will use the current stamp as its input, but generate a new
* stamp for future nodes.
*
* This function is called when adding a node to the render graph, during building resource
* dependencies. See `VKNodeInfo.build_links`
*/
ResourceWithStamp get_image_and_increase_stamp(VkImage vk_image);
/**
* Return the current stamp of the resource, and increase the stamp.
*
* When a node writes to a buffer, this method is called to increase the stamp of the buffer.
* The node that writes to the buffer will use the current stamp as its input, but generate the
* new stamp for future nodes.
*
* This function is called when adding a node to the render graph, during building resource
* dependencies. See `VKNodeInfo.build_links`
*/
ResourceWithStamp get_buffer_and_increase_stamp(VkBuffer vk_buffer);
/**
* Return the current stamp of the resource.
*
* When a node reads from a buffer, this method is called to get the current stamp the buffer.
*
* This function is called when adding a node to the render graph, during building resource
* dependencies. See `VKNodeInfo.build_links`
*/
ResourceWithStamp get_buffer(VkBuffer vk_buffer) const;
/**
* Return the current stamp of the resource.
*
* When a node reads from an image, this method is called to get the current stamp the image.
*
* This function is called when adding a node to the render graph, during building resource
* dependencies. See `VKNodeInfo.build_links`
*/
ResourceWithStamp get_image(VkImage vk_image) const;
bool use_dynamic_rendering_local_read = true;
void debug_print() const;
private:
void add_image(VkImage vk_image,
bool use_subresource_tracking,
VKResourceBarrierState barrier_state,
const char *name = nullptr);
/**
* Get the current stamp of the resource.
*/
static ResourceWithStamp get_stamp(ResourceHandle handle, const Resource &resource);
/**
* Get the current stamp of the resource and increase the stamp.
*/
static ResourceWithStamp get_and_increase_stamp(ResourceHandle handle, Resource &resource);
ResourceHandle create_resource_slot();
/**
* Get the ref to an image resource state.
*
* NOTE: Can only be called when the mutex has been locked in the same thread.
*/
inline Resource &get_image_resource(ResourceHandle resource_handle)
{
BLI_assert(resources_[resource_handle].type == VKResourceType::IMAGE);
return resources_[resource_handle];
}
inline const Resource &get_image_resource(ResourceHandle resource_handle) const
{
BLI_assert(resources_[resource_handle].type == VKResourceType::IMAGE);
return resources_[resource_handle];
}
/**
* Get the ref to an image resource state.
*
* NOTE: Can only be called when the mutex has been locked in the same thread.
*/
inline Resource &get_buffer_resource(ResourceHandle resource_handle)
{
BLI_assert(resources_[resource_handle].type == VKResourceType::BUFFER);
return resources_[resource_handle];
}
inline const Resource &get_buffer_resource(ResourceHandle resource_handle) const
{
BLI_assert(resources_[resource_handle].type == VKResourceType::BUFFER);
return resources_[resource_handle];
}
#ifdef VK_RESOURCE_STATE_TRACKER_VALIDATION
void validate() const;
#endif
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,243 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <sstream>
#include "vk_render_graph.hh"
#include "vk_scheduler.hh"
#include "BLI_index_range.hh"
#include "BLI_set.hh"
namespace blender::gpu::render_graph {
Span<NodeHandle> VKScheduler::select_nodes(const VKRenderGraph &render_graph)
{
result_.clear();
for (NodeHandle node_handle : render_graph.nodes_.index_range()) {
result_.append(node_handle);
}
reorder_nodes(render_graph);
return result_;
}
/* -------------------------------------------------------------------- */
/** \name Reorder - move data transfer and dispatches outside rendering scope
* \{ */
void VKScheduler::reorder_nodes(const VKRenderGraph &render_graph)
{
move_initial_transfer_to_start(render_graph);
move_transfer_and_dispatch_outside_rendering_scope(render_graph);
}
std::optional<std::pair<int64_t, int64_t>> VKScheduler::find_rendering_scope(
const VKRenderGraph &render_graph, IndexRange search_range) const
{
int64_t rendering_start = -1;
for (int64_t index : search_range) {
NodeHandle node_handle = result_[index];
const VKRenderGraphNode &node = render_graph.nodes_[node_handle];
if (node.type == VKNodeType::BEGIN_RENDERING) {
rendering_start = index;
}
if (node.type == VKNodeType::END_RENDERING && rendering_start != -1) {
return std::pair(rendering_start, index);
}
}
/* When using VK_EXT_dynamic_rendering_local_read, rendering scopes can be suspended and
* resumed without explicit END_RENDERING (see VKFrameBuffer::subpass_transition_impl).
* This can leave a BEGIN_RENDERING node without a matching END_RENDERING at the end of
* the search range. Orphaned nodes don't need reordering.
* When NOT using local read, an unmatched BEGIN_RENDERING is a bug. */
BLI_assert(rendering_start == -1 || render_graph.resources_.use_dynamic_rendering_local_read);
return std::nullopt;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reorder - Move initial data transfers to the start
* \{ */
void VKScheduler::move_initial_transfer_to_start(const VKRenderGraph &render_graph)
{
Vector<NodeHandle> data_transfers;
Vector<NodeHandle> other_nodes;
data_transfers.reserve(result_.size());
other_nodes.reserve(result_.size());
for (const int64_t index : result_.index_range()) {
NodeHandle node_handle = result_[index];
const VKRenderGraphNode &node = render_graph.nodes_[node_handle];
Span<VKRenderGraphBuffer> node_buffers = render_graph.linked_buffers(node);
Span<VKRenderGraphImage> node_images = render_graph.linked_images(node);
if (ELEM(node.type, VKNodeType::COPY_BUFFER)) {
if (node_buffers[0].resource.stamp == 0 && node_buffers[1].resource.stamp == 0) {
data_transfers.append(index);
continue;
}
}
else if (ELEM(node.type, VKNodeType::COPY_BUFFER_TO_IMAGE, VKNodeType::COPY_IMAGE_TO_BUFFER)) {
if (node_buffers[0].resource.stamp == 0 && node_images[0].resource.stamp == 0) {
data_transfers.append(index);
continue;
}
}
else if (ELEM(node.type, VKNodeType::FILL_BUFFER, VKNodeType::UPDATE_BUFFER)) {
if (node_buffers[0].resource.stamp == 0) {
data_transfers.append(index);
continue;
}
}
other_nodes.append(index);
}
MutableSpan<NodeHandle> store_data_transfers = result_.as_mutable_span().slice(
0, data_transfers.size());
MutableSpan<NodeHandle> store_other = result_.as_mutable_span().slice(data_transfers.size(),
other_nodes.size());
store_data_transfers.copy_from(data_transfers);
store_other.copy_from(other_nodes);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reorder - move data transfer and dispatches outside rendering scope
* \{ */
void VKScheduler::move_transfer_and_dispatch_outside_rendering_scope(
const VKRenderGraph &render_graph)
{
Vector<NodeHandle> pre_rendering_scope;
Vector<NodeHandle> rendering_scope;
Set<ResourceHandle> used_buffers;
foreach_rendering_scope(render_graph, [&](int64_t start_index, int64_t end_index) {
/* Move end_rendering right after the last graphics node. */
for (int index = end_index - 1; index >= start_index; index--) {
NodeHandle node_handle = result_[index];
const VKRenderGraphNode &node = render_graph.nodes_[node_handle];
if (node_type_is_rendering(node.type)) {
break;
}
std::swap(result_[end_index], result_[index]);
end_index -= 1;
}
/* Move begin_rendering right before the first graphics node. */
for (int index = start_index + 1; index < end_index; index++) {
NodeHandle node_handle = result_[index];
const VKRenderGraphNode &node = render_graph.nodes_[node_handle];
if (node_type_is_rendering(node.type)) {
break;
}
std::swap(result_[start_index], result_[index]);
start_index += 1;
}
/* Move buffer update buffer commands to before the rendering scope, unless the buffer is
* already being used by a draw command. Images modification could also be moved outside the
* rendering scope, but it is more tricky as they could also be attached to the frame-buffer.
*/
pre_rendering_scope.clear();
rendering_scope.clear();
used_buffers.clear();
for (int index = start_index + 1; index < end_index; index++) {
NodeHandle node_handle = result_[index];
const VKRenderGraphNode &node = render_graph.nodes_[node_handle];
/* Should we add this node to the rendering scope. This is only done when we need to reorder
* nodes. In that case the rendering_scope has already an item and we should add this node to
* or the rendering scope or before the rendering scope. Adding nodes before rendering scope
* is done in the VKNodeType::UPDATE_BUFFER branch. */
bool add_to_rendering_scope = !rendering_scope.is_empty();
if (node.type == VKNodeType::UPDATE_BUFFER) {
/* Checking the node links to reduce potential locking the resource mutex. */
if (!used_buffers.contains(
render_graph.links_.buffers[node.links.buffers.start()].resource.handle))
{
/* Buffer isn't used by this rendering scope so we can safely move it before the
* rendering scope begins. */
pre_rendering_scope.append(node_handle);
add_to_rendering_scope = false;
/* When this is the first time we move a node before the rendering we should start
* building up the rendering scope as well. This is postponed so we can safe some cycles
* when no nodes needs to be moved at all. */
if (rendering_scope.is_empty()) {
rendering_scope.extend(Span<NodeHandle>(&result_[start_index], index - start_index));
}
}
}
if (add_to_rendering_scope) {
/* When rendering scope has an item we are rewriting the execution order and need to track
* what should be inside the rendering scope. */
rendering_scope.append(node_handle);
}
/* Any read/write to buffer resources should be added to used_buffers in order to detect if
* it is safe to move a node before the rendering scope. */
for (const VKRenderGraphBuffer &link : render_graph.linked_buffers(node)) {
used_buffers.add(link.resource.handle);
}
}
/* When pre_rendering_scope has an item we want to rewrite the order.
* The number of nodes are not changed, so we can do this inline. */
if (!pre_rendering_scope.is_empty()) {
MutableSpan<NodeHandle> store_none_rendering = result_.as_mutable_span().slice(
start_index, pre_rendering_scope.size());
MutableSpan<NodeHandle> store_rendering = result_.as_mutable_span().slice(
start_index + pre_rendering_scope.size(), rendering_scope.size());
store_none_rendering.copy_from(pre_rendering_scope);
store_rendering.copy_from(rendering_scope);
start_index += pre_rendering_scope.size();
}
});
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug
* \{ */
void VKScheduler::debug_print(const VKRenderGraph &render_graph) const
{
std::stringstream ss;
int indent = 0;
for (int index : result_.index_range()) {
const NodeHandle node_handle = result_[index];
const VKRenderGraphNode &node = render_graph.nodes_[node_handle];
if (node.type == VKNodeType::END_RENDERING) {
indent--;
}
for (int i = 0; i < indent; i++) {
ss << " ";
}
ss << node.type << "\n";
#if 0
render_graph.debug_print(node_handle);
#endif
if (node.type == VKNodeType::BEGIN_RENDERING) {
indent++;
}
}
ss << "\n";
std::cout << ss.str();
}
/** \} */
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,117 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* The scheduler is responsible to find and reorder the nodes in the render graph to update an
* image or buffer to its latest content and state.
*/
#pragma once
#include "BLI_vector.hh"
#include "vk_common.hh"
#include "vk_render_graph_node.hh"
namespace blender::gpu::render_graph {
class VKRenderGraph;
/**
* VKScheduler is responsible for selecting and reordering of nodes in the render graph. This
* selection and order is used to convert the nodes to commands and submitting it to the GPU.
*
* This scheduler selects all nodes in the order they were added to the render graph.
*
* This is an initial implementation and should be enhanced for:
* - Moving data transfer and compute before drawing, when they are scheduled between drawing nodes
* that use the same pipeline.
* - Only select the nodes that are only needed for the given vk_image/vk_buffer. When performing
* read-backs of buffers should be done with as least as possible nodes as they can block
* drawing. It is better to do handle most nodes just before presenting the image. This would
* lead to less CPU locks.
* - Pruning branches that are not linked to anything. EEVEE can add debug commands that would
* eventually not been displayed on screen. These branches should be pruned. The challenge is
* that we need to know for certain that it isn't used in a not submitted part of the graph.
*
* TODO: Walking the render graph isn't implemented yet.
* The idea is to have a `Map<ResourceWithStamp, Vector<NodeHandle>> consumers` and
* `Map<ResourceWithStamp, NodeHandle> producers`.
* These attributes can be stored in the render graph and created when building the
* links, or can be created inside the VKScheduler as a variable. The exact detail which one would
* be better is unclear as there aren't any users yet. At the moment the scheduler would need them
* we need to figure out the best way to store and retrieve the consumers/producers.
*/
class VKScheduler {
private:
/**
* Results of `select_nodes_for_image`, `select_nodes_for_buffer` are cached in this instance to
* reduce memory operations.
*/
Vector<NodeHandle> result_;
public:
/**
* Determine which nodes of the render graph should be selected and in what order they should
* be executed to update the given vk_image to its latest content and state.
*
* NOTE: Currently will select all nodes.
* NOTE: Result becomes invalid by the next call to VKScheduler.
*/
[[nodiscard]] Span<NodeHandle> select_nodes(const VKRenderGraph &render_graph);
private:
void reorder_nodes(const VKRenderGraph &render_graph);
/**
* When a data transfer command writes to a resource which is initial it can be grouped at the
* beginning of the render graph.
*
* This reduces context switches when executing commands on the GPU.
*/
void move_initial_transfer_to_start(const VKRenderGraph &render_graph);
/**
* Any data transfer or dispatch nodes should be scheduled before or after a rendering scope.
*
* - Data transfer and dispatch nodes at the beginning are scheduled before
* the rendering begin.
* - Data transfer and dispatch nodes at the end are scheduled after the
* rendering end.
* - Data transfer and dispatch nodes in between draw commands will be pushed
* to the beginning if they are not yet being used.
* - When used the rendering will be suspended and the data transfer/dispatch nodes are
* scheduled between the suspended rendering and when the suspended rendering is
* continued.
*
* NOTE: Clearing attachments is considered a rendering command as specified by the vulkan
* specification.
*/
void move_transfer_and_dispatch_outside_rendering_scope(const VKRenderGraph &render_graph);
/**
* Find the first rendering scope inside the given search range of the result_.
*/
std::optional<std::pair<int64_t, int64_t>> find_rendering_scope(
const VKRenderGraph &render_graph, IndexRange search_range) const;
template<typename FuncT>
void foreach_rendering_scope(const VKRenderGraph &render_graph, const FuncT &func) const
{
for (std::optional<std::pair<int64_t, int64_t>> rendering_scope =
find_rendering_scope(render_graph, IndexRange(result_.index_range()));
rendering_scope.has_value();
rendering_scope =
find_rendering_scope(render_graph,
IndexRange(rendering_scope.value().second + 1,
result_.size() - rendering_scope.value().second - 1)))
{
func(rendering_scope.value().first, rendering_scope.value().second);
}
}
void debug_print(const VKRenderGraph &render_graph) const;
};
} // namespace blender::gpu::render_graph

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "vk_backbuffer_blit_infos.hh"
COMPUTE_SHADER_CREATE_INFO(vk_backbuffer_blit)
float srgb_to_linearrgb(float c)
{
if (c < 0.04045f) {
return (c < 0.0f) ? 0.0f : c * (1.0f / 12.92f);
}
return pow((c + 0.055f) * (1.0f / 1.055f), 2.4f);
}
float3 nonlinear_to_linear_scrgb(float3 c)
{
#ifdef USE_GAMMA22
return pow(c, float3(2.2f));
#else
return float3(srgb_to_linearrgb(c.r), srgb_to_linearrgb(c.g), srgb_to_linearrgb(c.b));
#endif
}
void main()
{
int2 dst_texel = int2(gl_GlobalInvocationID.xy);
int2 src_size = int2(imageSize(src_img));
int2 src_texel = int2(dst_texel.x, src_size.y - dst_texel.y - 1);
float4 color = imageLoad(src_img, int2(src_texel));
/*
* Convert from extended sRGB non-linear to linear.
*
* Preserves negative wide gamut values with sign/abs. May use either gamma 2.2
* decode to match most SDR sRGB displays, or the piecewise sRGB function to
* match Windows SDR applications in HDR node.
*/
color.rgb = sign(color.rgb) * nonlinear_to_linear_scrgb(abs(color.rgb)) * sdr_scale;
imageStore(dst_img, int2(dst_texel), color);
}

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef GPU_SHADER
# pragma once
#endif
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(vk_backbuffer_blit)
LOCAL_GROUP_SIZE(16, 16)
IMAGE(0, SFLOAT_16_16_16_16, read, Float2D, src_img)
IMAGE(1, SFLOAT_16_16_16_16, write, Float2D, dst_img)
PUSH_CONSTANT(float, sdr_scale)
PUSH_CONSTANT(bool, use_gamma22)
COMPUTE_SOURCE("vk_backbuffer_blit_comp.glsl")
ADDITIONAL_INFO(gpu_srgb_to_framebuffer_space)
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(vk_backbuffer_blit_gamma22)
ADDITIONAL_INFO(vk_backbuffer_blit)
DEFINE("USE_GAMMA22")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -0,0 +1,147 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "vk_data_conversion.hh"
#include "vk_device.hh"
#include "gpu_vertex_format_private.hh"
namespace blender::gpu::tests {
TEST(VulkanDataConversion, clamp_negative_to_zero)
{
const uint32_t f32_2 = 0b11000000000000000000000000000000;
const uint32_t f32_inf_min = 0b11111111100000000000000000000000;
const uint32_t f32_inf_max = 0b01111111100000000000000000000000;
const uint32_t f32_nan = 0b11111111111111111111111111111111;
/* F32(-2) doesn't fit in F11 as F11 only supports unsigned values. Clamp to zero. */
const uint32_t f11_0_expected = 0b00000000000;
const uint32_t f11_2_expected = 0b10000000000;
const uint32_t f11_inf_expected = 0b11111000000;
const uint32_t f11_nan_expected = 0b11111111111;
{
const uint32_t f11_0 = convert_float_formats<FormatF11, FormatF32, true>(f32_2);
EXPECT_EQ(f11_0, f11_0_expected);
const uint32_t f11_0b = convert_float_formats<FormatF11, FormatF32, true>(f32_inf_min);
EXPECT_EQ(f11_0b, f11_0_expected);
const uint32_t f11_inf = convert_float_formats<FormatF11, FormatF32, true>(f32_inf_max);
EXPECT_EQ(f11_inf, f11_inf_expected);
const uint32_t f11_nan = convert_float_formats<FormatF11, FormatF32, true>(f32_nan);
EXPECT_EQ(f11_nan, f11_nan_expected);
}
/* F32(-2) doesn't fit in F11 as F11 only supports unsigned values. Make absolute. */
{
const uint32_t f11_2 = convert_float_formats<FormatF11, FormatF32, false>(f32_2);
EXPECT_EQ(f11_2, f11_2_expected);
const uint32_t f11_inf = convert_float_formats<FormatF11, FormatF32, false>(f32_inf_min);
EXPECT_EQ(f11_inf, f11_inf_expected);
const uint32_t f11_infb = convert_float_formats<FormatF11, FormatF32, false>(f32_inf_max);
EXPECT_EQ(f11_infb, f11_inf_expected);
const uint32_t f11_nan = convert_float_formats<FormatF11, FormatF32, false>(f32_nan);
EXPECT_EQ(f11_nan, f11_nan_expected);
}
}
TEST(VulkanDataConversion, infinity_upper)
{
const uint32_t f32_inf = 0b01111111100000000000000000000000;
const uint32_t f11_inf_expected = 0b11111000000;
const uint32_t f11_inf = convert_float_formats<FormatF11, FormatF32, true>(f32_inf);
EXPECT_EQ(f11_inf, f11_inf_expected);
const uint32_t f10_inf_expected = 0b1111100000;
const uint32_t f10_inf = convert_float_formats<FormatF10, FormatF32, true>(f32_inf);
EXPECT_EQ(f10_inf, f10_inf_expected);
}
TEST(VulkanDataConversion, texture_rgb16f_as_floats_to_rgba16f)
{
const size_t num_pixels = 4;
const float input[] = {
1.0,
0.5,
0.2,
0.2,
1.0,
0.3,
0.4,
0.2,
1.0,
1.0,
1.0,
1.0,
};
uint64_t device[num_pixels];
convert_host_to_device(device,
input,
num_pixels,
GPU_DATA_FLOAT,
TextureFormat::SFLOAT_16_16_16,
TextureFormat::SFLOAT_16_16_16_16);
float read_back[num_pixels * 3];
convert_device_to_host(read_back,
device,
num_pixels,
GPU_DATA_FLOAT,
TextureFormat::SFLOAT_16_16_16,
TextureFormat::SFLOAT_16_16_16_16);
for (int i : IndexRange(num_pixels * 3)) {
EXPECT_NEAR(input[i], read_back[i], 0.01);
}
}
TEST(VulkanDataConversion, texture_rgb32f_as_floats_to_rgba32f)
{
const size_t num_pixels = 4;
const float input[] = {
1.0,
0.5,
0.2,
0.2,
1.0,
0.3,
0.4,
0.2,
1.0,
1.0,
1.0,
1.0,
};
float device[num_pixels * 4];
convert_host_to_device(device,
input,
num_pixels,
GPU_DATA_FLOAT,
TextureFormat::SFLOAT_32_32_32,
TextureFormat::SFLOAT_32_32_32_32);
float read_back[num_pixels * 3];
convert_device_to_host(read_back,
device,
num_pixels,
GPU_DATA_FLOAT,
TextureFormat::SFLOAT_32_32_32,
TextureFormat::SFLOAT_32_32_32_32);
for (int i : IndexRange(num_pixels * 3)) {
EXPECT_NEAR(input[i], read_back[i], 0.01);
}
}
} // namespace blender::gpu::tests

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "vk_memory_layout.hh"
namespace blender::gpu {
template<typename Layout>
static void def_attr(const shader::Type type,
const int array_size,
const uint32_t expected_alignment,
const uint32_t expected_reserve,
uint32_t *r_offset)
{
align<Layout>(type, array_size, r_offset);
EXPECT_EQ(*r_offset, expected_alignment);
reserve<Layout>(type, array_size, r_offset);
EXPECT_EQ(*r_offset, expected_reserve);
}
TEST(std140, fl)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 0, 0, 4, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 16);
}
TEST(std140, _2fl)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 0, 0, 4, &offset);
def_attr<Std140>(shader::Type::float_t, 0, 4, 8, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 16);
}
TEST(std140, _3fl)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 0, 0, 4, &offset);
def_attr<Std140>(shader::Type::float_t, 0, 4, 8, &offset);
def_attr<Std140>(shader::Type::float_t, 0, 8, 12, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 16);
}
TEST(std140, _4fl)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 0, 0, 4, &offset);
def_attr<Std140>(shader::Type::float_t, 0, 4, 8, &offset);
def_attr<Std140>(shader::Type::float_t, 0, 8, 12, &offset);
def_attr<Std140>(shader::Type::float_t, 0, 12, 16, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 16);
}
TEST(std140, fl2)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 2, 0, 32, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 32);
}
TEST(std140, fl_fl2)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 0, 0, 4, &offset);
def_attr<Std140>(shader::Type::float_t, 2, 16, 48, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 48);
}
TEST(std140, fl_vec2)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float_t, 0, 0, 4, &offset);
def_attr<Std140>(shader::Type::float2_t, 0, 8, 16, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 16);
}
TEST(std140, gpu_shader_2D_widget_base)
{
uint32_t offset = 0;
def_attr<Std140>(shader::Type::float4_t, 12, 0, 192, &offset);
def_attr<Std140>(shader::Type::float4x4_t, 0, 192, 256, &offset);
def_attr<Std140>(shader::Type::float3_t, 0, 256, 268, &offset);
def_attr<Std140>(shader::Type::bool_t, 0, 268, 272, &offset);
align_end_of_struct<Std140>(&offset);
EXPECT_EQ(offset, 272);
}
TEST(std430, overlay_grid)
{
uint32_t offset = 0;
def_attr<Std430>(shader::Type::float3_t, 0, 0, 12, &offset);
def_attr<Std430>(shader::Type::int_t, 0, 12, 16, &offset);
align_end_of_struct<Std430>(&offset);
EXPECT_EQ(offset, 16);
}
TEST(std430, simple_lighting)
{
uint32_t offset = 0;
def_attr<Std430>(shader::Type::float4x4_t, 0, 0, 64, &offset);
def_attr<Std430>(shader::Type::float3x3_t, 0, 64, 112, &offset);
align_end_of_struct<Std430>(&offset);
EXPECT_EQ(offset, 112);
}
TEST(std430, compositor_cryptomatte_matte_compute)
{
uint32_t offset = 0;
def_attr<Std430>(shader::Type::float2_t, 0, 0, 8, &offset);
def_attr<Std430>(shader::Type::float_t, 0, 8, 12, &offset);
def_attr<Std430>(shader::Type::float_t, 32, 12, 140, &offset);
align_end_of_struct<Std430>(&offset);
EXPECT_EQ(offset, 144);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,951 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <algorithm>
#include <cstdio>
#include <sstream>
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_threads.h"
#include "CLG_log.h"
#include "GPU_capabilities.hh"
#include "gpu_capabilities_private.hh"
#include "gpu_platform_private.hh"
#include "vk_batch.hh"
#include "vk_context.hh"
#include "vk_fence.hh"
#include "vk_framebuffer.hh"
#include "vk_ghost_api.hh"
#include "vk_index_buffer.hh"
#include "vk_pixel_buffer.hh"
#include "vk_query.hh"
#include "vk_shader.hh"
#include "vk_state_manager.hh"
#include "vk_storage_buffer.hh"
#include "vk_texture.hh"
#include "vk_texture_pool.hh"
#include "vk_uniform_buffer.hh"
#include "vk_vertex_buffer.hh"
#include "vk_backend.hh"
namespace blender {
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
static const char *vk_extension_get(int index)
{
return VKBackend::get().device.extension_name_get(index);
}
bool GPU_vulkan_is_supported_driver(VkPhysicalDevice vk_physical_device)
{
/* Check for known faulty drivers. */
VkPhysicalDeviceProperties2 vk_physical_device_properties = {};
VkPhysicalDeviceDriverProperties vk_physical_device_driver_properties = {};
vk_physical_device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
vk_physical_device_driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
vk_physical_device_properties.pNext = &vk_physical_device_driver_properties;
vkGetPhysicalDeviceProperties2(vk_physical_device, &vk_physical_device_properties);
#ifdef _WIN32
/* Intel IRIS on 10th gen CPU (and older) crashes with drivers before 101.2140 due to multiple
* driver issues.
*
* 1) Workbench is working, but EEVEE pipelines are failing. Calling vkCreateGraphicsPipelines
* for certain EEVEE shaders (Shadow, Deferred rendering) would return with VK_SUCCESS, but
* without a created VkPipeline handle.
*
* 2) When vkCmdBeginRendering is called some requirements need to be met, that can only be met
* when actually calling a vkCmdDraw* command. According to the Vulkan specs the requirements
* should only be met when calling a vkCmdDraw* command.
*/
if (vk_physical_device_driver_properties.driverID == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS &&
vk_physical_device_properties.properties.deviceType ==
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
{
const uint32_t driver_version = vk_physical_device_properties.properties.driverVersion;
uint32_t driver_version_major = driver_version >> 14u;
uint32_t driver_version_minor = driver_version & 0x3fffu;
if (driver_version_major < 101 || (driver_version_major == 101 && driver_version_minor < 2140))
{
return false;
}
}
#endif
#ifndef _WIN32
/* NVIDIA drivers below 550 don't work on Linux. When sending command to the GPU there is not
* always a reply back when they are finished. The issue is reported on the Internet many times,
* but there is no mention of a solution. This means that on Linux we can only support GTX900 and
* or use MesaNVK.
*/
uint32_t conformance_version = VK_MAKE_API_VERSION(
vk_physical_device_driver_properties.conformanceVersion.major,
vk_physical_device_driver_properties.conformanceVersion.minor,
vk_physical_device_driver_properties.conformanceVersion.subminor,
vk_physical_device_driver_properties.conformanceVersion.patch);
if (vk_physical_device_driver_properties.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY &&
conformance_version < VK_MAKE_API_VERSION(1, 3, 7, 2))
{
return false;
}
/* NVIDIA driver 580.76.05 doesn't start using specific Wayland configurations #144625. There are
* multiple reports also not Blender related and NVIDIA mentions that a new driver will be
* released. It is unclear if that driver will fix our issue. For now disabling this driver on
* Linux. This also disables it for configurations that are working as well (including X11). */
if (vk_physical_device_driver_properties.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY &&
((StringRefNull(vk_physical_device_driver_properties.driverInfo).find("580.76.5", 0) !=
StringRef::not_found) ||
(StringRefNull(vk_physical_device_driver_properties.driverInfo).find("580.76.05", 0) !=
StringRef::not_found)))
{
return false;
}
#endif
#ifdef _WIN32
if (vk_physical_device_driver_properties.driverID == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
/* Any Qualcomm driver older than 31.0.112.0 will not be capable of running blender due
* to an issue in their semaphore timeline implementation. The driver could return
* timelines that have not been provided by Blender. As Blender uses timelines for resource
* management this resulted in resources to be destroyed, that are still in use. */
/* Public version 31.0.112 uses vulkan driver version 512.827.0. */
const uint32_t driver_version = vk_physical_device_properties.properties.driverVersion;
constexpr uint32_t version_31_0_112 = VK_MAKE_VERSION(512, 827, 0);
if (driver_version < version_31_0_112) {
CLOG_WARN(&LOG,
"Detected qualcomm driver is not supported. To run the Vulkan backend "
"driver 31.0.112.0 or later is required. Switching to OpenGL.");
return false;
}
}
#endif
return true;
}
static Vector<StringRefNull> missing_capabilities_get(VkPhysicalDevice vk_physical_device)
{
Vector<StringRefNull> missing_capabilities;
/* Check device features. */
VkPhysicalDeviceVulkan12Features features_12 = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES};
VkPhysicalDeviceVulkan11Features features_11 = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, &features_12};
VkPhysicalDeviceFeatures2 features = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
&features_11};
vkGetPhysicalDeviceFeatures2(vk_physical_device, &features);
#ifndef __APPLE__
/* Features currently not supported by Mesa KosmicKrisp. */
if (features.features.geometryShader == VK_FALSE) {
missing_capabilities.append("geometry shaders");
}
#endif
if (features.features.vertexPipelineStoresAndAtomics == VK_FALSE) {
missing_capabilities.append("vertex pipeline stores and atomics");
}
if (features.features.multiViewport == VK_FALSE) {
missing_capabilities.append("multi viewport");
}
if (features.features.shaderClipDistance == VK_FALSE) {
missing_capabilities.append("shader clip distance");
}
if (features.features.fragmentStoresAndAtomics == VK_FALSE) {
missing_capabilities.append("fragment stores and atomics");
}
if (features.features.logicOp == VK_FALSE) {
missing_capabilities.append("logical operations");
}
if (features.features.dualSrcBlend == VK_FALSE) {
missing_capabilities.append("dual source blending");
}
if (features.features.imageCubeArray == VK_FALSE) {
missing_capabilities.append("image cube array");
}
if (features.features.multiDrawIndirect == VK_FALSE) {
missing_capabilities.append("multi draw indirect");
}
if (features.features.drawIndirectFirstInstance == VK_FALSE) {
missing_capabilities.append("draw indirect first instance");
}
if (features_11.shaderDrawParameters == VK_FALSE) {
missing_capabilities.append("shader draw parameters");
}
if (features_12.timelineSemaphore == VK_FALSE) {
missing_capabilities.append("timeline semaphores");
}
if (features_12.bufferDeviceAddress == VK_FALSE) {
missing_capabilities.append("buffer device address");
}
/* Check device extensions. */
uint32_t vk_extension_count;
vkEnumerateDeviceExtensionProperties(vk_physical_device, nullptr, &vk_extension_count, nullptr);
Array<VkExtensionProperties> vk_extensions(vk_extension_count);
vkEnumerateDeviceExtensionProperties(
vk_physical_device, nullptr, &vk_extension_count, vk_extensions.data());
Set<StringRefNull> extensions;
for (VkExtensionProperties &vk_extension : vk_extensions) {
extensions.add(vk_extension.extensionName);
}
if (!extensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
if (!extensions.contains(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
missing_capabilities.append(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
}
#ifndef __APPLE__
/* Metal doesn't support provoking vertex. */
if (!extensions.contains(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME)) {
missing_capabilities.append(VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
}
#endif
return missing_capabilities;
}
/**
* Disable implicit layers and only allow layers that we trust.
*
* Render doc layer is hidden behind a debug flag. There are malicious layers that impersonate
* RenderDoc and can crash when loaded. See #139543.
*
* Must be called before any `vkCreateInstance` so temporary Vulkan instances created during
* argument handling (e.g. `--gpu-device help`) don't load implicit layers either.
*/
static void vk_restrict_loader_layers()
{
std::stringstream allowed_layers;
allowed_layers << "VK_LAYER_KHRONOS_*";
allowed_layers << ",VK_LAYER_AMD_*";
allowed_layers << ",VK_LAYER_INTEL_*";
allowed_layers << ",VK_LAYER_NV_*";
allowed_layers << ",VK_LAYER_MESA_*";
if (bool(G.debug & G_DEBUG_GPU)) {
allowed_layers << ",VK_LAYER_LUNARG_*";
allowed_layers << ",VK_LAYER_RENDERDOC_*";
}
BLI_setenv("VK_LOADER_LAYERS_DISABLE", "~implicit~");
BLI_setenv("VK_LOADER_LAYERS_ALLOW", allowed_layers.str().c_str());
}
static bool vk_instance_create_for_platform_checks(VkInstance *r_instance)
{
vk_restrict_loader_layers();
/* Initialize an vulkan 1.2 instance. */
VkApplicationInfo vk_application_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
vk_application_info.pApplicationName = "Blender";
vk_application_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
vk_application_info.pEngineName = "Blender";
vk_application_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
vk_application_info.apiVersion = VK_API_VERSION_1_2;
VkInstanceCreateInfo vk_instance_info = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
vk_instance_info.pApplicationInfo = &vk_application_info;
*r_instance = VK_NULL_HANDLE;
vkCreateInstance(&vk_instance_info, nullptr, r_instance);
return *r_instance != VK_NULL_HANDLE;
}
bool VKBackend::is_supported()
{
CLG_logref_init(&LOG);
VkInstance vk_instance = VK_NULL_HANDLE;
if (!vk_instance_create_for_platform_checks(&vk_instance)) {
CLOG_ERROR(&LOG, "Unable to initialize a Vulkan 1.2 instance.");
return false;
}
/* Go over all the devices. */
uint32_t physical_devices_count = 0;
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, nullptr);
Array<VkPhysicalDevice> vk_physical_devices(physical_devices_count);
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, vk_physical_devices.data());
for (VkPhysicalDevice vk_physical_device : vk_physical_devices) {
VkPhysicalDeviceProperties vk_properties = {};
vkGetPhysicalDeviceProperties(vk_physical_device, &vk_properties);
if (!GPU_vulkan_is_supported_driver(vk_physical_device)) {
CLOG_WARN(&LOG,
"Installed driver for device [%s] has known issues and will not be used. Updating "
"driver might improve compatibility.",
vk_properties.deviceName);
continue;
}
Vector<StringRefNull> missing_capabilities = missing_capabilities_get(vk_physical_device);
/* Report result. */
if (missing_capabilities.is_empty()) {
/* This device meets minimum requirements. */
CLOG_DEBUG(
&LOG,
"Device [%s] supports minimum requirements. Skip checking other GPUs. Another GPU "
"can still be selected during auto-detection.",
vk_properties.deviceName);
vkDestroyInstance(vk_instance, nullptr);
return true;
}
std::stringstream ss;
ss << "Device [" << vk_properties.deviceName
<< "] does not meet minimum requirements. Missing features are [";
for (StringRefNull &feature : missing_capabilities) {
ss << feature << ", ";
}
ss.seekp(-2, std::ios_base::end);
ss << "]";
CLOG_WARN(&LOG, "%s", ss.str().c_str());
}
/* No device found meeting the minimum requirements. */
vkDestroyInstance(vk_instance, nullptr);
CLOG_ERROR(&LOG,
"No Vulkan device found that meets the minimum requirements. "
"Updating GPU driver can improve compatibility.");
return false;
}
void VKBackend::supported_devices_print(FILE *fp)
{
CLG_logref_init(&LOG);
VkInstance vk_instance = VK_NULL_HANDLE;
if (!vk_instance_create_for_platform_checks(&vk_instance)) {
fprintf(fp, "Unable to initialize a Vulkan 1.2 instance.\n");
return;
}
uint32_t physical_devices_count = 0;
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, nullptr);
Array<VkPhysicalDevice> vk_physical_devices(physical_devices_count);
vkEnumeratePhysicalDevices(vk_instance, &physical_devices_count, vk_physical_devices.data());
struct Row {
int index;
std::string identifier;
std::string name;
};
Vector<Row> rows;
int index = 0;
for (VkPhysicalDevice vk_physical_device : vk_physical_devices) {
if (missing_capabilities_get(vk_physical_device).is_empty() &&
GPU_vulkan_is_supported_driver(vk_physical_device))
{
VkPhysicalDeviceProperties vk_properties = {};
vkGetPhysicalDeviceProperties(vk_physical_device, &vk_properties);
std::stringstream identifier;
identifier << std::hex << vk_properties.vendorID << "/" << vk_properties.deviceID << "/"
<< index;
rows.append({index, identifier.str(), std::string(vk_properties.deviceName)});
}
index++;
}
if (rows.is_empty()) {
fprintf(fp, " (no supported Vulkan devices found)\n");
vkDestroyInstance(vk_instance, nullptr);
return;
}
const char *col_index = "Index";
const char *col_id = "Device-ID";
const char *col_name = "Name";
size_t w_index = strlen(col_index);
size_t w_id = strlen(col_id);
for (const Row &row : rows) {
char buf[16];
w_index = std::max(w_index, BLI_snprintf_rlen(buf, sizeof(buf), "%d", row.index));
w_id = std::max(w_id, row.identifier.size());
}
fprintf(fp, " %-*s %-*s %s\n", int(w_index), col_index, int(w_id), col_id, col_name);
for (const Row &row : rows) {
fprintf(fp,
" %-*d %-*s %s\n",
int(w_index),
row.index,
int(w_id),
row.identifier.c_str(),
row.name.c_str());
}
vkDestroyInstance(vk_instance, nullptr);
}
static GPUOSType determine_os_type()
{
#ifdef _WIN32
return GPU_OS_WIN;
#elif defined(__APPLE__)
return GPU_OS_MAC;
#else
return GPU_OS_UNIX;
#endif
}
void VKBackend::platform_init()
{
GPG.init(GPU_DEVICE_ANY,
determine_os_type(),
GPU_DRIVER_ANY,
GPU_SUPPORT_LEVEL_SUPPORTED,
GPU_BACKEND_VULKAN,
"",
"",
"",
GPU_ARCHITECTURE_IMR);
}
static void init_device_list(GHOST_IContext *ghost_context)
{
GHOST_VulkanHandles vulkan_handles = {};
ghost_context->getVulkanHandles(vulkan_handles);
uint32_t physical_devices_count = 0;
vkEnumeratePhysicalDevices(vulkan_handles.instance, &physical_devices_count, nullptr);
Array<VkPhysicalDevice> vk_physical_devices(physical_devices_count);
vkEnumeratePhysicalDevices(
vulkan_handles.instance, &physical_devices_count, vk_physical_devices.data());
int index = 0;
for (VkPhysicalDevice vk_physical_device : vk_physical_devices) {
if (missing_capabilities_get(vk_physical_device).is_empty() &&
GPU_vulkan_is_supported_driver(vk_physical_device))
{
VkPhysicalDeviceProperties vk_properties = {};
vkGetPhysicalDeviceProperties(vk_physical_device, &vk_properties);
std::stringstream identifier;
identifier << std::hex << vk_properties.vendorID << "/" << vk_properties.deviceID << "/"
<< index;
GPG.devices.append({identifier.str(),
index,
vk_properties.vendorID,
vk_properties.deviceID,
std::string(vk_properties.deviceName)});
}
index++;
}
std::ranges::sort(GPG.devices, [&](const GPUDevice &a, const GPUDevice &b) {
if (a.name == b.name) {
return a.index < b.index;
}
return a.name < b.name;
});
}
void VKBackend::platform_init(const VKDevice &device)
{
const VkPhysicalDeviceProperties &properties = device.physical_device_properties_get();
GPUDeviceType device_type = device.device_type();
GPUDriverType driver = device.driver_type();
GPUOSType os = determine_os_type();
GPUSupportLevel support_level = GPU_SUPPORT_LEVEL_SUPPORTED;
std::string vendor_name = device.vendor_name();
std::string driver_version = device.driver_version();
/* GPG has already been initialized, but without a specific device. Calling init twice will
* clear the list of devices. Making a copy of the device list and set it after initialization to
* make sure the list isn't destroyed at this moment, but only when the backend is destroyed. */
Vector<GPUDevice> devices = GPG.devices;
GPG.init(device_type,
os,
driver,
support_level,
GPU_BACKEND_VULKAN,
vendor_name.c_str(),
properties.deviceName,
driver_version.c_str(),
GPU_ARCHITECTURE_IMR);
GPG.devices = devices;
const VkPhysicalDeviceIDProperties &id_properties = device.physical_device_id_properties_get();
GPG.device_uuid = Array<uint8_t, 16>(Span<uint8_t>(id_properties.deviceUUID, VK_UUID_SIZE));
if (id_properties.deviceLUIDValid) {
GPG.device_luid = Array<uint8_t, 8>(Span<uint8_t>(id_properties.deviceUUID, VK_LUID_SIZE));
GPG.device_luid_node_mask = id_properties.deviceNodeMask;
}
else {
GPG.device_luid.reinitialize(0);
GPG.device_luid_node_mask = 0;
}
CLOG_INFO(&LOG,
"Using vendor [%s] device [%s] driver version [%s].",
vendor_name.c_str(),
device.vk_physical_device_properties_.deviceName,
driver_version.c_str());
}
enum class IntelGpuArch : uint32_t {
Gen9AndOlder,
Gen11,
Gen12,
Xe = Gen12,
Xe2,
Xe3AndNewer,
};
inline IntelGpuArch get_intel_gpu_arch(uint32_t device_id)
{
/* Source for device IDs:
* https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/include/pci_ids/iris_pci_ids.h
*/
switch (device_id & 0xFF00) {
case 0x2900: // Broadwater
case 0x2A00: // Broadwater/Eagle Lake
case 0x2E00: // Eagle Lake
case 0x0000: // Iron Lake
case 0x0100: // Ivy Bridge/Sandy Bridge/Baytrail
case 0x0F00: // Baytrail
case 0x0400: // Haswell
case 0x0C00: // Haswell
case 0x0D00: // Haswell
case 0x0A00: // Haswell / Appollo Lake
case 0x2200: // Cherrytrail
case 0x1600: // Broadwell
case 0x5A00: // Apollo Lake
case 0x1900: // Skylake
case 0x1A00: // Apollo Lake
case 0x3100: // Gemini Lake
case 0x5900: // Kaby Lake/Amber Lake
case 0x8700: // Kaby Lake/Coffee Lake
case 0x3E00: // Coffee Lake/Whiskey Lake
case 0x9B00: // Comet Lake
return IntelGpuArch::Gen9AndOlder;
case 0x8A00: // Ice Lake
case 0x4500: // Elkhart Lake
case 0x4E00: // Jasper Lake
return IntelGpuArch::Gen11;
case 0x9A00: // Tiger Lake
case 0x4C00: // Rocket Lake
case 0x4900: // DG1
case 0x4600: // Alder Lake
case 0x4F00: // Alchemist
case 0x5600: // Alchemist
case 0xA700: // Raptor Lake
case 0x7D00: // Meteor Lake / Arrow Lake
case 0xB600: // Meteor Lake / Arrow Lake
return IntelGpuArch::Xe;
case 0x6400: // Lunar Lake
case 0xE200: // Battlemage
return IntelGpuArch::Xe2;
case 0xB000: // Panther Lake
case 0xFD00: // Wildcat Lake
default:
return IntelGpuArch::Xe3AndNewer;
}
}
void VKBackend::detect_workarounds(VKDevice &device)
{
VKWorkarounds workarounds;
VKExtensions extensions;
if (G.debug & G_DEBUG_GPU_FORCE_WORKAROUNDS) {
CLOG_WARN(&LOG, "Forcing workarounds and disabling features and extensions");
/* Force workarounds and disable extensions. */
workarounds.not_aligned_pixel_formats = true;
extensions.shader_output_layer = false;
extensions.shader_output_viewport_index = false;
extensions.fragment_shader_barycentric = false;
extensions.dynamic_rendering_local_read = false;
extensions.dynamic_rendering_unused_attachments = false;
extensions.pageable_device_local_memory = false;
extensions.wide_lines = false;
extensions.line_rasterization = false;
extensions.extended_dynamic_state = false;
GCaps.stencil_export_support = false;
GCaps.texture_pool_workaround = true;
device.workarounds_ = workarounds;
device.extensions_ = extensions;
return;
}
if (G.debug & G_DEBUG_GPU_NO_TEXTURE_POOL) {
GCaps.texture_pool_workaround = true;
}
extensions.shader_output_layer =
device.physical_device_vulkan_12_features_get().shaderOutputLayer;
extensions.shader_output_viewport_index =
device.physical_device_vulkan_12_features_get().shaderOutputViewportIndex;
extensions.wide_lines = device.physical_device_features_get().wideLines;
extensions.fragment_shader_barycentric = device.supports_extension(
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
extensions.dynamic_rendering_local_read = device.supports_extension(
VK_KHR_DYNAMIC_RENDERING_LOCAL_READ_EXTENSION_NAME);
extensions.dynamic_rendering_unused_attachments = device.supports_extension(
VK_EXT_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_EXTENSION_NAME);
extensions.logic_ops = device.physical_device_features_get().logicOp;
extensions.maintenance4 = device.supports_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME);
extensions.memory_priority = device.supports_extension(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME);
extensions.pageable_device_local_memory = device.supports_extension(
VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_EXTENSION_NAME);
extensions.graphics_pipeline_library = device.supports_extension(
VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME);
extensions.line_rasterization = device.supports_extension(
VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME);
extensions.extended_dynamic_state = device.supports_extension(
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
extensions.vertex_input_dynamic_state = device.supports_extension(
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
#if 0
extensions.host_image_copy = device.supports_extension(VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME);
#endif
#ifdef _WIN32
extensions.external_memory = device.supports_extension(
VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME);
#elif not defined(__APPLE__)
extensions.external_memory = device.supports_extension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
#else
extensions.external_memory = false;
#endif
/* AMD GPUs don't support texture formats that use are aligned to 24 or 48 bits. */
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY) ||
GPU_type_matches(GPU_DEVICE_APPLE, GPU_OS_MAC, GPU_DRIVER_ANY))
{
workarounds.not_aligned_pixel_formats = true;
}
/* During testing graphics pipeline library feature it was detected that it would crash on
* official AMD drivers.
*/
if (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) &&
bool(G.debug & G_DEBUG_GPU))
{
extensions.graphics_pipeline_library = false;
extensions.vertex_input_dynamic_state = false;
}
/* Disable vertex input dynamic state for Qualcomm devices (#153414).
*
* TODO: We should re-validate vertex input dynamic state as there are multiple vendors with
* similar issues. It might be an oversight. Will wait for feedback from the driver developers
* and perform some out of bounds error checks.
*/
if (GPU_type_matches(GPU_DEVICE_QUALCOMM, GPU_OS_WIN, GPU_DRIVER_ANY)) {
extensions.vertex_input_dynamic_state = false;
}
/* Only enable by default dynamic rendering local read on Qualcomm devices. NVIDIA, AMD and Intel
* performance is better when disabled (20%). On Qualcomm devices the improvement can be
* substantial (16% on shader_balls.blend).
*
* `--debug-gpu-vulkan-local-read` can be used to use dynamic rendering local read on any
* supported platform.
*
* TODO: Check if bottleneck is during command building. If so we could fine-tune this after the
* device command building landed (T132682).
*/
if ((G.debug & G_DEBUG_GPU_FORCE_VULKAN_LOCAL_READ) == 0 &&
!GPU_type_matches(GPU_DEVICE_QUALCOMM, GPU_OS_ANY, GPU_DRIVER_ANY))
{
extensions.dynamic_rendering_local_read = false;
}
/* When using host image copy on certain NVIDIA platforms the allocation of textures that only
* use GPU_TEXTURE_USAGE_SHADER_READ/WRITE will fail to allocate the memory. This needs some more
* research as this might just be a missing flag when allocating. Another solution is to not
* allow host_imag_copy when only these two flags are set as the rest seems to work as expected.
*
* See #151826
*/
if (GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
extensions.host_image_copy = false;
}
#ifdef _WIN32
if (GPU_type_matches(GPU_DEVICE_INTEL | GPU_DEVICE_INTEL_UHD, GPU_OS_WIN, GPU_DRIVER_OFFICIAL)) {
IntelGpuArch gpu_arch = get_intel_gpu_arch(device.physical_device_properties_get().deviceID);
/* Intel Gen9 iGPUs (Intel 7th to 10th Gen Processor Graphics driver) show a black screen at
* application startup when using VK_EXT_vertex_input_dynamic_state.
*
* See #147721
*/
if (gpu_arch == IntelGpuArch::Gen9AndOlder) {
extensions.vertex_input_dynamic_state = false;
}
/* Using the texture pool causes varying issues on older Intel iGPUs.
* Note: Gen12 iGPUs are partly covered by the Intel 11th to 14th Gen Processor Graphics driver
* and the Intel Arc Graphics driver (the latter handles Arrow Lake and Meteor Lake).
* - Visual corruptions can be seen on Gen9 and older iGPUs (Intel 7th to 10th Gen Processor
* Graphics driver; #147721).
* - When using the image cache, visual artifacts can be seen on Gen11 and Gen12 iGPUs
* (#156496) and Gen12 dGPUs (#160002).
* - When using the texture pool without the image cache, memory leaks happen on Gen11 and
* Gen12 GPUs (#157777).
*/
if (gpu_arch <= IntelGpuArch::Gen12) {
GCaps.texture_pool_workaround = true;
}
}
#endif
#ifdef __APPLE__
extensions.extended_dynamic_state = false;
#endif
device.workarounds_ = workarounds;
device.extensions_ = extensions;
}
void VKBackend::platform_exit()
{
GPG.clear();
VKDevice &device = VKBackend::get().device;
if (device.is_initialized()) {
device.deinit();
}
}
void VKBackend::init_resources()
{
compiler_ = MEM_new<ShaderCompiler>(
__func__, GPU_max_parallel_compilations(), GPUWorker::ContextType::Main);
}
void VKBackend::delete_resources()
{
MEM_delete(compiler_);
}
void VKBackend::compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len)
{
VKContext &context = *VKContext::get();
render_graph::VKResourceAccessInfo &resources = context.reset_and_get_access_info();
render_graph::VKDispatchNode::CreateInfo dispatch_info(resources);
context.update_pipeline_data(dispatch_info.dispatch_node.pipeline_data);
dispatch_info.dispatch_node.group_count_x = groups_x_len;
dispatch_info.dispatch_node.group_count_y = groups_y_len;
dispatch_info.dispatch_node.group_count_z = groups_z_len;
context.render_graph().add_node(dispatch_info);
}
void VKBackend::compute_dispatch_indirect(StorageBuf *indirect_buf)
{
BLI_assert(indirect_buf);
VKContext &context = *VKContext::get();
VKStorageBuffer &indirect_buffer = *unwrap(indirect_buf);
render_graph::VKResourceAccessInfo &resources = context.reset_and_get_access_info();
render_graph::VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(resources);
context.update_pipeline_data(dispatch_indirect_info.dispatch_indirect_node.pipeline_data);
dispatch_indirect_info.dispatch_indirect_node.buffer = indirect_buffer.vk_handle();
dispatch_indirect_info.dispatch_indirect_node.offset = 0;
context.render_graph().add_node(dispatch_indirect_info);
}
Context *VKBackend::context_alloc(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context)
{
if (ghost_window) {
BLI_assert(ghost_context == nullptr);
ghost_context = ghost_window->getDrawingContext();
}
BLI_assert(ghost_context != nullptr);
if (!device.is_initialized()) {
device.init(ghost_context);
device.extensions_get().log();
device.workarounds_get().log();
init_device_list(ghost_context);
}
VKContext *context = new VKContext(ghost_window, ghost_context);
device.context_register(*context);
ghost_context->setVulkanSwapBuffersCallbacks(
VKContext::swap_buffer_draw_callback,
VKContext::swap_buffer_acquired_callback,
VKContext::openxr_acquire_framebuffer_image_callback,
VKContext::openxr_release_framebuffer_image_callback);
return context;
}
Batch *VKBackend::batch_alloc()
{
return new VKBatch();
}
Fence *VKBackend::fence_alloc()
{
return new VKFence();
}
FrameBuffer *VKBackend::framebuffer_alloc(const char *name)
{
return new VKFrameBuffer(name);
}
IndexBuf *VKBackend::indexbuf_alloc()
{
return new VKIndexBuffer();
}
PixelBuffer *VKBackend::pixelbuf_alloc(size_t size)
{
return new VKPixelBuffer(size);
}
QueryPool *VKBackend::querypool_alloc()
{
return new VKQueryPool();
}
Shader *VKBackend::shader_alloc(const char *name)
{
return new VKShader(name);
}
Texture *VKBackend::texture_alloc(const char *name)
{
return new VKTexture(name);
}
TexturePool *VKBackend::texturepool_alloc()
{
if (GCaps.texture_pool_workaround) {
CLOG_TRACE(&LOG, "Using texture pool \"TexturePoolImpl\".");
return new TexturePoolImpl();
}
CLOG_TRACE(&LOG, "Using texture pool \"VKTexturePool\".");
return new VKTexturePool();
}
UniformBuf *VKBackend::uniformbuf_alloc(size_t size, const char *name)
{
return new VKUniformBuffer(size, name);
}
StorageBuf *VKBackend::storagebuf_alloc(size_t size, GPUUsageType usage, const char *name)
{
return new VKStorageBuffer(size, usage, name);
}
VertBuf *VKBackend::vertbuf_alloc()
{
return new VKVertexBuffer();
}
void VKBackend::render_begin()
{
VKThreadData &thread_data = device.current_thread_data();
BLI_assert_msg(thread_data.rendering_depth >= 0, "Unbalanced `GPU_render_begin/end`");
thread_data.rendering_depth += 1;
}
void VKBackend::render_end()
{
VKThreadData &thread_data = device.current_thread_data();
thread_data.rendering_depth -= 1;
BLI_assert_msg(thread_data.rendering_depth >= 0, "Unbalanced `GPU_render_begin/end`");
if (G.background) {
if (thread_data.rendering_depth == 0) {
VKContext *context = VKContext::get();
if (context != nullptr) {
context->flush();
}
std::scoped_lock lock(device.orphaned_data.mutex_get());
device.orphaned_data.move_data(device.orphaned_data_render,
device.orphaned_data.timeline_ + 1);
}
}
/* When performing animation render we want to release any discarded resources during rendering
* after each frame.
*/
if (G.is_rendering && thread_data.rendering_depth == 0 && !BLI_thread_is_main()) {
std::scoped_lock lock(device.orphaned_data.mutex_get());
device.orphaned_data.move_data(device.orphaned_data_render,
device.orphaned_data.timeline_ + 1);
}
}
void VKBackend::render_step(bool force_resource_release)
{
if (force_resource_release) {
std::scoped_lock lock(device.orphaned_data.mutex_get());
device.orphaned_data.move_data(device.orphaned_data_render,
device.orphaned_data.timeline_ + 1);
}
}
void VKBackend::capabilities_init(VKDevice &device)
{
const VkPhysicalDeviceProperties &properties = device.physical_device_properties_get();
const VkPhysicalDeviceLimits &limits = properties.limits;
/* Reset all capabilities from previous context. */
GCaps = {};
GCaps.geometry_shader_support = true;
GCaps.stencil_export_support = device.supports_extension(
VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
GCaps.max_texture_size = max_ii(limits.maxImageDimension1D, limits.maxImageDimension2D);
GCaps.max_texture_3d_size = min_uu(limits.maxImageDimension3D, INT_MAX);
GCaps.max_buffer_texture_size = min_uu(limits.maxTexelBufferElements, UINT_MAX);
GCaps.max_texture_layers = min_uu(limits.maxImageArrayLayers, INT_MAX);
GCaps.max_textures = min_uu(limits.maxPerStageDescriptorSampledImages, INT_MAX);
GCaps.max_images = min_uu(limits.maxPerStageDescriptorStorageImages, INT_MAX);
for (int i = 0; i < 3; i++) {
GCaps.max_work_group_count[i] = min_uu(limits.maxComputeWorkGroupCount[i], INT_MAX);
GCaps.max_work_group_size[i] = min_uu(limits.maxComputeWorkGroupSize[i], INT_MAX);
}
GCaps.max_uniforms_vert = GCaps.max_uniforms_frag = min_uu(
limits.maxPerStageDescriptorUniformBuffers, INT_MAX);
GCaps.max_batch_indices = min_uu(limits.maxDrawIndirectCount, INT_MAX);
GCaps.max_batch_vertices = min_uu(limits.maxDrawIndexedIndexValue, INT_MAX);
GCaps.max_vertex_attribs = min_uu(limits.maxVertexInputAttributes, INT_MAX);
GCaps.max_varying_floats = min_uu(limits.maxVertexOutputComponents, INT_MAX);
GCaps.max_shader_storage_buffer_bindings = GCaps.max_compute_shader_storage_blocks = min_uu(
limits.maxPerStageDescriptorStorageBuffers, INT_MAX);
GCaps.max_uniform_buffer_size = size_t(limits.maxUniformBufferRange);
GCaps.max_storage_buffer_size = size_t(limits.maxStorageBufferRange);
GCaps.storage_buffer_alignment = limits.minStorageBufferOffsetAlignment;
GCaps.max_parallel_compilations = BLI_system_thread_count();
GCaps.mem_stats_support = true;
uint32_t vk_extension_count;
vkEnumerateDeviceExtensionProperties(
device.physical_device_get(), nullptr, &vk_extension_count, nullptr);
GCaps.extensions_len = vk_extension_count;
GCaps.extension_get = vk_extension_get;
detect_workarounds(device);
}
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <cstdio>
#include "gpu_backend.hh"
#ifdef WITH_RENDERDOC
# include "renderdoc_api.hh"
#endif
#include "vk_common.hh"
#include "vk_device.hh"
#include "vk_shader_compiler.hh"
namespace blender::gpu {
class VKContext;
class VKDescriptorSet;
class VKDescriptorSetTracker;
class VKBackend : public GPUBackend {
private:
#ifdef WITH_RENDERDOC
renderdoc::api::Renderdoc renderdoc_api_;
#endif
public:
/* Global instance to device handles. */
VKDevice device;
VKBackend()
{
platform_init();
}
virtual ~VKBackend()
{
VKBackend::platform_exit();
}
/**
* Does the running platform contain any device that meets the minimum requirements to start the
* Vulkan backend.
*
* Function is used to validate that a Blender UI can be started. It calls vulkan API commands
* directly to ensure no parts of Blender needs to be initialized.
*/
static bool is_supported();
/**
* Print one line per Vulkan device that meets minimum requirements, in the format used by
* `--gpu-device help`. Each line is `<vendor-hex>/<device-hex>/<index> <name>`.
* Operates without an active backend (creates a temporary Vulkan instance).
*/
static void supported_devices_print(FILE *fp);
void init_resources() override;
void delete_resources() override;
void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) override;
void compute_dispatch_indirect(StorageBuf *indirect_buf) override;
Context *context_alloc(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context) override;
Batch *batch_alloc() override;
Fence *fence_alloc() override;
FrameBuffer *framebuffer_alloc(const char *name) override;
IndexBuf *indexbuf_alloc() override;
PixelBuffer *pixelbuf_alloc(size_t size) override;
QueryPool *querypool_alloc() override;
Shader *shader_alloc(const char *name) override;
Texture *texture_alloc(const char *name) override;
TexturePool *texturepool_alloc() override;
UniformBuf *uniformbuf_alloc(size_t size, const char *name) override;
StorageBuf *storagebuf_alloc(size_t size, GPUUsageType usage, const char *name) override;
VertBuf *vertbuf_alloc() override;
void shader_cache_dir_clear_old() override
{
VKShaderCompiler::cache_dir_clear_old();
}
/* Render Frame Coordination --
* Used for performing per-frame actions globally */
void render_begin() override;
void render_end() override;
void render_step(bool /*force_resource_release*/) override;
bool debug_capture_begin(const char *title);
void debug_capture_end();
static VKBackend &get()
{
return *static_cast<VKBackend *>(GPUBackend::get());
}
static void platform_init(const VKDevice &device);
static void capabilities_init(VKDevice &device);
private:
static void detect_workarounds(VKDevice &device);
static void platform_init();
static void platform_exit();
/* These classes are allowed to modify the global device. */
friend class VKContext;
friend class VKDescriptorSet;
friend class VKDescriptorSetTracker;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,132 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_batch.hh"
#include "render_graph/nodes/vk_pipeline_data.hh"
#include "vk_context.hh"
#include "vk_framebuffer.hh"
#include "vk_index_buffer.hh"
#include "vk_state_manager.hh"
#include "vk_storage_buffer.hh"
#include "vk_vertex_attribute_object.hh"
#include "vk_vertex_buffer.hh"
namespace blender::gpu {
void VKBatch::draw(int vertex_first, int vertex_count, int instance_first, int instance_count)
{
VKContext &context = *VKContext::get();
render_graph::VKResourceAccessInfo &resource_access_info = context.reset_and_get_access_info();
VKVertexAttributeObject vao;
vao.update_bindings(context, *this);
VKIndexBuffer *index_buffer = index_buffer_get();
const bool draw_indexed = index_buffer != nullptr;
/* Upload geometry */
if (draw_indexed) {
index_buffer->upload_data();
}
VKFrameBuffer &framebuffer = *context.active_framebuffer_get();
framebuffer.rendering_ensure(context);
if (draw_indexed) {
render_graph::VKDrawIndexedNode::CreateInfo draw_indexed(resource_access_info);
draw_indexed.node_data.index_count = vertex_count;
draw_indexed.node_data.instance_count = instance_count;
draw_indexed.node_data.first_index = index_buffer->index_start_get() + vertex_first;
draw_indexed.node_data.vertex_offset = index_buffer->index_base_get();
draw_indexed.node_data.first_instance = instance_first;
draw_indexed.node_data.index_buffer.buffer = index_buffer->vk_handle();
draw_indexed.node_data.index_buffer.index_type = index_buffer->vk_index_type();
vao.bind(draw_indexed.node_data.vertex_buffers);
context.update_pipeline_data(framebuffer, prim_type, vao, draw_indexed.node_data.graphics);
context.render_graph().add_node(draw_indexed);
}
else {
render_graph::VKDrawNode::CreateInfo draw(resource_access_info);
draw.node_data.vertex_count = vertex_count;
draw.node_data.instance_count = instance_count;
draw.node_data.first_vertex = vertex_first;
draw.node_data.first_instance = instance_first;
vao.bind(draw.node_data.vertex_buffers);
context.update_pipeline_data(framebuffer, prim_type, vao, draw.node_data.graphics);
context.render_graph().add_node(draw);
}
}
void VKBatch::draw_indirect(StorageBuf *indirect_buf, intptr_t offset)
{
multi_draw_indirect(indirect_buf, 1, offset, 0);
}
void VKBatch::multi_draw_indirect(StorageBuf *indirect_buf,
const int count,
const intptr_t offset,
const intptr_t stride)
{
VKStorageBuffer &indirect_buffer = *unwrap(unwrap(indirect_buf));
multi_draw_indirect(indirect_buffer.vk_handle(), count, offset, stride);
}
void VKBatch::multi_draw_indirect(const VkBuffer indirect_buffer,
const int count,
const intptr_t offset,
const intptr_t stride)
{
VKContext &context = *VKContext::get();
render_graph::VKResourceAccessInfo &resource_access_info = context.reset_and_get_access_info();
VKVertexAttributeObject vao;
vao.update_bindings(context, *this);
VKIndexBuffer *index_buffer = index_buffer_get();
const bool draw_indexed = index_buffer != nullptr;
/* Upload geometry */
if (draw_indexed) {
index_buffer->upload_data();
}
VKFrameBuffer &framebuffer = *context.active_framebuffer_get();
framebuffer.rendering_ensure(context);
if (draw_indexed) {
render_graph::VKDrawIndexedIndirectNode::CreateInfo draw_indexed_indirect(
resource_access_info);
draw_indexed_indirect.node_data.indirect_buffer = indirect_buffer;
draw_indexed_indirect.node_data.offset = offset;
draw_indexed_indirect.node_data.draw_count = count;
draw_indexed_indirect.node_data.stride = stride;
draw_indexed_indirect.node_data.index_buffer.buffer = index_buffer->vk_handle();
draw_indexed_indirect.node_data.index_buffer.index_type = index_buffer->vk_index_type();
vao.bind(draw_indexed_indirect.node_data.vertex_buffers);
context.update_pipeline_data(
framebuffer, prim_type, vao, draw_indexed_indirect.node_data.graphics);
context.render_graph().add_node(draw_indexed_indirect);
}
else {
render_graph::VKDrawIndirectNode::CreateInfo draw(resource_access_info);
draw.node_data.indirect_buffer = indirect_buffer;
draw.node_data.offset = offset;
draw.node_data.draw_count = count;
draw.node_data.stride = stride;
vao.bind(draw.node_data.vertex_buffers);
context.update_pipeline_data(framebuffer, prim_type, vao, draw.node_data.graphics);
context.render_graph().add_node(draw);
}
}
} // namespace blender::gpu

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_index_buffer.hh"
#include "vk_vertex_buffer.hh"
#include "GPU_batch.hh"
namespace blender::gpu {
class VKBatch : public Batch {
public:
void draw(int vertex_first, int vertex_count, int instance_first, int instance_count) override;
void draw_indirect(StorageBuf *indirect_buf, intptr_t offset) override;
void multi_draw_indirect(StorageBuf *indirect_buf,
int count,
intptr_t offset,
intptr_t stride) override;
void multi_draw_indirect(VkBuffer indirect_buf, int count, intptr_t offset, intptr_t stride);
VKVertexBuffer *vertex_buffer_get(int index);
VKIndexBuffer *index_buffer_get();
};
inline VKBatch *unwrap(Batch *batch)
{
return static_cast<VKBatch *>(batch);
}
inline VKVertexBuffer *VKBatch::vertex_buffer_get(int index)
{
return unwrap(verts_(index));
}
inline VKIndexBuffer *VKBatch::index_buffer_get()
{
return unwrap(unwrap(elem));
}
} // namespace blender::gpu

View File

@@ -0,0 +1,280 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_buffer.hh"
#include "vk_backend.hh"
#include "vk_context.hh"
#include <vulkan/vulkan_core.h>
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
VKBuffer::~VKBuffer()
{
if (is_allocated()) {
free();
}
}
bool VKBuffer::create(size_t size_in_bytes,
VkBufferUsageFlags buffer_usage,
VmaMemoryUsage vma_memory_usage,
VmaAllocationCreateFlags allocation_flags,
float priority,
bool export_memory,
const char *debug_name)
{
BLI_assert(!is_allocated());
BLI_assert(vk_buffer_ == VK_NULL_HANDLE);
BLI_assert(mapped_memory_ == nullptr);
if (allocation_failed_) {
return false;
}
size_in_bytes_ = size_in_bytes;
/*
* Vulkan doesn't allow empty buffers but some areas (DrawManager Instance data, PyGPU) create
* them.
*/
alloc_size_in_bytes_ = ceil_to_multiple_ul(max_ulul(size_in_bytes_, 16), 16);
VKDevice &device = VKBackend::get().device;
/* Precheck max buffer size. */
if (device.extensions_get().maintenance4 &&
alloc_size_in_bytes_ > device.physical_device_maintenance4_properties_get().maxBufferSize)
{
CLOG_WARN(
&LOG,
"Couldn't allocate buffer, requested allocation exceeds the maxBufferSize of the device.");
allocation_failed_ = true;
size_in_bytes_ = 0;
alloc_size_in_bytes_ = 0;
return false;
}
VmaAllocator allocator = device.mem_allocator_get();
VkBufferCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
create_info.flags = 0;
create_info.size = alloc_size_in_bytes_;
create_info.usage = buffer_usage;
/* We use the same command queue for the compute and graphics pipeline, so it is safe to use
* exclusive resource handling. */
create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
create_info.queueFamilyIndexCount = 1;
const uint32_t queue_family_indices[1] = {device.queue_family_get()};
create_info.pQueueFamilyIndices = queue_family_indices;
VkExternalMemoryBufferCreateInfo external_memory_create_info = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, 0};
VmaAllocationCreateInfo vma_create_info = {};
vma_create_info.flags = allocation_flags;
vma_create_info.priority = priority;
vma_create_info.usage = vma_memory_usage;
if (export_memory) {
create_info.pNext = &external_memory_create_info;
external_memory_create_info.handleTypes = vk_external_memory_handle_type();
/* Dedicated allocation for zero offset. */
vma_create_info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
vma_create_info.pool = device.vma_pools.external_memory_pixel_buffer.pool;
}
if (debug_name && G.debug & G_DEBUG_GPU) {
vma_create_info.flags |= VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
vma_create_info.pUserData = (void *)debug_name;
}
VkResult result = vmaCreateBuffer(
allocator, &create_info, &vma_create_info, &vk_buffer_, &allocation_, nullptr);
if (result != VK_SUCCESS) {
allocation_failed_ = true;
size_in_bytes_ = 0;
alloc_size_in_bytes_ = 0;
return false;
}
device.resources.add_buffer(vk_buffer_);
if (debug_name) {
debug::object_label(vk_buffer_, debug_name);
}
/* Check if the memory is mappable. Although the Vulkan specs allow to map any memory that is
* host visible, VMA checks for specific host access flags.
*
* Source:
* https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/memory_mapping.html
*/
const bool is_mappable = bool(allocation_flags &
(VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT));
if (is_mappable) {
return map();
}
return true;
}
void VKBuffer::update_immediately(const void *data) const
{
update_sub_immediately(0, size_in_bytes_, data);
}
void VKBuffer::update_sub_immediately(size_t start_offset,
size_t data_size,
const void *data) const
{
BLI_assert_msg(is_mapped(), "Cannot update a non-mapped buffer.");
memcpy(static_cast<uint8_t *>(mapped_memory_) + start_offset, data, data_size);
}
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;
VmaAllocator allocator = device.mem_allocator_get();
vmaFlushAllocation(allocator, allocation_, 0, max_ulul(size_in_bytes(), 1));
}
void VKBuffer::clear(VKContext &context, uint32_t clear_value)
{
render_graph::VKFillBufferNode::CreateInfo fill_buffer = {};
fill_buffer.vk_buffer = vk_buffer_;
fill_buffer.data = clear_value;
fill_buffer.size = alloc_size_in_bytes_;
context.render_graph().add_node(fill_buffer);
}
void VKBuffer::async_flush_to_host(VKContext &context)
{
BLI_assert(async_timeline_ == 0);
context.rendering_end();
async_timeline_ = context.flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
}
void VKBuffer::read_async(VKContext &context, void *data)
{
BLI_assert_msg(is_mapped(), "Cannot read a non-mapped buffer.");
if (async_timeline_ == 0) {
async_flush_to_host(context);
}
VKDevice &device = VKBackend::get().device;
device.wait_for_timeline(async_timeline_);
async_timeline_ = 0;
memcpy(data, mapped_memory_, size_in_bytes_);
}
void VKBuffer::read(VKContext &context, void *data) const
{
BLI_assert_msg(is_mapped(), "Cannot read a non-mapped buffer.");
BLI_assert(async_timeline_ == 0);
context.rendering_end();
context.flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::WAIT_FOR_COMPLETION |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
memcpy(data, mapped_memory_, size_in_bytes_);
}
bool VKBuffer::map()
{
BLI_assert(!is_mapped());
const VKDevice &device = VKBackend::get().device;
VmaAllocator allocator = device.mem_allocator_get();
VkResult result = vmaMapMemory(allocator, allocation_, &mapped_memory_);
return result == VK_SUCCESS;
}
void VKBuffer::unmap()
{
BLI_assert(is_mapped());
const VKDevice &device = VKBackend::get().device;
VmaAllocator allocator = device.mem_allocator_get();
vmaUnmapMemory(allocator, allocation_);
mapped_memory_ = nullptr;
}
VkDeviceMemory VKBuffer::export_memory_get(size_t &memory_size)
{
const VKDevice &device = VKBackend::get().device;
VmaAllocator allocator = device.mem_allocator_get();
VmaAllocationInfo info = {};
vmaGetAllocationInfo(allocator, allocation_, &info);
/* VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT should ensure this. */
if (info.offset != 0) {
BLI_assert(!"Failed to get zero offset export memory for Vulkan buffer");
return VK_NULL_HANDLE;
}
memory_size = info.size;
return info.deviceMemory;
}
void VKBuffer::flush_mapped_memory()
{
const VKDevice &device = VKBackend::get().device;
VmaAllocator allocator = device.mem_allocator_get();
vmaFlushAllocation(allocator, allocation_, 0, size_in_bytes_);
}
void VKBuffer::invalidate_mapped_memory()
{
const VKDevice &device = VKBackend::get().device;
VmaAllocator allocator = device.mem_allocator_get();
vmaInvalidateAllocation(allocator, allocation_, 0, size_in_bytes_);
}
bool VKBuffer::free()
{
if (is_mapped()) {
unmap();
}
VKDiscardPool::discard_pool_get().discard_buffer(vk_buffer_, allocation_);
allocation_ = VK_NULL_HANDLE;
vk_buffer_ = VK_NULL_HANDLE;
return true;
}
void VKBuffer::free_immediately(VKDevice &device)
{
BLI_assert(vk_buffer_ != VK_NULL_HANDLE);
BLI_assert(allocation_ != VK_NULL_HANDLE);
if (is_mapped()) {
unmap();
}
device.resources.remove_buffer(vk_buffer_);
vmaDestroyBuffer(device.mem_allocator_get(), vk_buffer_, allocation_);
allocation_ = VK_NULL_HANDLE;
vk_buffer_ = VK_NULL_HANDLE;
}
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,177 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_context_private.hh"
#include "BLI_utility_mixins.hh"
#include "vk_common.hh"
namespace blender::gpu {
class VKContext;
class VKDevice;
/**
* Class for handing vulkan buffers (allocation/updating/binding).
*/
class VKBuffer : public NonCopyable {
size_t size_in_bytes_ = 0;
size_t alloc_size_in_bytes_ = 0;
VkBuffer vk_buffer_ = VK_NULL_HANDLE;
VmaAllocation allocation_ = VK_NULL_HANDLE;
TimelineValue async_timeline_ = 0;
/** Has a previous allocation failed. Will skip reallocations. */
bool allocation_failed_ = false;
/* Pointer to the virtually mapped memory. */
void *mapped_memory_ = nullptr;
VkDeviceAddress vk_device_address = 0;
public:
VKBuffer() = default;
virtual ~VKBuffer();
/** Has this buffer been allocated? */
bool is_allocated() const;
/**
* Allocate the buffer.
*/
bool create(size_t size,
VkBufferUsageFlags buffer_usage,
VmaMemoryUsage vma_memory_usage,
VmaAllocationCreateFlags vma_allocation_flags,
float priority,
bool export_memory = false,
const char *debug_name = "VKBuffer");
void clear(VKContext &context, uint32_t clear_value);
void update_immediately(const void *data) const;
void update_sub_immediately(size_t start_offset, size_t data_size, 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;
/**
* Read the buffer (synchronously).
*/
void read(VKContext &context, void *data) const;
/**
* Start a async read-back.
*/
void async_flush_to_host(VKContext &context);
/**
* Wait until the async read back is finished and fill the given data with the content of the
* buffer.
*
* Will start a new async read-back when there is no read back in progress.
*/
void read_async(VKContext &context, void *data);
/**
* Free the buffer.
*
* Discards the buffer so it can be destroyed safely later. Buffers can still be used when
* rendering so we can only destroy them after the rendering is completed.
*/
bool free();
/**
* Destroy the buffer immediately.
*/
void free_immediately(VKDevice &device);
int64_t size_in_bytes() const
{
return size_in_bytes_;
}
inline int64_t allocated_size_in_bytes() const
{
return alloc_size_in_bytes_;
}
VkBuffer vk_handle() const
{
return vk_buffer_;
}
/**
* Get the reference to the mapped memory.
*
* Can only be called when the buffer is (still) mapped.
*/
void *mapped_memory_get() const;
VkDeviceAddress device_address_get() const
{
return vk_device_address;
}
/**
* Is this buffer mapped (visible on host)
*/
bool is_mapped() const;
/**
* Get allocated device memory.
*/
VkDeviceMemory export_memory_get(size_t &memory_size);
/**
* Flush the mapped memory after writing to it on the host. Only has an effect if the memory is
* not host coherent.
*/
void flush_mapped_memory();
/**
* Invalidates the mapped memory after writing to it on the device. Only has an effect if the
* memory is not host coherent.
*/
void invalidate_mapped_memory();
private:
/** Check if this buffer is mapped. */
bool map();
void unmap();
};
inline void *VKBuffer::mapped_memory_get() const
{
BLI_assert_msg(this->is_mapped(), "Cannot access a non-mapped buffer.");
return mapped_memory_;
}
inline bool VKBuffer::is_mapped() const
{
return mapped_memory_ != nullptr;
}
inline bool VKBuffer::is_allocated() const
{
return allocation_ != VK_NULL_HANDLE;
}
/**
* Helper struct to enable buffers to be bound with an offset.
*
* Used for de-interleaved vertex input buffers and immediate mode buffers.
*/
struct VKBufferWithOffset {
VkBuffer buffer;
VkDeviceSize offset;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_math_base.h"
#include "vk_buffer.hh"
#include "vk_buffer_pool.hh"
#include "vk_context.hh"
#include "vk_debug.hh"
#include <memory>
namespace blender::gpu {
VKBufferPool::~VKBufferPool()
{
BLI_assert(buffers_.is_empty());
}
VKBufferWithOffset VKBufferPool::append(Span<uint8_t> data)
{
const bool should_allocate_new_buffer = buffers_.is_empty() ||
(buffers_.last()->size_in_bytes() - buffer_offset_) <
data.size();
/* Flush last buffer as all data is available. */
if (should_allocate_new_buffer && !buffers_.is_empty() && buffer_offset_ != 0) {
finalize_active_buffer();
}
if (should_allocate_new_buffer) {
VkDeviceSize allocation_size = std::max(VkDeviceSize(data.size()), default_buffer_size_);
std::unique_ptr<VKBuffer> new_buffer = std::make_unique<VKBuffer>();
new_buffer->create(allocation_size,
vk_buffer_usage_,
vma_memory_usage_,
vma_allocation_create_flags_,
priority_,
false,
name_.c_str());
debug::object_label(new_buffer->vk_handle(), name_);
buffers_.append(std::move(new_buffer));
buffer_offset_ = 0;
}
VKBufferWithOffset result = {.buffer = buffers_.last()->vk_handle(), .offset = buffer_offset_};
/* Resize data_ when more space is needed than the default_buffer_size_. */
if (buffer_offset_ + data.size() > data_.size()) {
data_.resize(buffer_offset_ + data.size());
}
data_.as_mutable_span()
.slice(IndexRange::from_begin_size(buffer_offset_, data.size()))
.copy_from(data);
buffer_offset_ += data.size();
buffer_offset_ = ceil_to_multiple_ul(buffer_offset_, alignment_);
return result;
}
void VKBufferPool::ensure_uploaded()
{
if (!buffers_.is_empty() && buffer_offset_ != 0) {
finalize_active_buffer();
}
}
void VKBufferPool::discard()
{
buffers_.clear();
buffer_offset_ = 0;
}
void VKBufferPool::finalize_active_buffer()
{
BLI_assert(!buffers_.is_empty());
std::unique_ptr<VKBuffer> &active_buffer = buffers_.last();
if (active_buffer->is_mapped()) {
active_buffer->update_sub_immediately(0, buffer_offset_, data_.data());
}
else {
VKContext &context = *VKContext::get();
void *data_copy = MEM_new_uninitialized(active_buffer->allocated_size_in_bytes(), __func__);
memcpy(data_copy, data_.data(), std::min(data_.size(), int64_t(buffer_offset_)));
active_buffer->update_render_graph(context, data_copy);
}
}
} // namespace blender::gpu

View File

@@ -0,0 +1,78 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_string_ref.hh"
#include "vk_buffer.hh"
#include "vk_common.hh"
namespace blender::gpu {
/**
* \brief A pool of VkBuffers.
*/
class VKBufferPool {
/**
* All allocated buffers in the pool
*/
Vector<std::unique_ptr<VKBuffer>> buffers_;
StringRefNull name_;
VkDeviceSize buffer_offset_;
VkDeviceSize default_buffer_size_;
VkDeviceSize alignment_;
Vector<uint8_t> data_;
VkBufferUsageFlags vk_buffer_usage_;
VmaMemoryUsage vma_memory_usage_;
VmaAllocationCreateFlags vma_allocation_create_flags_;
float priority_;
public:
VKBufferPool(VKBufferPool &&other) = default;
VKBufferPool(StringRefNull name,
VkDeviceSize default_buffer_size,
VkDeviceSize alignment,
VkBufferUsageFlags vk_buffer_usage,
VmaMemoryUsage vma_memory_usage,
VmaAllocationCreateFlags vma_allocation_create_flags,
float priority = 1.0f)
: name_(name),
default_buffer_size_(default_buffer_size),
alignment_(alignment),
vk_buffer_usage_(vk_buffer_usage),
vma_memory_usage_(vma_memory_usage),
vma_allocation_create_flags_(vma_allocation_create_flags),
priority_(priority)
{
data_.resize(default_buffer_size_);
}
virtual ~VKBufferPool();
/**
* \brief Append the given data.
*
* \returns the buffer handle and offset in the buffer where the data is stored.
* \note data might not be available in the buffer yet. Use `ensure_uploaded` to ensure that all
* data is available by the GPU.
*/
VKBufferWithOffset append(Span<uint8_t> data);
/**
* \brief Ensure that all the data is uploaded.
*/
void ensure_uploaded();
/**
* \brief Discard the current buffers to the discard pool.
*/
void discard();
private:
void finalize_active_buffer();
};
} // namespace blender::gpu

View File

@@ -0,0 +1,906 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_utildefines.h"
#include "vk_backend.hh"
#include "vk_common.hh"
namespace blender::gpu {
VkImageAspectFlags to_vk_image_aspect_flag_bits(const TextureFormat format)
{
switch (format) {
/* Formats texture & render-buffer */
case TextureFormat::UINT_32_32_32_32:
case TextureFormat::UINT_32_32:
case TextureFormat::UINT_32:
case TextureFormat::UINT_16_16_16_16:
case TextureFormat::UINT_16_16:
case TextureFormat::UINT_16:
case TextureFormat::UINT_8_8_8_8:
case TextureFormat::UINT_8_8:
case TextureFormat::UINT_8:
case TextureFormat::SINT_32_32_32_32:
case TextureFormat::SINT_32_32:
case TextureFormat::SINT_32:
case TextureFormat::SINT_16_16_16_16:
case TextureFormat::SINT_16_16:
case TextureFormat::SINT_16:
case TextureFormat::SINT_8_8_8_8:
case TextureFormat::SINT_8_8:
case TextureFormat::SINT_8:
case TextureFormat::SFLOAT_32_32_32_32:
case TextureFormat::SFLOAT_32_32:
case TextureFormat::SFLOAT_32:
case TextureFormat::SFLOAT_16_16_16_16:
case TextureFormat::SFLOAT_16_16:
case TextureFormat::SFLOAT_16:
case TextureFormat::UNORM_16_16_16_16:
case TextureFormat::UNORM_16_16:
case TextureFormat::UNORM_16:
case TextureFormat::UNORM_8_8_8_8:
case TextureFormat::UNORM_8_8:
case TextureFormat::UNORM_8:
return VK_IMAGE_ASPECT_COLOR_BIT;
/* Special formats texture & render-buffer */
case TextureFormat::UNORM_10_10_10_2:
case TextureFormat::UINT_10_10_10_2:
case TextureFormat::UFLOAT_11_11_10:
case TextureFormat::SRGBA_8_8_8_8:
return VK_IMAGE_ASPECT_COLOR_BIT;
/* Depth Formats. */
case TextureFormat::SFLOAT_32_DEPTH:
case TextureFormat::UNORM_16_DEPTH:
return VK_IMAGE_ASPECT_DEPTH_BIT;
case TextureFormat::SFLOAT_32_DEPTH_UINT_8:
return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
/* Texture only formats. */
case TextureFormat::UINT_32_32_32:
case TextureFormat::UINT_16_16_16:
case TextureFormat::UINT_8_8_8:
case TextureFormat::SINT_32_32_32:
case TextureFormat::SINT_16_16_16:
case TextureFormat::SINT_8_8_8:
case TextureFormat::UNORM_16_16_16:
case TextureFormat::UNORM_8_8_8:
case TextureFormat::SNORM_16_16_16_16:
case TextureFormat::SNORM_16_16_16:
case TextureFormat::SNORM_16_16:
case TextureFormat::SNORM_16:
case TextureFormat::SNORM_8_8_8_8:
case TextureFormat::SNORM_8_8_8:
case TextureFormat::SNORM_8_8:
case TextureFormat::SNORM_8:
case TextureFormat::SFLOAT_32_32_32:
case TextureFormat::SFLOAT_16_16_16:
return VK_IMAGE_ASPECT_COLOR_BIT;
/* Special formats, texture only. */
case TextureFormat::SRGB_DXT1:
case TextureFormat::SRGB_DXT3:
case TextureFormat::SRGB_DXT5:
case TextureFormat::SNORM_DXT1:
case TextureFormat::SNORM_DXT3:
case TextureFormat::SNORM_DXT5:
case TextureFormat::SRGBA_8_8_8:
case TextureFormat::UFLOAT_9_9_9_EXP_5:
return VK_IMAGE_ASPECT_COLOR_BIT;
case TextureFormat::Invalid:
BLI_assert_unreachable();
break;
}
BLI_assert_unreachable();
return 0;
}
VkImageAspectFlags to_vk_image_aspect_flag_bits(const GPUFrameBufferBits buffers)
{
VkImageAspectFlags result = 0;
if (buffers & GPU_COLOR_BIT) {
result |= VK_IMAGE_ASPECT_COLOR_BIT;
}
if (buffers & GPU_DEPTH_BIT) {
result |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if (buffers & GPU_STENCIL_BIT) {
result |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
return result;
}
TextureFormat to_gpu_format(const VkFormat format)
{
switch (format) {
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_B8G8R8A8_UNORM:
return TextureFormat::UNORM_8_8_8_8;
case VK_FORMAT_R16G16B16A16_SFLOAT:
return TextureFormat::SFLOAT_16_16_16_16;
default:
BLI_assert_unreachable();
}
return TextureFormat::SFLOAT_32_32_32_32;
}
std::string to_gpu_format_string(VkFormat format)
{
#define CASE(a, b, c, blender_enum, vk_enum, e, f, g, h) \
case VK_FORMAT_##vk_enum: \
return STRINGIFY(blender_enum);
switch (format) {
GPU_TEXTURE_TARGET_FORMAT_EXPAND(CASE)
case VK_FORMAT_UNDEFINED:
return "UNDEFINED";
default:
BLI_assert_unreachable();
break;
}
#undef CASE
return "Invalid";
}
VkFormat to_vk_format(const TextureFormat format)
{
#define CASE(a, b, c, blender_enum, vk_enum, e, f, g, h) \
case TextureFormat::blender_enum: \
return VK_FORMAT_##vk_enum;
switch (format) {
GPU_TEXTURE_FORMAT_EXPAND(CASE)
case TextureFormat::Invalid:
break;
}
#undef CASE
return VK_FORMAT_UNDEFINED;
}
static VkFormat to_vk_format_norm(const GPUVertCompType type, const uint32_t size)
{
switch (type) {
case GPU_COMP_I8:
switch (size) {
case 1:
return VK_FORMAT_R8_SNORM;
case 2:
return VK_FORMAT_R8G8_SNORM;
case 3:
return VK_FORMAT_R8G8B8_SNORM;
case 4:
return VK_FORMAT_R8G8B8A8_SNORM;
case 16:
return VK_FORMAT_R8G8B8A8_SNORM;
default:
BLI_assert_unreachable();
return VK_FORMAT_R8_SNORM;
}
break;
case GPU_COMP_U8:
switch (size) {
case 1:
return VK_FORMAT_R8_UNORM;
case 2:
return VK_FORMAT_R8G8_UNORM;
case 3:
return VK_FORMAT_R8G8B8_UNORM;
case 4:
return VK_FORMAT_R8G8B8A8_UNORM;
case 16:
return VK_FORMAT_R8G8B8A8_UNORM;
default:
BLI_assert_unreachable();
return VK_FORMAT_R8_UNORM;
}
break;
case GPU_COMP_I16:
switch (size) {
case 2:
return VK_FORMAT_R16_SNORM;
case 4:
return VK_FORMAT_R16G16_SNORM;
case 6:
return VK_FORMAT_R16G16B16_SNORM;
case 8:
return VK_FORMAT_R16G16B16A16_SNORM;
default:
BLI_assert_unreachable();
return VK_FORMAT_R16_SNORM;
}
break;
case GPU_COMP_U16:
switch (size) {
case 2:
return VK_FORMAT_R16_UNORM;
case 4:
return VK_FORMAT_R16G16_UNORM;
case 6:
return VK_FORMAT_R16G16B16_UNORM;
case 8:
return VK_FORMAT_R16G16B16A16_UNORM;
default:
BLI_assert_unreachable();
return VK_FORMAT_R16_UNORM;
}
break;
case GPU_COMP_I10:
BLI_assert(size == 4);
return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
case GPU_COMP_I32:
case GPU_COMP_U32:
case GPU_COMP_F32:
default:
break;
}
BLI_assert_unreachable();
return VK_FORMAT_R32_SFLOAT;
}
static VkFormat to_vk_format_float(const GPUVertCompType type, const uint32_t size)
{
switch (type) {
case GPU_COMP_F32:
switch (size) {
case 4:
return VK_FORMAT_R32_SFLOAT;
case 8:
return VK_FORMAT_R32G32_SFLOAT;
case 12:
return VK_FORMAT_R32G32B32_SFLOAT;
case 16:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case 64:
return VK_FORMAT_R32G32B32A32_SFLOAT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R32_SFLOAT;
}
default:
break;
}
BLI_assert_unreachable();
return VK_FORMAT_R32_SFLOAT;
}
static VkFormat to_vk_format_int(const GPUVertCompType type, const uint32_t size)
{
switch (type) {
case GPU_COMP_I8:
switch (size) {
case 1:
return VK_FORMAT_R8_SINT;
case 2:
return VK_FORMAT_R8G8_SINT;
case 3:
return VK_FORMAT_R8G8B8_SINT;
case 4:
return VK_FORMAT_R8G8B8A8_SINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R8_SINT;
}
break;
case GPU_COMP_U8:
switch (size) {
case 1:
return VK_FORMAT_R8_UINT;
case 2:
return VK_FORMAT_R8G8_UINT;
case 3:
return VK_FORMAT_R8G8B8_UINT;
case 4:
return VK_FORMAT_R8G8B8A8_UINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R8_UINT;
}
break;
case GPU_COMP_I16:
switch (size) {
case 2:
return VK_FORMAT_R16_SINT;
case 4:
return VK_FORMAT_R16G16_SINT;
case 6:
return VK_FORMAT_R16G16B16_SINT;
case 8:
return VK_FORMAT_R16G16B16A16_SINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R16_SINT;
}
break;
case GPU_COMP_U16:
switch (size) {
case 2:
return VK_FORMAT_R16_UINT;
case 4:
return VK_FORMAT_R16G16_UINT;
case 6:
return VK_FORMAT_R16G16B16_UINT;
case 8:
return VK_FORMAT_R16G16B16A16_UINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R16_UINT;
}
break;
case GPU_COMP_I32:
switch (size) {
case 4:
return VK_FORMAT_R32_SINT;
case 8:
return VK_FORMAT_R32G32_SINT;
case 12:
return VK_FORMAT_R32G32B32_SINT;
case 16:
return VK_FORMAT_R32G32B32A32_SINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R32_SINT;
}
break;
case GPU_COMP_U32:
switch (size) {
case 4:
return VK_FORMAT_R32_UINT;
case 8:
return VK_FORMAT_R32G32_UINT;
case 12:
return VK_FORMAT_R32G32B32_UINT;
case 16:
return VK_FORMAT_R32G32B32A32_UINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R32_UINT;
}
break;
case GPU_COMP_F32:
switch (size) {
case 4:
return VK_FORMAT_R32_SINT;
case 8:
return VK_FORMAT_R32G32_SINT;
case 12:
return VK_FORMAT_R32G32B32_SINT;
case 16:
return VK_FORMAT_R32G32B32A32_SINT;
default:
BLI_assert_unreachable();
return VK_FORMAT_R32_SINT;
}
break;
case GPU_COMP_I10:
BLI_assert(size == 4);
return VK_FORMAT_A2B10G10R10_SINT_PACK32;
default:
break;
}
BLI_assert_unreachable();
return VK_FORMAT_R32_SFLOAT;
}
VkFormat to_vk_format(const GPUVertCompType type, const uint32_t size, GPUVertFetchMode fetch_mode)
{
switch (fetch_mode) {
case GPU_FETCH_FLOAT:
return to_vk_format_float(type, size);
case GPU_FETCH_INT:
return to_vk_format_int(type, size);
break;
case GPU_FETCH_INT_TO_FLOAT_UNIT:
return to_vk_format_norm(type, size);
break;
default:
break;
}
BLI_assert_unreachable();
return VK_FORMAT_R32_SFLOAT;
}
VkFormat to_vk_format(const shader::Type type)
{
switch (type) {
case shader::Type::float_t:
return VK_FORMAT_R32_SFLOAT;
case shader::Type::float2_t:
return VK_FORMAT_R32G32_SFLOAT;
case shader::Type::float3_t:
return VK_FORMAT_R32G32B32_SFLOAT;
case shader::Type::float4_t:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case shader::Type::uint_t:
return VK_FORMAT_R32_UINT;
case shader::Type::uint2_t:
return VK_FORMAT_R32G32_UINT;
case shader::Type::uint3_t:
return VK_FORMAT_R32G32B32_UINT;
case shader::Type::uint4_t:
return VK_FORMAT_R32G32B32A32_UINT;
case shader::Type::int_t:
return VK_FORMAT_R32_SINT;
case shader::Type::int2_t:
return VK_FORMAT_R32G32_SINT;
case shader::Type::int3_t:
return VK_FORMAT_R32G32B32_SINT;
case shader::Type::int4_t:
return VK_FORMAT_R32G32B32A32_SINT;
case shader::Type::float4x4_t:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case shader::Type::float3x3_t:
case shader::Type::bool_t:
case shader::Type::float3_10_10_10_2_t:
case shader::Type::uchar_t:
case shader::Type::uchar2_t:
case shader::Type::uchar3_t:
case shader::Type::uchar4_t:
case shader::Type::char_t:
case shader::Type::char2_t:
case shader::Type::char3_t:
case shader::Type::char4_t:
case shader::Type::short_t:
case shader::Type::short2_t:
case shader::Type::short3_t:
case shader::Type::short4_t:
case shader::Type::ushort_t:
case shader::Type::ushort2_t:
case shader::Type::ushort3_t:
case shader::Type::ushort4_t:
break;
}
BLI_assert_unreachable();
return VK_FORMAT_R32G32B32A32_SFLOAT;
}
VkQueryType to_vk_query_type(const GPUQueryType query_type)
{
switch (query_type) {
case GPU_QUERY_OCCLUSION:
return VK_QUERY_TYPE_OCCLUSION;
}
BLI_assert_unreachable();
return VK_QUERY_TYPE_OCCLUSION;
}
VkImageType to_vk_image_type(const GPUTextureType type)
{
/* See
* https://vulkan.lunarg.com/doc/view/1.3.243.0/linux/1.3-extensions/vkspec.html#resources-image-views-compatibility
* for reference */
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_BUFFER:
case GPU_TEXTURE_1D_ARRAY:
return VK_IMAGE_TYPE_1D;
case GPU_TEXTURE_2D:
case GPU_TEXTURE_2D_ARRAY:
case GPU_TEXTURE_CUBE:
case GPU_TEXTURE_CUBE_ARRAY:
return VK_IMAGE_TYPE_2D;
case GPU_TEXTURE_3D:
return VK_IMAGE_TYPE_3D;
case GPU_TEXTURE_ARRAY:
/* GPU_TEXTURE_ARRAY should always be used together with 1D, 2D, or CUBE. */
break;
}
BLI_assert_unreachable();
return VK_IMAGE_TYPE_1D;
}
VkImageViewType to_vk_image_view_type(const GPUTextureType type,
const eImageViewUsage view_type,
VKImageViewArrayed arrayed)
{
VkImageViewType result = VK_IMAGE_VIEW_TYPE_1D;
switch (type) {
case GPU_TEXTURE_1D:
case GPU_TEXTURE_BUFFER:
result = VK_IMAGE_VIEW_TYPE_1D;
break;
case GPU_TEXTURE_2D:
result = VK_IMAGE_VIEW_TYPE_2D;
break;
case GPU_TEXTURE_3D:
result = VK_IMAGE_VIEW_TYPE_3D;
break;
case GPU_TEXTURE_CUBE:
result = view_type == eImageViewUsage::Attachment ? VK_IMAGE_VIEW_TYPE_2D_ARRAY :
VK_IMAGE_VIEW_TYPE_CUBE;
break;
case GPU_TEXTURE_1D_ARRAY:
result = VK_IMAGE_VIEW_TYPE_1D_ARRAY;
break;
case GPU_TEXTURE_2D_ARRAY:
result = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
break;
case GPU_TEXTURE_CUBE_ARRAY:
result = view_type == eImageViewUsage::Attachment ? VK_IMAGE_VIEW_TYPE_2D_ARRAY :
VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
break;
case GPU_TEXTURE_ARRAY:
/* GPU_TEXTURE_ARRAY should always be used together with 1D, 2D, or CUBE. */
break;
}
if (arrayed == VKImageViewArrayed::NOT_ARRAYED) {
if (result == VK_IMAGE_VIEW_TYPE_1D_ARRAY) {
result = VK_IMAGE_VIEW_TYPE_1D;
}
else if (result == VK_IMAGE_VIEW_TYPE_2D_ARRAY) {
result = VK_IMAGE_VIEW_TYPE_2D;
}
else if (result == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) {
result = VK_IMAGE_VIEW_TYPE_CUBE;
}
}
else if (arrayed == VKImageViewArrayed::ARRAYED) {
if (result == VK_IMAGE_VIEW_TYPE_1D) {
result = VK_IMAGE_VIEW_TYPE_1D_ARRAY;
}
else if (result == VK_IMAGE_VIEW_TYPE_2D) {
result = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
}
else if (result == VK_IMAGE_VIEW_TYPE_CUBE) {
result = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
}
}
return result;
}
VkComponentSwizzle to_vk_component_swizzle(const char swizzle)
{
switch (swizzle) {
case '0':
return VK_COMPONENT_SWIZZLE_ZERO;
case '1':
return VK_COMPONENT_SWIZZLE_ONE;
case 'r':
return VK_COMPONENT_SWIZZLE_R;
case 'g':
return VK_COMPONENT_SWIZZLE_G;
case 'b':
return VK_COMPONENT_SWIZZLE_B;
case 'a':
return VK_COMPONENT_SWIZZLE_A;
default:
break;
}
BLI_assert_unreachable();
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const double4 data)
{
VkClearColorValue result = {{0.0f}};
switch (format) {
/* All float-like formats (i.e. everything except literal int/uint) go
* into VkClearColorValue float color fields. */
case GPU_DATA_FLOAT:
case GPU_DATA_HALF_FLOAT:
case GPU_DATA_UBYTE:
case GPU_DATA_10_11_11_REV:
case GPU_DATA_2_10_10_10_REV: {
result.float32[0] = float(data[0]);
result.float32[1] = float(data[1]);
result.float32[2] = float(data[2]);
result.float32[3] = float(data[3]);
break;
}
case GPU_DATA_INT: {
result.int32[0] = int32_t(data[0]);
result.int32[1] = int32_t(data[1]);
result.int32[2] = int32_t(data[2]);
result.int32[3] = int32_t(data[3]);
break;
}
case GPU_DATA_UINT: {
result.uint32[0] = uint32_t(data[0]);
result.uint32[1] = uint32_t(data[1]);
result.uint32[2] = uint32_t(data[2]);
result.uint32[3] = uint32_t(data[3]);
break;
}
case GPU_DATA_UINT_24_8_DEPRECATED: {
BLI_assert_unreachable();
break;
}
}
return result;
}
VkIndexType to_vk_index_type(const GPUIndexBufType index_type)
{
switch (index_type) {
case GPU_INDEX_U16:
return VK_INDEX_TYPE_UINT16;
case GPU_INDEX_U32:
return VK_INDEX_TYPE_UINT32;
default:
break;
}
BLI_assert_unreachable();
return VK_INDEX_TYPE_UINT16;
}
VkPrimitiveTopology to_vk_primitive_topology(const GPUPrimType prim_type)
{
switch (prim_type) {
case GPU_PRIM_POINTS:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case GPU_PRIM_LINES:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case GPU_PRIM_TRIS:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case GPU_PRIM_LINE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case GPU_PRIM_LINE_LOOP:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case GPU_PRIM_TRI_STRIP:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case GPU_PRIM_TRI_FAN:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
case GPU_PRIM_LINES_ADJ:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
case GPU_PRIM_TRIS_ADJ:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
case GPU_PRIM_LINE_STRIP_ADJ:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
case GPU_PRIM_NONE:
break;
}
BLI_assert_unreachable();
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
}
VkCullModeFlags to_vk_cull_mode_flags(const GPUFaceCullTest cull_test)
{
switch (cull_test) {
case GPU_CULL_FRONT:
return VK_CULL_MODE_FRONT_BIT;
case GPU_CULL_BACK:
return VK_CULL_MODE_BACK_BIT;
case GPU_CULL_NONE:
return VK_CULL_MODE_NONE;
}
BLI_assert_unreachable();
return VK_CULL_MODE_NONE;
}
VkSamplerAddressMode to_vk_sampler_address_mode(const GPUSamplerExtendMode extend_mode)
{
switch (extend_mode) {
case GPU_SAMPLER_EXTEND_MODE_EXTEND:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case GPU_SAMPLER_EXTEND_MODE_REPEAT:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case GPU_SAMPLER_EXTEND_MODE_MIRRORED_REPEAT:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
}
BLI_assert_unreachable();
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
static VkDescriptorType to_vk_descriptor_type_image(const shader::ImageType &image_type)
{
switch (image_type) {
case shader::ImageType::Float1D:
case shader::ImageType::Float1DArray:
case shader::ImageType::Float2D:
case shader::ImageType::Float2DArray:
case shader::ImageType::Float3D:
case shader::ImageType::FloatCube:
case shader::ImageType::FloatCubeArray:
case shader::ImageType::Int1D:
case shader::ImageType::Int1DArray:
case shader::ImageType::Int2D:
case shader::ImageType::Int2DArray:
case shader::ImageType::Int3D:
case shader::ImageType::IntCube:
case shader::ImageType::IntCubeArray:
case shader::ImageType::AtomicInt2D:
case shader::ImageType::AtomicInt2DArray:
case shader::ImageType::AtomicInt3D:
case shader::ImageType::Uint1D:
case shader::ImageType::Uint1DArray:
case shader::ImageType::Uint2D:
case shader::ImageType::Uint2DArray:
case shader::ImageType::Uint3D:
case shader::ImageType::UintCube:
case shader::ImageType::UintCubeArray:
case shader::ImageType::AtomicUint2D:
case shader::ImageType::AtomicUint2DArray:
case shader::ImageType::AtomicUint3D:
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
case shader::ImageType::FloatBuffer:
case shader::ImageType::IntBuffer:
case shader::ImageType::UintBuffer:
return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
default:
BLI_assert_msg(false, "ImageType not supported.");
}
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
}
static VkDescriptorType to_vk_descriptor_type_sampler(const shader::ImageType &image_type)
{
switch (image_type) {
case shader::ImageType::undefined:
case shader::ImageType::Float1D:
case shader::ImageType::Float1DArray:
case shader::ImageType::Float2D:
case shader::ImageType::Float2DArray:
case shader::ImageType::Float3D:
case shader::ImageType::FloatCube:
case shader::ImageType::FloatCubeArray:
case shader::ImageType::Int1D:
case shader::ImageType::Int1DArray:
case shader::ImageType::Int2D:
case shader::ImageType::Int2DArray:
case shader::ImageType::Int3D:
case shader::ImageType::IntCube:
case shader::ImageType::IntCubeArray:
case shader::ImageType::AtomicInt2D:
case shader::ImageType::AtomicInt2DArray:
case shader::ImageType::AtomicInt3D:
case shader::ImageType::Uint1D:
case shader::ImageType::Uint1DArray:
case shader::ImageType::Uint2D:
case shader::ImageType::Uint2DArray:
case shader::ImageType::Uint3D:
case shader::ImageType::UintCube:
case shader::ImageType::UintCubeArray:
case shader::ImageType::AtomicUint2D:
case shader::ImageType::AtomicUint2DArray:
case shader::ImageType::AtomicUint3D:
case shader::ImageType::Shadow2D:
case shader::ImageType::Shadow2DArray:
case shader::ImageType::ShadowCube:
case shader::ImageType::ShadowCubeArray:
case shader::ImageType::Depth2D:
case shader::ImageType::Depth2DArray:
case shader::ImageType::DepthCube:
case shader::ImageType::DepthCubeArray:
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
case shader::ImageType::FloatBuffer:
case shader::ImageType::IntBuffer:
case shader::ImageType::UintBuffer:
return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
}
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
}
VkDescriptorType to_vk_descriptor_type(const shader::ShaderCreateInfo::Resource &resource)
{
switch (resource.bind_type) {
case shader::ShaderCreateInfo::Resource::BindType::IMAGE:
return to_vk_descriptor_type_image(resource.image.type);
case shader::ShaderCreateInfo::Resource::BindType::SAMPLER:
return to_vk_descriptor_type_sampler(resource.sampler.type);
case shader::ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case shader::ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
}
BLI_assert_unreachable();
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
}
VkImageCreateFlags to_vk_image_create(const GPUTextureType texture_type,
const GPUTextureFormatFlag format_flag,
const eGPUTextureUsage usage)
{
VkImageCreateFlags result = 0;
if (ELEM(texture_type, GPU_TEXTURE_CUBE, GPU_TEXTURE_CUBE_ARRAY)) {
result |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
}
/* sRGB textures needs to be mutable as they can be used as non-sRGB frame-buffer attachments. */
if (usage & GPU_TEXTURE_USAGE_ATTACHMENT && format_flag & GPU_FORMAT_SRGB) {
result |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
}
return result;
}
VkImageUsageFlags to_vk_image_usage(const eGPUTextureUsage usage,
const GPUTextureFormatFlag format_flag,
bool use_image_host_copy)
{
const VKDevice &device = VKBackend::get().device;
const VKExtensions &extensions = device.extensions_get();
VkImageUsageFlags result = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if (usage & GPU_TEXTURE_USAGE_SHADER_READ) {
result |= VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (usage & GPU_TEXTURE_USAGE_SHADER_WRITE) {
result |= VK_IMAGE_USAGE_STORAGE_BIT;
}
if (usage & GPU_TEXTURE_USAGE_ATTACHMENT) {
if (format_flag & GPU_FORMAT_COMPRESSED) {
/* These formats aren't supported as an attachment. When using GPU_TEXTURE_USAGE_DEFAULT they
* are still being evaluated to be attachable. So we need to skip them. */
}
else {
if (format_flag & (GPU_FORMAT_DEPTH | GPU_FORMAT_STENCIL)) {
result |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
else {
result |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
result |= extensions.dynamic_rendering_local_read ? VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT :
VK_IMAGE_USAGE_SAMPLED_BIT;
}
}
}
if (usage & GPU_TEXTURE_USAGE_HOST_READ) {
result |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (use_image_host_copy) {
result |= VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT;
}
/* Disable some usages based on the given format flag to support more devices. */
if (format_flag & GPU_FORMAT_SRGB) {
/* NVIDIA devices don't create SRGB textures when it storage bit is set. */
result &= ~VK_IMAGE_USAGE_STORAGE_BIT;
}
if (format_flag & (GPU_FORMAT_DEPTH | GPU_FORMAT_STENCIL)) {
/* NVIDIA devices don't create depth textures when it storage bit is set. */
result &= ~VK_IMAGE_USAGE_STORAGE_BIT;
}
return result;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,164 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <typeinfo>
#ifdef _WIN32
# include "BLI_winstuff.h"
#endif
#include <vulkan/vulkan.h>
#ifdef _WIN32
# include <vulkan/vulkan_win32.h>
#endif
#define VMA_VULKAN_VERSION 1002000 // Vulkan 1.2
#if !defined(_WIN32) or defined(_M_ARM64)
/* Silence compilation warning on non-windows x64 systems. */
# define VMA_EXTERNAL_MEMORY_WIN32 0
#endif
#include "vk_mem_alloc.h"
#include "GPU_index_buffer.hh"
#include "GPU_state.hh"
#include "gpu_query.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_texture_private.hh"
namespace blender::gpu {
using TimelineValue = uint64_t;
/**
* Based on the usage of an Image View a different image view type should be created.
*
* When using a GPU_TEXTURE_CUBE as an frame buffer attachment it will be used as a
* GPU_TEXTURE_2D_ARRAY. eg only a single side of the cube map will be attached. But when bound as
* a shader resource the cube-map will be used.
*/
enum class eImageViewUsage {
/** Image View will be used as a bindable shader resource. */
ShaderBinding,
/** Image View will be used as an frame-buffer attachment. */
Attachment,
};
enum class VKImageViewArrayed {
DONT_CARE,
NOT_ARRAYED,
ARRAYED,
};
struct VKSubImageRange {
uint32_t mipmap_level = 0;
uint32_t mipmap_count = VK_REMAINING_MIP_LEVELS;
uint32_t layer_base = 0;
uint32_t layer_count = VK_REMAINING_ARRAY_LAYERS;
};
VkImageAspectFlags to_vk_image_aspect_flag_bits(const TextureFormat format);
VkImageAspectFlags to_vk_image_aspect_flag_bits(const GPUFrameBufferBits buffers);
VkFormat to_vk_format(const TextureFormat format);
BLI_INLINE VkFormat to_vk_format(const TextureTargetFormat format)
{
return to_vk_format(to_texture_format(format));
}
TextureFormat to_gpu_format(const VkFormat format);
std::string to_gpu_format_string(VkFormat format);
VkFormat to_vk_format(const GPUVertCompType type,
const uint32_t size,
const GPUVertFetchMode fetch_mode);
VkFormat to_vk_format(const shader::Type type);
VkQueryType to_vk_query_type(const GPUQueryType query_type);
VkComponentSwizzle to_vk_component_swizzle(const char swizzle);
VkImageViewType to_vk_image_view_type(const GPUTextureType type,
eImageViewUsage view_type,
VKImageViewArrayed arrayed);
VkImageType to_vk_image_type(const GPUTextureType type);
VkClearColorValue to_vk_clear_color_value(const eGPUDataFormat format, const double4 data);
VkIndexType to_vk_index_type(const GPUIndexBufType index_type);
VkPrimitiveTopology to_vk_primitive_topology(const GPUPrimType prim_type);
VkCullModeFlags to_vk_cull_mode_flags(const GPUFaceCullTest cull_test);
VkSamplerAddressMode to_vk_sampler_address_mode(const GPUSamplerExtendMode extend_mode);
VkDescriptorType to_vk_descriptor_type(const shader::ShaderCreateInfo::Resource &resource);
VkImageCreateFlags to_vk_image_create(const GPUTextureType texture_type,
const GPUTextureFormatFlag format_flag,
const eGPUTextureUsage usage);
VkImageUsageFlags to_vk_image_usage(const eGPUTextureUsage usage,
const GPUTextureFormatFlag format_flag,
bool use_image_host_copy);
template<typename T> VkObjectType to_vk_object_type(T /*vk_obj*/)
{
const std::type_info &tid = typeid(T);
#define VK_EQ_TYPEID(name, name2) \
if (tid == typeid(name)) { \
return VK_OBJECT_TYPE_##name2; \
}
VK_EQ_TYPEID(VkInstance, INSTANCE);
VK_EQ_TYPEID(VkPhysicalDevice, PHYSICAL_DEVICE);
VK_EQ_TYPEID(VkDevice, DEVICE);
VK_EQ_TYPEID(VkQueue, QUEUE);
VK_EQ_TYPEID(VkSemaphore, SEMAPHORE);
VK_EQ_TYPEID(VkCommandBuffer, COMMAND_BUFFER);
VK_EQ_TYPEID(VkFence, FENCE);
VK_EQ_TYPEID(VkDeviceMemory, DEVICE_MEMORY);
VK_EQ_TYPEID(VkBuffer, BUFFER);
VK_EQ_TYPEID(VkImage, IMAGE);
VK_EQ_TYPEID(VkEvent, EVENT);
VK_EQ_TYPEID(VkQueryPool, QUERY_POOL);
VK_EQ_TYPEID(VkBufferView, BUFFER_VIEW);
VK_EQ_TYPEID(VkImageView, IMAGE_VIEW);
VK_EQ_TYPEID(VkShaderModule, SHADER_MODULE);
VK_EQ_TYPEID(VkPipelineCache, PIPELINE_CACHE);
VK_EQ_TYPEID(VkPipelineLayout, PIPELINE_LAYOUT);
VK_EQ_TYPEID(VkRenderPass, RENDER_PASS);
VK_EQ_TYPEID(VkPipeline, PIPELINE);
VK_EQ_TYPEID(VkDescriptorSetLayout, DESCRIPTOR_SET_LAYOUT);
VK_EQ_TYPEID(VkSampler, SAMPLER);
VK_EQ_TYPEID(VkDescriptorPool, DESCRIPTOR_POOL);
VK_EQ_TYPEID(VkDescriptorSet, DESCRIPTOR_SET);
VK_EQ_TYPEID(VkFramebuffer, FRAMEBUFFER);
VK_EQ_TYPEID(VkCommandPool, COMMAND_POOL);
VK_EQ_TYPEID(VkSamplerYcbcrConversion, SAMPLER_YCBCR_CONVERSION);
VK_EQ_TYPEID(VkDescriptorUpdateTemplate, DESCRIPTOR_UPDATE_TEMPLATE);
VK_EQ_TYPEID(VkSurfaceKHR, SURFACE_KHR);
VK_EQ_TYPEID(VkSwapchainKHR, SWAPCHAIN_KHR);
VK_EQ_TYPEID(VkDisplayKHR, DISPLAY_KHR);
VK_EQ_TYPEID(VkDisplayModeKHR, DISPLAY_MODE_KHR);
VK_EQ_TYPEID(VkDebugReportCallbackEXT, DEBUG_REPORT_CALLBACK_EXT);
#ifdef VK_ENABLE_BETA_EXTENSIONS
VK_EQ_TYPEID(VkVideoSessionKHR, VIDEO_SESSION_KHR);
#endif
#ifdef VK_ENABLE_BETA_EXTENSIONS
VK_EQ_TYPEID(VkVideoSessionParametersKHR, VIDEO_SESSION_PARAMETERS_KHR);
#endif
VK_EQ_TYPEID(VkCuModuleNVX, CU_MODULE_NVX);
VK_EQ_TYPEID(VkCuFunctionNVX, CU_FUNCTION_NVX);
VK_EQ_TYPEID(VkDebugUtilsMessengerEXT, DEBUG_UTILS_MESSENGER_EXT);
VK_EQ_TYPEID(VkAccelerationStructureKHR, ACCELERATION_STRUCTURE_KHR);
VK_EQ_TYPEID(VkValidationCacheEXT, VALIDATION_CACHE_EXT);
VK_EQ_TYPEID(VkAccelerationStructureNV, ACCELERATION_STRUCTURE_NV);
VK_EQ_TYPEID(VkPerformanceConfigurationINTEL, PERFORMANCE_CONFIGURATION_INTEL);
VK_EQ_TYPEID(VkDeferredOperationKHR, DEFERRED_OPERATION_KHR);
VK_EQ_TYPEID(VkIndirectCommandsLayoutNV, INDIRECT_COMMANDS_LAYOUT_NV);
VK_EQ_TYPEID(VkPrivateDataSlotEXT, PRIVATE_DATA_SLOT_EXT);
BLI_assert_unreachable();
#undef VK_EQ_TYPEID
return VK_OBJECT_TYPE_UNKNOWN;
}
#define NOT_YET_IMPLEMENTED \
printf("%s:%d `%s` not implemented yet\n", __FILE__, __LINE__, __func__);
} // namespace blender::gpu

View File

@@ -0,0 +1,660 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "DNA_userdef_types.h"
#include "GPU_debug.hh"
#include "gpu_capabilities_private.hh"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_debug.hh"
#include "vk_framebuffer.hh"
#include "vk_immediate.hh"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "vk_state_manager.hh"
#include "vk_texture.hh"
#include "vk_vertex_attribute_object.hh"
namespace blender::gpu {
VKContext::VKContext(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context)
: push_constants_pool(VKBufferPool("PushConstants",
64 * 1024,
VKBackend::get()
.device.physical_device_properties_get()
.limits.minUniformBufferOffsetAlignment,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
0.8f))
{
ghost_window_ = ghost_window;
ghost_context_ = ghost_context;
state_manager = new VKStateManager();
imm = new VKImmediate();
back_left = new VKFrameBuffer("back_left");
front_left = new VKFrameBuffer("front_left");
active_fb = back_left;
}
VKContext::~VKContext()
{
if (surface_texture_) {
back_left->attachment_remove(GPU_FB_COLOR_ATTACHMENT0);
front_left->attachment_remove(GPU_FB_COLOR_ATTACHMENT0);
GPU_texture_free(surface_texture_);
surface_texture_ = nullptr;
}
free_resources();
delete imm;
imm = nullptr;
VKDevice &device = VKBackend::get().device;
device.context_unregister(*this);
this->process_frame_timings();
}
void VKContext::sync_backbuffer()
{
if (ghost_window_) {
GHOST_VulkanSwapChainData swap_chain_data = {};
ghost_window_->getVulkanSwapChainFormat(&swap_chain_data);
const bool reset_framebuffer = swap_chain_format_.format !=
swap_chain_data.surface_format.format ||
swap_chain_format_.colorSpace !=
swap_chain_data.surface_format.colorSpace ||
vk_extent_.width != swap_chain_data.extent.width ||
vk_extent_.height != swap_chain_data.extent.height;
if (reset_framebuffer) {
if (has_active_framebuffer()) {
deactivate_framebuffer();
}
if (surface_texture_) {
GPU_texture_free(surface_texture_);
surface_texture_ = nullptr;
}
vk_extent_ = swap_chain_data.extent;
vk_extent_.width = max_uu(vk_extent_.width, 1u);
vk_extent_.height = max_uu(vk_extent_.height, 1u);
surface_texture_ = GPU_texture_create_2d(
"back-left",
vk_extent_.width,
vk_extent_.height,
1,
to_gpu_format(swap_chain_data.surface_format.format),
GPU_TEXTURE_USAGE_ATTACHMENT | GPU_TEXTURE_USAGE_SHADER_READ,
nullptr);
back_left->attachment_set(GPU_FB_COLOR_ATTACHMENT0,
GPU_ATTACHMENT_TEXTURE(surface_texture_));
front_left->attachment_set(GPU_FB_COLOR_ATTACHMENT0,
GPU_ATTACHMENT_TEXTURE(surface_texture_));
back_left->bind(false);
swap_chain_format_ = swap_chain_data.surface_format;
GCaps.hdr_viewport_support = (swap_chain_format_.format == VK_FORMAT_R16G16B16A16_SFLOAT) &&
ELEM(swap_chain_format_.colorSpace,
VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT,
VK_COLOR_SPACE_PASS_THROUGH_EXT);
}
}
}
void VKContext::activate()
{
/* Make sure no other context is already bound to this thread. */
BLI_assert(is_active_ == false);
VKDevice &device = VKBackend::get().device;
VKThreadData &thread_data = device.current_thread_data();
thread_data_ = std::reference_wrapper<VKThreadData>(thread_data);
if (!render_graph_.has_value()) {
render_graph_ = std::reference_wrapper<render_graph::VKRenderGraph>(
*device.render_graph_new());
/* Recreate the debug group stack for the new graph.
* Note: there is no associated `debug_group_end` as the graph groups
* are implicitly closed on submission. */
for (const StringRef &group : debug_stack) {
std::string str_group = group;
render_graph_.value().get().debug_group_begin(str_group.c_str(),
debug::get_debug_group_color(str_group));
}
}
is_active_ = true;
sync_backbuffer();
immActivate();
}
void VKContext::deactivate()
{
flush_render_graph(RenderGraphFlushFlags(0));
immDeactivate();
thread_data_.reset();
is_active_ = false;
}
void VKContext::begin_frame() {}
void VKContext::end_frame()
{
this->process_frame_timings();
}
void VKContext::flush()
{
/* Submit when flushing to avoid out-of-memory errors and TDRs when more and more commands are
* added in background mode without ever submitting work to the GPU. */
flush_render_graph(RenderGraphFlushFlags::SUBMIT | RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
}
TimelineValue VKContext::flush_render_graph(RenderGraphFlushFlags flags,
VkPipelineStageFlags wait_dst_stage_mask,
VkSemaphore wait_semaphore,
VkSemaphore signal_semaphore,
VkFence signal_fence)
{
if (has_active_framebuffer()) {
VKFrameBuffer &framebuffer = *active_framebuffer_get();
if (framebuffer.is_rendering()) {
framebuffer.rendering_end(*this);
}
}
VKDevice &device = VKBackend::get().device;
push_constants_pool.ensure_uploaded();
push_constants_pool.discard();
descriptor_set_get().upload_descriptor_sets();
TimelineValue timeline = device.render_graph_submit(
&render_graph_.value().get(),
discard_pool,
bool(flags & RenderGraphFlushFlags::SUBMIT),
bool(flags & RenderGraphFlushFlags::WAIT_FOR_SUBMISSION),
bool(flags & RenderGraphFlushFlags::WAIT_FOR_COMPLETION),
wait_dst_stage_mask,
wait_semaphore,
signal_semaphore,
signal_fence);
render_graph_.reset();
streaming_buffers_.clear();
if (bool(flags & RenderGraphFlushFlags::RENEW_RENDER_GRAPH)) {
render_graph_ = std::reference_wrapper<render_graph::VKRenderGraph>(
*device.render_graph_new());
/* Recreate the debug group stack for the new graph.
* Note: there is no associated `debug_group_end` as the graph groups
* are implicitly closed on submission. */
for (const StringRef &group : debug_stack) {
std::string str_group = group;
render_graph_.value().get().debug_group_begin(str_group.c_str(),
debug::get_debug_group_color(str_group));
}
}
return timeline;
}
void VKContext::finish()
{
flush_render_graph(RenderGraphFlushFlags::SUBMIT | RenderGraphFlushFlags::WAIT_FOR_COMPLETION |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
}
void VKContext::memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb)
{
const VKDevice &device = VKBackend::get().device;
device.memory_statistics_get(r_total_mem_kb, r_free_mem_kb);
}
/* -------------------------------------------------------------------- */
/** \name State manager
* \{ */
VKDescriptorPools &VKContext::descriptor_pools_get()
{
return thread_data_.value().get().descriptor_pools;
}
VKDescriptorSetTracker &VKContext::descriptor_set_get()
{
return thread_data_.value().get().descriptor_set;
}
VKStateManager &VKContext::state_manager_get() const
{
return *static_cast<VKStateManager *>(state_manager);
}
void VKContext::debug_unbind_all_ubo()
{
state_manager_get().uniform_buffer_unbind_all();
};
void VKContext::debug_unbind_all_ssbo()
{
state_manager_get().storage_buffer_unbind_all();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Frame-buffer
* \{ */
void VKContext::activate_framebuffer(VKFrameBuffer &framebuffer)
{
if (has_active_framebuffer()) {
deactivate_framebuffer();
}
BLI_assert(active_fb == nullptr);
active_fb = &framebuffer;
framebuffer.update_size();
framebuffer.update_srgb();
framebuffer.rendering_reset();
}
VKFrameBuffer *VKContext::active_framebuffer_get() const
{
return unwrap(active_fb);
}
bool VKContext::has_active_framebuffer() const
{
return active_framebuffer_get() != nullptr;
}
void VKContext::deactivate_framebuffer()
{
VKFrameBuffer *framebuffer = active_framebuffer_get();
BLI_assert(framebuffer != nullptr);
if (framebuffer->is_rendering()) {
framebuffer->rendering_end(*this);
}
active_fb = nullptr;
}
void VKContext::rendering_end()
{
VKFrameBuffer *framebuffer = active_framebuffer_get();
if (framebuffer) {
framebuffer->rendering_end(*this);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Pipeline
* \{ */
void VKContext::update_pipeline_data(const VKFrameBuffer &framebuffer,
GPUPrimType primitive,
VKVertexAttributeObject &vao,
render_graph::VKPipelineDataGraphics &r_pipeline_data)
{
VKShader &vk_shader = unwrap(*shader);
VKStateManager &state_manager = state_manager_get();
/* Disable non-vulkan state flags to reduce unneeded pipeline compilation. */
state_manager.state.clip_control = 0;
framebuffer.vk_viewports_append(r_pipeline_data.viewport.viewports);
framebuffer.vk_render_areas_append(r_pipeline_data.viewport.scissors);
/* Override size of point shader when GPU_point size < 0 */
const float point_size = state_manager.mutable_state.point_size;
if (primitive == GPU_PRIM_POINTS && point_size < 0.0) {
GPU_shader_uniform_1f(shader, "size", -point_size);
}
VKDevice &device = VKBackend::get().device;
const VKExtensions &extensions = device.extensions_get();
/* Dynamic state line width */
const bool is_line_primitive = ELEM(primitive,
GPU_PRIM_LINES,
GPU_PRIM_LINE_LOOP,
GPU_PRIM_LINE_STRIP,
GPU_PRIM_LINES_ADJ,
GPU_PRIM_LINE_STRIP_ADJ);
if (is_line_primitive) {
r_pipeline_data.line_width = extensions.wide_lines ? state_manager.mutable_state.line_width :
1.0f;
}
else {
r_pipeline_data.line_width.reset();
}
/* Dynamic state stencil state */
if (framebuffer.stencil_attachment_format_get() != VK_FORMAT_UNDEFINED &&
state_manager.state.stencil_test != GPU_STENCIL_NONE)
{
r_pipeline_data.stencil_state = {state_manager.mutable_state.stencil_compare_mask,
state_manager.mutable_state.stencil_reference,
state_manager.mutable_state.stencil_write_mask};
}
else {
r_pipeline_data.stencil_state.reset();
}
/* VK_EXT_extended_dynamic_state */
if (extensions.extended_dynamic_state) {
r_pipeline_data.front_face = state_manager.state.invert_facing ?
VK_FRONT_FACE_COUNTER_CLOCKWISE :
VK_FRONT_FACE_CLOCKWISE;
}
VKVertexInputDescriptionPool::Key vertex_input_description_key =
device.vertex_input_descriptions.get_or_insert(vao.vertex_input);
if (extensions.vertex_input_dynamic_state) {
r_pipeline_data.vertex_input_description = vertex_input_description_key;
}
update_pipeline_data(
vk_shader,
vk_shader.ensure_and_get_graphics_pipeline(
primitive, vertex_input_description_key, state_manager, framebuffer, constants_state_),
r_pipeline_data.pipeline_data);
}
void VKContext::update_pipeline_data(render_graph::VKPipelineData &r_pipeline_data)
{
VKShader &vk_shader = unwrap(*shader);
update_pipeline_data(
vk_shader, vk_shader.ensure_and_get_compute_pipeline(constants_state_), r_pipeline_data);
}
void VKContext::update_pipeline_data(VKShader &vk_shader,
VkPipeline vk_pipeline,
render_graph::VKPipelineData &r_pipeline_data)
{
r_pipeline_data.vk_pipeline_layout = vk_shader.vk_pipeline_layout;
r_pipeline_data.vk_pipeline = vk_pipeline;
/* Update push constants. */
r_pipeline_data.push_constants_range = IndexRange::from_begin_size(0, 0);
const VKPushConstants::Layout &push_constants_layout =
vk_shader.interface_get().push_constants_layout_get();
if (push_constants_layout.storage_type_get() == VKPushConstants::StorageType::PUSH_CONSTANTS) {
r_pipeline_data.push_constants_range = render_graph().copy_push_constants(
Span<uint8_t>(static_cast<const uint8_t *>(vk_shader.push_constants.data()),
push_constants_layout.size_in_bytes()));
}
/* Update descriptor set. */
r_pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
if (vk_shader.has_descriptor_set()) {
VKDescriptorSetTracker &descriptor_set = descriptor_set_get();
descriptor_set.update_descriptor_set(*this, access_info_, r_pipeline_data);
}
}
render_graph::VKResourceAccessInfo &VKContext::reset_and_get_access_info()
{
access_info_.reset();
return access_info_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Graphics pipeline
* \{ */
void VKContext::swap_buffer_acquired_callback()
{
VKContext *context = VKContext::get();
BLI_assert(context);
context->swap_buffer_acquired_handler();
}
void VKContext::swap_buffer_draw_callback(const GHOST_VulkanSwapChainData *swap_chain_data,
bool wait_for_submission)
{
VKContext *context = VKContext::get();
BLI_assert(context);
context->swap_buffer_draw_handler(*swap_chain_data, wait_for_submission);
}
void VKContext::swap_buffer_acquired_handler()
{
sync_backbuffer();
}
void VKContext::swap_buffer_draw_handler(const GHOST_VulkanSwapChainData &swap_chain_data,
bool wait_for_submission)
{
const bool do_blit_to_swapchain = swap_chain_data.image != VK_NULL_HANDLE;
const bool use_shader = ELEM(swap_chain_data.surface_format.colorSpace,
VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT,
VK_COLOR_SPACE_PASS_THROUGH_EXT);
/* When swapchain is invalid/minimized we only flush the render graph to free GPU resources. */
if (!do_blit_to_swapchain) {
flush_render_graph(RenderGraphFlushFlags::SUBMIT | RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
return;
}
VKDevice &device = VKBackend::get().device;
render_graph::VKRenderGraph &render_graph = this->render_graph();
VKFrameBuffer &framebuffer = *unwrap(active_fb);
framebuffer.rendering_end(*this);
VKTexture *color_attachment = unwrap(unwrap(framebuffer.color_tex(0)));
device.resources.add_swapchain_image(swap_chain_data.image, "SwapchainImage");
GPU_debug_group_begin("BackBuffer.Blit");
if (use_shader) {
VKTexture swap_chain_texture("swap_chain_texture");
swap_chain_texture.init_swapchain(swap_chain_data.image,
to_gpu_format(swap_chain_data.surface_format.format));
Shader *shader = device.vk_backbuffer_blit_sh_get();
GPU_shader_bind(shader);
GPU_shader_uniform_1f(shader, "sdr_scale", swap_chain_data.sdr_scale);
VKStateManager &state_manager = state_manager_get();
state_manager.image_bind(color_attachment, 0);
state_manager.image_bind(&swap_chain_texture, 1);
int2 dispatch_size = math::divide_ceil(
int2(swap_chain_data.extent.width, swap_chain_data.extent.height), int2(16));
VKBackend::get().compute_dispatch(UNPACK2(dispatch_size), 1);
}
else {
render_graph::VKBlitImageNode::CreateInfo blit_image = {};
blit_image.src_image = color_attachment->vk_image_handle();
blit_image.dst_image = swap_chain_data.image;
blit_image.filter = VK_FILTER_LINEAR;
VkImageBlit &region = blit_image.region;
region.srcOffsets[0] = {0, 0, 0};
region.srcOffsets[1] = {color_attachment->width_get(), color_attachment->height_get(), 1};
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.mipLevel = 0;
region.srcSubresource.baseArrayLayer = 0;
region.srcSubresource.layerCount = 1;
region.dstOffsets[0] = {0, int32_t(swap_chain_data.extent.height), 0};
region.dstOffsets[1] = {int32_t(swap_chain_data.extent.width), 0, 1};
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.dstSubresource.mipLevel = 0;
region.dstSubresource.baseArrayLayer = 0;
region.dstSubresource.layerCount = 1;
render_graph.add_node(blit_image);
}
render_graph::VKSynchronizationNode::CreateInfo synchronization = {};
synchronization.vk_image = swap_chain_data.image;
synchronization.vk_image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
synchronization.vk_image_aspect = VK_IMAGE_ASPECT_COLOR_BIT;
render_graph.add_node(synchronization);
GPU_debug_group_end();
wait_for_submission |= swap_chain_data.submission_fence != VK_NULL_HANDLE;
flush_render_graph(RenderGraphFlushFlags::SUBMIT | RenderGraphFlushFlags::RENEW_RENDER_GRAPH |
(wait_for_submission ? RenderGraphFlushFlags::WAIT_FOR_SUBMISSION :
RenderGraphFlushFlags::NONE),
VK_PIPELINE_STAGE_TRANSFER_BIT,
swap_chain_data.acquire_semaphore,
swap_chain_data.present_semaphore,
swap_chain_data.submission_fence);
/* Discard/remove not owning swapchain handlers.
* During a regular swapchain update, NVIDIA can use the same image multiple times in a row.
* Placing these images in the discard pool results in incorrect state, best to remove them
* directly. */
if (wait_for_submission) {
device.resources.remove_image(swap_chain_data.image);
}
else {
discard_pool.discard_swapchain_image(swap_chain_data.image);
}
#if 0
device.debug_print();
#endif
}
void VKContext::specialization_constants_set(
const shader::SpecializationConstants *constants_state)
{
constants_state_ = (constants_state != nullptr) ? *constants_state :
shader::SpecializationConstants{};
}
std::unique_ptr<VKStreamingBuffer> &VKContext::get_or_create_streaming_buffer(
VKBuffer &buffer, VkDeviceSize min_offset_alignment)
{
for (std::unique_ptr<VKStreamingBuffer> &streaming_buffer : streaming_buffers_) {
if (streaming_buffer->vk_buffer_dst() == buffer.vk_handle()) {
return streaming_buffer;
}
}
streaming_buffers_.append(std::make_unique<VKStreamingBuffer>(buffer, min_offset_alignment));
return streaming_buffers_.last();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name OpenXR
* \{ */
void VKContext::openxr_acquire_framebuffer_image_callback(GHOST_VulkanOpenXRData *openxr_data)
{
VKContext *context = VKContext::get();
BLI_assert(context);
context->openxr_acquire_framebuffer_image_handler(*openxr_data);
}
void VKContext::openxr_release_framebuffer_image_callback(GHOST_VulkanOpenXRData *openxr_data)
{
VKContext *context = VKContext::get();
BLI_assert(context);
context->openxr_release_framebuffer_image_handler(*openxr_data);
}
void VKContext::openxr_acquire_framebuffer_image_handler(GHOST_VulkanOpenXRData &openxr_data)
{
VKFrameBuffer &framebuffer = *unwrap(active_fb);
VKTexture *color_attachment = unwrap(unwrap(framebuffer.color_tex(0)));
openxr_data.extent.width = color_attachment->width_get();
openxr_data.extent.height = color_attachment->height_get();
/* Determine the data format for data transfer. */
const TextureFormat device_format = color_attachment->device_format_get();
eGPUDataFormat data_format = GPU_DATA_HALF_FLOAT;
if (ELEM(device_format, TextureFormat::UNORM_8_8_8_8)) {
data_format = GPU_DATA_UBYTE;
}
switch (openxr_data.data_transfer_mode) {
case GHOST_kVulkanXRModeCPU:
openxr_data.cpu.image_data = MEM_new_uninitialized(
color_attachment->read_size_get(0, data_format), __func__);
color_attachment->read(0, data_format, openxr_data.cpu.image_data);
break;
case GHOST_kVulkanXRModeFD: {
flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::WAIT_FOR_COMPLETION |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
if (openxr_data.gpu.vk_image_blender != color_attachment->vk_image_handle()) {
VKMemoryExport exported_memory = color_attachment->export_memory(
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT);
openxr_data.gpu.image_handle = exported_memory.handle;
openxr_data.gpu.new_handle = true;
openxr_data.gpu.image_format = to_vk_format(color_attachment->device_format_get());
openxr_data.gpu.memory_size = exported_memory.memory_size;
openxr_data.gpu.memory_offset = exported_memory.memory_offset;
openxr_data.gpu.vk_image_blender = color_attachment->vk_image_handle();
}
break;
}
case GHOST_kVulkanXRModeWin32: {
flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::WAIT_FOR_COMPLETION |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
if (openxr_data.gpu.vk_image_blender != color_attachment->vk_image_handle()) {
VKMemoryExport exported_memory = color_attachment->export_memory(
VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT);
openxr_data.gpu.image_handle = exported_memory.handle;
openxr_data.gpu.new_handle = true;
openxr_data.gpu.image_format = to_vk_format(color_attachment->device_format_get());
openxr_data.gpu.memory_size = exported_memory.memory_size;
openxr_data.gpu.memory_offset = exported_memory.memory_offset;
openxr_data.gpu.vk_image_blender = color_attachment->vk_image_handle();
}
break;
}
case GHOST_kVulkanXRModeRenderGraph:
break;
}
}
void VKContext::openxr_release_framebuffer_image_handler(GHOST_VulkanOpenXRData &openxr_data)
{
switch (openxr_data.data_transfer_mode) {
case GHOST_kVulkanXRModeCPU:
MEM_delete_void(openxr_data.cpu.image_data);
openxr_data.cpu.image_data = nullptr;
break;
case GHOST_kVulkanXRModeFD:
/* Nothing to do as import of the handle by the XrInstance removes the ownership of the
* handle. Ref
* https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_external_memory_fd.html#_issues
*/
break;
case GHOST_kVulkanXRModeWin32:
#ifdef _WIN32
if (openxr_data.gpu.new_handle) {
/* Exported handle isn't consumed during import and should be freed after use. */
CloseHandle(HANDLE(openxr_data.gpu.image_handle));
openxr_data.gpu.image_handle = 0;
}
#endif
break;
case GHOST_kVulkanXRModeRenderGraph:
break;
}
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,186 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "gpu_context_private.hh"
#include "GHOST_Types.hh"
#include "render_graph/vk_render_graph.hh"
#include "vk_buffer_pool.hh"
#include "vk_common.hh"
#include "vk_debug.hh"
#include "vk_descriptor_pools.hh"
#include "vk_resource_pool.hh"
#include "vk_streaming_buffer.hh"
namespace blender::gpu {
class VKFrameBuffer;
class VKVertexAttributeObject;
class VKBatch;
class VKStateManager;
class VKShader;
class VKThreadData;
class VKDevice;
enum RenderGraphFlushFlags {
NONE = 0,
RENEW_RENDER_GRAPH = 1 << 0,
SUBMIT = 1 << 1,
WAIT_FOR_SUBMISSION = 1 << 2,
WAIT_FOR_COMPLETION = 1 << 3,
};
ENUM_OPERATORS(RenderGraphFlushFlags);
class VKContext : public Context, NonCopyable {
friend class VKDevice;
private:
VkExtent2D vk_extent_ = {};
VkSurfaceFormatKHR swap_chain_format_ = {};
gpu::Texture *surface_texture_ = nullptr;
GHOST_IContext *ghost_context_;
Vector<std::unique_ptr<VKStreamingBuffer>> streaming_buffers_;
/* Reusable data. Stored inside context to limit reallocations. */
render_graph::VKResourceAccessInfo access_info_ = {};
std::optional<std::reference_wrapper<VKThreadData>> thread_data_;
std::optional<std::reference_wrapper<render_graph::VKRenderGraph>> render_graph_;
/* Active shader specialization constants state. */
shader::SpecializationConstants constants_state_;
/* Debug scope timings. Adapted form GLContext::TimeQuery.
* Only supports CPU timings for now. */
struct ScopeTimings {
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
using Nanoseconds = std::chrono::nanoseconds;
std::string name;
bool finished;
TimePoint cpu_start, cpu_end;
};
Vector<ScopeTimings> scope_timings;
void process_frame_timings();
public:
VKDiscardPool discard_pool;
VKBufferPool push_constants_pool;
const render_graph::VKRenderGraph &render_graph() const
{
return render_graph_.value().get();
}
render_graph::VKRenderGraph &render_graph()
{
return render_graph_.value().get();
}
VKContext(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context);
virtual ~VKContext();
void activate() override;
void deactivate() override;
void begin_frame() override;
void end_frame() override;
void flush() override;
TimelineValue flush_render_graph(
RenderGraphFlushFlags flags,
VkPipelineStageFlags wait_dst_stage_mask = VK_PIPELINE_STAGE_NONE,
VkSemaphore wait_semaphore = VK_NULL_HANDLE,
VkSemaphore signal_semaphore = VK_NULL_HANDLE,
VkFence signal_fence = VK_NULL_HANDLE);
void finish() override;
void memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) override;
void debug_group_begin(const char *name, int index) override;
void debug_group_end() override;
bool debug_capture_begin(const char *title) override;
void debug_capture_end() override;
void *debug_capture_scope_create(const char *name) override;
bool debug_capture_scope_begin(void *scope) override;
void debug_capture_scope_end(void *scope) override;
void debug_unbind_all_ubo() override;
void debug_unbind_all_ssbo() override;
bool has_active_framebuffer() const;
void activate_framebuffer(VKFrameBuffer &framebuffer);
void deactivate_framebuffer();
VKFrameBuffer *active_framebuffer_get() const;
/**
* Ensure that the active framebuffer isn't rendering.
*
* Between `vkCmdBeginRendering` and `vkCmdEndRendering` the framebuffer is rendering. Dispatch
* and transfer commands cannot be called between these commands. They can call this method to
* ensure that the framebuffer is outside these calls.
*/
void rendering_end();
render_graph::VKResourceAccessInfo &reset_and_get_access_info();
/**
* Update the give shader data with the current state of the context.
*/
void update_pipeline_data(render_graph::VKPipelineData &r_pipeline_data);
void update_pipeline_data(const VKFrameBuffer &framebuffer,
GPUPrimType primitive,
VKVertexAttributeObject &vao,
render_graph::VKPipelineDataGraphics &r_pipeline_data);
void sync_backbuffer();
static VKContext *get()
{
return static_cast<VKContext *>(Context::get());
}
VKDescriptorPools &descriptor_pools_get();
VKDescriptorSetTracker &descriptor_set_get();
VKStateManager &state_manager_get() const;
static void swap_buffer_draw_callback(const GHOST_VulkanSwapChainData *data,
bool wait_for_submission);
static void swap_buffer_acquired_callback();
static void openxr_acquire_framebuffer_image_callback(GHOST_VulkanOpenXRData *data);
static void openxr_release_framebuffer_image_callback(GHOST_VulkanOpenXRData *data);
void specialization_constants_set(const shader::SpecializationConstants *constants_state);
std::unique_ptr<VKStreamingBuffer> &get_or_create_streaming_buffer(
VKBuffer &buffer, VkDeviceSize min_offset_alignment);
private:
void swap_buffer_draw_handler(const GHOST_VulkanSwapChainData &data, bool wait_for_submission);
void swap_buffer_acquired_handler();
void openxr_acquire_framebuffer_image_handler(GHOST_VulkanOpenXRData &data);
void openxr_release_framebuffer_image_handler(GHOST_VulkanOpenXRData &data);
void update_pipeline_data(VKShader &shader,
VkPipeline vk_pipeline,
render_graph::VKPipelineData &r_pipeline_data);
};
BLI_INLINE bool operator==(const VKContext &a, const VKContext &b)
{
return static_cast<const void *>(&a) == static_cast<const void *>(&b);
}
} // namespace blender::gpu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,240 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_math_vector_types.hh"
#include "gpu_texture_private.hh"
#include "vk_common.hh"
namespace blender::gpu {
struct VKWorkarounds;
/**
* \brief Check if data conversion is needed when uploading host_format to a device_format.
*
* \param host_format: format of the host buffer.
* \param host_texture_format: texture format of the host buffer.
* \param device_format: format of the device buffer.
*/
bool needs_conversion(eGPUDataFormat host_format,
TextureFormat host_texture_format,
TextureFormat device_format);
/**
* Convert host buffer to device buffer.
*
* \param dst_buffer: device buffer.
* \param src_buffer: host buffer.
* \param buffer_size: number of pixels to convert from the start of the given buffer.
* \param host_format: format of the host buffer.
* \param host_texture_format: texture format of the host buffer.
* \param device_format: format of the device buffer.
*
* \note Will assert when the host_format/device_format combination isn't valid
* (#validate_data_format) or supported. Some combinations aren't supported in Vulkan due to
* platform incompatibility.
*/
void convert_host_to_device(void *dst_buffer,
const void *src_buffer,
size_t buffer_size,
eGPUDataFormat host_format,
TextureFormat host_texture_format,
TextureFormat device_format);
/**
* Convert device buffer to host buffer.
*
* \param dst_buffer: host buffer
* \param src_buffer: device buffer.
* \param buffer_size: number of pixels to convert from the start of the given buffer.
* \param host_format: format of the host buffer
* \param host_texture_format: texture format of the host buffer.
* \param device_format: format of the device buffer.
*
* \note Will assert when the host_format/device_format combination isn't valid
* (#validate_data_format) or supported. Some combinations aren't supported in Vulkan due to
* platform incompatibility.
*/
void convert_device_to_host(void *dst_buffer,
const void *src_buffer,
size_t buffer_size,
eGPUDataFormat host_format,
TextureFormat host_texture_format,
TextureFormat device_format);
/* -------------------------------------------------------------------- */
/** \name Floating point conversions
* \{ */
/**
* Description of a IEEE 754-1985 floating point data type.
*/
template<bool HasSignBit, uint8_t MantissaBitLen, uint8_t ExponentBitLen>
class FloatingPointFormat {
public:
static constexpr bool HAS_SIGN = HasSignBit;
static constexpr uint8_t SIGN_SHIFT = MantissaBitLen + ExponentBitLen;
static constexpr uint32_t SIGN_MASK = HasSignBit ? 1 : 0;
static constexpr uint8_t MANTISSA_LEN = MantissaBitLen;
static constexpr uint8_t MANTISSA_SHIFT = 0;
static constexpr uint32_t MANTISSA_MASK = (1 << MantissaBitLen) - 1;
static constexpr uint32_t MANTISSA_NAN_MASK = MANTISSA_MASK;
static constexpr uint8_t EXPONENT_SHIFT = MantissaBitLen;
static constexpr uint8_t EXPONENT_LEN = ExponentBitLen;
static constexpr uint32_t EXPONENT_MASK = (1 << ExponentBitLen) - 1;
static constexpr int32_t EXPONENT_BIAS = (1 << (ExponentBitLen - 1)) - 1;
static constexpr int32_t EXPONENT_SPECIAL_MASK = EXPONENT_MASK;
static uint32_t get_mantissa(uint32_t floating_point_number)
{
return (floating_point_number >> MANTISSA_SHIFT) & MANTISSA_MASK;
}
static uint32_t clear_mantissa(uint32_t floating_point_number)
{
return floating_point_number & ~(MANTISSA_MASK << MANTISSA_SHIFT);
}
static uint32_t set_mantissa(uint32_t mantissa, uint32_t floating_point_number)
{
uint32_t result = clear_mantissa(floating_point_number);
result |= mantissa << MANTISSA_SHIFT;
return result;
}
static uint32_t get_exponent(uint32_t floating_point_number)
{
return ((floating_point_number >> EXPONENT_SHIFT) & EXPONENT_MASK);
}
static uint32_t clear_exponent(uint32_t floating_point_number)
{
return floating_point_number & ~(EXPONENT_MASK << EXPONENT_SHIFT);
}
static uint32_t set_exponent(uint32_t exponent, uint32_t floating_point_number)
{
uint32_t result = clear_exponent(floating_point_number);
result |= (exponent) << EXPONENT_SHIFT;
return result;
}
static bool is_signed(uint32_t floating_point_number)
{
if constexpr (HasSignBit) {
return (floating_point_number >> SIGN_SHIFT) & SIGN_MASK;
}
return false;
}
static uint32_t clear_sign(uint32_t floating_point_number)
{
return floating_point_number & ~(1 << SIGN_SHIFT);
}
static uint32_t set_sign(bool sign, uint32_t floating_point_number)
{
if constexpr (!HasSignBit) {
return floating_point_number;
}
uint32_t result = clear_sign(floating_point_number);
result |= uint32_t(sign) << SIGN_SHIFT;
return result;
}
};
using FormatF32 = FloatingPointFormat<true, 23, 8>;
using FormatF11 = FloatingPointFormat<false, 6, 5>;
using FormatF10 = FloatingPointFormat<false, 5, 5>;
/**
* Convert between low precision floating (including 32 bit floats).
*
* The input and output values are bits (uint32_t) as this function does a bit-wise operations to
* convert between the formats. Additional conversion rules can be applied to the conversion
* function. Due to the implementation the compiler would make an optimized version depending on
* the actual possibilities.
*/
template<
/**
* FloatingPointFormat of the value that is converted to.
*/
typename DestinationFormat,
/**
* FloatingPointFormat of the value that is converted from.
*/
typename SourceFormat,
/**
* Should negative values be clamped to zero when DestinationFormat doesn't contain a sign
* bit. Also -Inf will be clamped to zero.
*
* When set to `false` and DestinationFormat doesn't contain a sign bit the value will be
* made absolute.
*/
bool ClampNegativeToZero = true>
uint32_t convert_float_formats(uint32_t value)
{
bool is_signed = SourceFormat::is_signed(value);
uint32_t mantissa = SourceFormat::get_mantissa(value);
int32_t exponent = SourceFormat::get_exponent(value);
const bool is_nan = (exponent == SourceFormat::EXPONENT_SPECIAL_MASK) && mantissa;
const bool is_inf = (exponent == SourceFormat::EXPONENT_SPECIAL_MASK) && (mantissa == 0);
const bool is_zero = (exponent == 0 && mantissa == 0);
/* Sign conversion */
if constexpr (!DestinationFormat::HAS_SIGN && ClampNegativeToZero) {
if (is_signed && !is_nan) {
return 0;
}
}
if (is_zero) {
return 0;
}
if (is_inf) {
exponent = DestinationFormat::EXPONENT_SPECIAL_MASK;
}
else if (is_nan) {
exponent = DestinationFormat::EXPONENT_SPECIAL_MASK;
mantissa = DestinationFormat::MANTISSA_NAN_MASK;
}
else {
/* Exponent conversion */
exponent -= SourceFormat::EXPONENT_BIAS;
/* Clamping when destination has lower precision. */
if constexpr (SourceFormat::EXPONENT_LEN > DestinationFormat::EXPONENT_LEN) {
if (exponent > DestinationFormat::EXPONENT_BIAS) {
exponent = 0;
mantissa = SourceFormat::MANTISSA_MASK;
}
else if (exponent < -DestinationFormat::EXPONENT_BIAS) {
return 0;
}
}
exponent += DestinationFormat::EXPONENT_BIAS;
/* Mantissa conversion */
if constexpr (SourceFormat::MANTISSA_LEN > DestinationFormat::MANTISSA_LEN) {
mantissa = mantissa >> (SourceFormat::MANTISSA_LEN - DestinationFormat::MANTISSA_LEN);
}
else if constexpr (SourceFormat::MANTISSA_LEN < DestinationFormat::MANTISSA_LEN) {
mantissa = mantissa << (DestinationFormat::MANTISSA_LEN - SourceFormat::MANTISSA_LEN);
}
}
uint32_t result = 0;
result = DestinationFormat::set_sign(is_signed, result);
result = DestinationFormat::set_exponent(exponent, result);
result = DestinationFormat::set_mantissa(mantissa, result);
return result;
}
/** \} */
}; // namespace blender::gpu

View File

@@ -0,0 +1,300 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <sstream>
#include "BKE_global.hh"
#include "BLI_utildefines.h"
#include "CLG_log.h"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_debug.hh"
#include "vk_to_string.hh"
#include "gpu_profile_report.hh"
namespace blender {
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
void VKContext::debug_group_begin(const char *name, int index)
{
UNUSED_VARS(index);
render_graph().debug_group_begin(name, debug::get_debug_group_color(name));
if (!G.profile_gpu) {
return;
}
ScopeTimings timings = {};
timings.name = name;
timings.finished = false;
timings.cpu_start = ScopeTimings::Clock::now();
scope_timings.append(timings);
}
void VKContext::debug_group_end()
{
render_graph().debug_group_end();
if (!G.profile_gpu) {
return;
}
for (int i = scope_timings.size() - 1; i >= 0; i--) {
ScopeTimings &query = scope_timings[i];
if (!query.finished) {
query.finished = true;
query.cpu_end = ScopeTimings::Clock::now();
break;
}
if (i == 0) {
CLOG_ERROR(&LOG, "Profile GPU error: Extra GPU_debug_group_end() call.");
}
}
}
void VKContext::process_frame_timings()
{
if (!G.profile_gpu) {
return;
}
Vector<ScopeTimings> &queries = scope_timings;
bool frame_is_valid = !queries.is_empty();
for (int i = queries.size() - 1; i >= 0; i--) {
if (!queries[i].finished) {
frame_is_valid = false;
CLOG_ERROR(&LOG, "Profile GPU error: Missing GPU_debug_group_end() call");
}
break;
}
if (!frame_is_valid) {
return;
}
for (ScopeTimings &query : queries) {
ProfileReport::get().add_group_cpu(query.name,
query.cpu_start.time_since_epoch().count(),
query.cpu_end.time_since_epoch().count());
}
queries.clear();
}
bool VKContext::debug_capture_begin(const char *title)
{
return VKBackend::get().debug_capture_begin(title);
}
bool VKBackend::debug_capture_begin(const char *title)
{
#ifdef WITH_RENDERDOC
bool result = renderdoc_api_.start_frame_capture(device.instance_get(), nullptr);
if (result && title) {
renderdoc_api_.set_frame_capture_title(title);
}
return result;
#else
UNUSED_VARS(title);
return false;
#endif
}
void VKContext::debug_capture_end()
{
VKBackend::get().debug_capture_end();
}
void VKBackend::debug_capture_end()
{
#ifdef WITH_RENDERDOC
renderdoc_api_.end_frame_capture(device.instance_get(), nullptr);
#endif
}
void *VKContext::debug_capture_scope_create(const char *name)
{
return (void *)name;
}
bool VKContext::debug_capture_scope_begin(void *scope)
{
#ifdef WITH_RENDERDOC
const char *title = (const char *)scope;
if (StringRefNull(title) != StringRefNull(G.gpu_debug_scope_name)) {
return false;
}
VKBackend::get().debug_capture_begin(title);
#else
UNUSED_VARS(scope);
#endif
return false;
}
void VKContext::debug_capture_scope_end(void *scope)
{
#ifdef WITH_RENDERDOC
const char *title = (const char *)scope;
if (StringRefNull(title) == StringRefNull(G.gpu_debug_scope_name)) {
VKBackend::get().debug_capture_end();
}
#else
UNUSED_VARS(scope);
#endif
}
} // namespace gpu
namespace gpu::debug {
void VKDebuggingTools::init(VkInstance vk_instance)
{
CLG_logref_init(&LOG);
init_messenger(vk_instance);
}
void VKDebuggingTools::deinit(VkInstance vk_instance)
{
destroy_messenger(vk_instance);
}
void object_label(VkObjectType vk_object_type, uint64_t object_handle, const char *name)
{
const VKDevice &device = VKBackend::get().device;
if (G.debug & G_DEBUG_GPU && device.functions.vkSetDebugUtilsObjectName && object_handle != 0) {
VkDebugUtilsObjectNameInfoEXT info = {};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
info.objectType = vk_object_type;
info.objectHandle = object_handle;
info.pObjectName = name;
device.functions.vkSetDebugUtilsObjectName(device.vk_handle(), &info);
}
}
} // namespace gpu::debug
namespace gpu::debug {
void VKDebuggingTools::print_labels(const VkDebugUtilsMessengerCallbackDataEXT *callback_data)
{
std::stringstream ss;
for (uint32_t object = 0; object < callback_data->objectCount; ++object) {
ss << " - ObjectType[" << to_string(callback_data->pObjects[object].objectType) << "],";
ss << "Handle[0x" << std::hex << uintptr_t(callback_data->pObjects[object].objectHandle)
<< "]";
if (callback_data->pObjects[object].pObjectName) {
ss << ",Name[" << callback_data->pObjects[object].pObjectName << "]";
}
ss << std::endl;
}
for (uint32_t label = 0; label < callback_data->cmdBufLabelCount; ++label) {
if (callback_data->pCmdBufLabels[label].pLabelName) {
ss << " - CommandBuffer : " << callback_data->pCmdBufLabels[label].pLabelName << std::endl;
}
}
for (uint32_t label = 0; label < callback_data->queueLabelCount; ++label) {
if (callback_data->pQueueLabels[label].pLabelName) {
ss << " - Queue : " << callback_data->pQueueLabels[label].pLabelName << std::endl;
}
}
ss << std::endl;
printf("%s", ss.str().c_str());
}
static VKAPI_ATTR VkBool32 VKAPI_CALL
messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
void *user_data)
{
CLG_Level level = CLG_LEVEL_INFO;
if (message_severity & (VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT))
{
level = CLG_LEVEL_INFO;
}
if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
level = CLG_LEVEL_WARN;
}
if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
level = CLG_LEVEL_ERROR;
}
if (bool(message_type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)) {
const char *format = "{0x%x}%s\n %s";
CLOG_AT_LEVEL(&LOG,
level,
format,
callback_data->messageIdNumber,
callback_data->pMessageIdName,
callback_data->pMessage);
const bool do_labels = (callback_data->objectCount + callback_data->cmdBufLabelCount +
callback_data->queueLabelCount) > 0;
const bool log_active = CLOG_CHECK(&LOG, level);
if (do_labels && log_active) {
VKDebuggingTools &debugging_tools = *reinterpret_cast<VKDebuggingTools *>(user_data);
debugging_tools.print_labels(callback_data);
}
}
else {
const char *format = "%s: %s";
CLOG_AT_LEVEL(&LOG, level, format, callback_data->pMessageIdName, callback_data->pMessage);
}
return VK_FALSE;
};
void VKDebuggingTools::init_messenger(VkInstance vk_instance)
{
if (vk_debug_utils_messenger != VK_NULL_HANDLE) {
return;
}
VKDevice &device = VKBackend::get().device;
if (!device.functions.vkCreateDebugUtilsMessenger) {
return;
}
VkDebugUtilsMessengerCreateInfoEXT create_info;
create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
create_info.pfnUserCallback = messenger_callback;
create_info.pUserData = this;
device.functions.vkCreateDebugUtilsMessenger(
vk_instance, &create_info, nullptr, &vk_debug_utils_messenger);
}
void VKDebuggingTools::destroy_messenger(VkInstance vk_instance)
{
if (vk_debug_utils_messenger == VK_NULL_HANDLE) {
return;
}
VKDevice &device = VKBackend::get().device;
device.functions.vkDestroyDebugUtilsMessenger(vk_instance, vk_debug_utils_messenger, nullptr);
vk_debug_utils_messenger = VK_NULL_HANDLE;
}
}; // namespace gpu::debug
} // namespace blender

View File

@@ -0,0 +1,64 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BKE_global.hh"
#include "BLI_compiler_attrs.h"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "vk_common.hh"
#include <typeindex>
namespace blender::gpu {
class VKContext;
class VKDevice;
namespace debug {
class VKDebuggingTools {
public:
void init(VkInstance vk_instance);
void deinit(VkInstance vk_instance);
void init_messenger(VkInstance vk_instance);
void destroy_messenger(VkInstance vk_instance);
void print_labels(const VkDebugUtilsMessengerCallbackDataEXT *callback_data);
private:
VkDebugUtilsMessengerEXT vk_debug_utils_messenger = VK_NULL_HANDLE;
};
void object_label(VkObjectType vk_object_type, uint64_t object_handle, const char *name);
template<typename T> void object_label(T vk_object_type, const StringRefNull name)
{
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
const size_t label_size = 64;
char label[label_size];
memset(label, 0, label_size);
static int stats = 0;
SNPRINTF(label, "%s_%d", name.c_str(), stats++);
object_label(to_vk_object_type(vk_object_type),
uint64_t(vk_object_type),
const_cast<const char *>(label));
};
/**
* How to use:
* \code{.cc}
* debug::raise_message(0xB41ca2, VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
* "This is a raise message. %llx", (uintptr_t)vk_object);
* \endcode
*/
void raise_message(int32_t id_number,
VkDebugUtilsMessageSeverityFlagBitsEXT vk_severity_flag_bits,
const char *fmt,
...) ATTR_PRINTF_FORMAT(3, 4);
} // namespace debug
} // namespace blender::gpu

View File

@@ -0,0 +1,102 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_descriptor_pools.hh"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_device.hh"
namespace blender::gpu {
VKDescriptorPools::~VKDescriptorPools()
{
const VKDevice &device = VKBackend::get().device;
for (const VkDescriptorPool vk_descriptor_pool : recycled_pools_) {
vkDestroyDescriptorPool(device.vk_handle(), vk_descriptor_pool, nullptr);
}
recycled_pools_.clear();
if (vk_descriptor_pool_ != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(device.vk_handle(), vk_descriptor_pool_, nullptr);
vk_descriptor_pool_ = VK_NULL_HANDLE;
}
}
void VKDescriptorPools::init(const VKDevice &device)
{
ensure_pool(device);
}
void VKDescriptorPools::ensure_pool(const VKDevice &device)
{
if (vk_descriptor_pool_ != VK_NULL_HANDLE) {
return;
}
std::scoped_lock lock(mutex_);
if (!recycled_pools_.is_empty()) {
vk_descriptor_pool_ = recycled_pools_.pop_last();
return;
}
Vector<VkDescriptorPoolSize> pool_sizes = {
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, POOL_SIZE_STORAGE_BUFFER},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, POOL_SIZE_STORAGE_IMAGE},
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, POOL_SIZE_COMBINED_IMAGE_SAMPLER},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, POOL_SIZE_UNIFORM_BUFFER},
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, POOL_SIZE_UNIFORM_TEXEL_BUFFER},
{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, POOL_SIZE_INPUT_ATTACHMENT}};
VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.maxSets = POOL_SIZE_DESCRIPTOR_SETS;
pool_info.poolSizeCount = pool_sizes.size();
pool_info.pPoolSizes = pool_sizes.data();
vkCreateDescriptorPool(device.vk_handle(), &pool_info, nullptr, &vk_descriptor_pool_);
}
void VKDescriptorPools::discard_active_pool(VKContext &context)
{
context.discard_pool.discard_descriptor_pool_for_reuse(vk_descriptor_pool_, this);
vk_descriptor_pool_ = VK_NULL_HANDLE;
}
void VKDescriptorPools::recycle(VkDescriptorPool vk_descriptor_pool)
{
const VKDevice &device = VKBackend::get().device;
vkResetDescriptorPool(device.vk_handle(), vk_descriptor_pool, 0);
std::scoped_lock lock(mutex_);
recycled_pools_.append(vk_descriptor_pool);
}
VkDescriptorSet VKDescriptorPools::allocate(const VkDescriptorSetLayout descriptor_set_layout)
{
BLI_assert(descriptor_set_layout != VK_NULL_HANDLE);
BLI_assert(vk_descriptor_pool_ != VK_NULL_HANDLE);
const VKDevice &device = VKBackend::get().device;
VkDescriptorSetAllocateInfo allocate_info = {};
allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocate_info.descriptorPool = vk_descriptor_pool_;
allocate_info.descriptorSetCount = 1;
allocate_info.pSetLayouts = &descriptor_set_layout;
VkDescriptorSet vk_descriptor_set = VK_NULL_HANDLE;
VkResult result = vkAllocateDescriptorSets(
device.vk_handle(), &allocate_info, &vk_descriptor_set);
if (ELEM(result, VK_ERROR_OUT_OF_POOL_MEMORY, VK_ERROR_FRAGMENTED_POOL)) {
{
VKContext &context = *VKContext::get();
discard_active_pool(context);
ensure_pool(device);
}
return allocate(descriptor_set_layout);
}
return vk_descriptor_set;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_vector.hh"
#include "vk_descriptor_set.hh"
namespace blender::gpu {
class VKDevice;
/**
* List of VkDescriptorPools.
*
* In Vulkan a pool is constructed with a fixed size per resource type. When more resources are
* needed it a next pool should be created. VKDescriptorPools will keep track of those pools and
* construct new pools when the previous one is exhausted.
*/
class VKDescriptorPools {
/**
* Pool sizes to use. When one descriptor pool is requested to allocate a descriptor but isn't
* able to do so, it will fail.
*
* See VKDescriptorSetTracker::upload_descriptor_sets for rebalancing the pool sizes.
*/
static constexpr uint32_t POOL_SIZE_STORAGE_BUFFER = 1000;
static constexpr uint32_t POOL_SIZE_DESCRIPTOR_SETS = 250;
static constexpr uint32_t POOL_SIZE_STORAGE_IMAGE = 250;
static constexpr uint32_t POOL_SIZE_COMBINED_IMAGE_SAMPLER = 250;
static constexpr uint32_t POOL_SIZE_UNIFORM_BUFFER = 500;
static constexpr uint32_t POOL_SIZE_UNIFORM_TEXEL_BUFFER = 100;
static constexpr uint32_t POOL_SIZE_INPUT_ATTACHMENT = 100;
/**
* Unused recycled pools.
*
* When a pool is full it is being discarded (for reuse). After all descriptor sets of the pool
* are unused the descriptor pool can be reused.
* Note: descriptor pools/sets are pinned to a single thread so the pools should always return to
* the instance it was created on.
*/
Vector<VkDescriptorPool> recycled_pools_;
/** Active descriptor pool. Should always be a valid handle. */
VkDescriptorPool vk_descriptor_pool_ = VK_NULL_HANDLE;
Mutex mutex_;
public:
~VKDescriptorPools();
void init(const VKDevice &vk_device);
/**
* Allocate a new descriptor set.
*
* When the active descriptor pool is full it is discarded and another descriptor pool is
* ensured.
*/
VkDescriptorSet allocate(const VkDescriptorSetLayout descriptor_set_layout);
/**
* Recycle a previous discarded descriptor pool.
*/
void recycle(VkDescriptorPool vk_descriptor_pool);
private:
void add_new_pool(const VKDevice &device);
void discard_active_pool(VKContext &vk_context);
void ensure_pool(const VKDevice &device);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,695 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_descriptor_set.hh"
#include "vk_buffer.hh"
#include "vk_index_buffer.hh"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "vk_state_manager.hh"
#include "vk_storage_buffer.hh"
#include "vk_texture.hh"
#include "vk_vertex_buffer.hh"
#include "CLG_log.h"
namespace blender::gpu {
static CLG_LogRef LOG = {"gpu.vulkan"};
void VKDescriptorSetTracker::update_descriptor_set(VKContext &context,
render_graph::VKResourceAccessInfo &access_info,
render_graph::VKPipelineData &r_pipeline_data)
{
VKShader &shader = *unwrap(context.shader);
VKStateManager &state_manager = context.state_manager_get();
/* Need to know the exact buffer and offset when shader uses an uniform buffer to store push
* constants. */
VKBufferWithOffset push_constants_buffer = {};
/* Bind uniform push constants to descriptor set. */
if (shader.push_constants.layout_get().storage_type_get() ==
VKPushConstants::StorageType::BUFFER)
{
push_constants_buffer = shader.push_constants.update_uniform_buffer(context);
}
update_resource_access_info(context, access_info, push_constants_buffer);
/* Can we reuse previous descriptor set. */
const VkDescriptorSetLayout shader_descriptor_set_layout = shader.vk_descriptor_set_layout_get();
if (!state_manager.is_dirty && vk_descriptor_set_layout_ == shader_descriptor_set_layout &&
shader.push_constants.layout_get().storage_type_get() !=
VKPushConstants::StorageType::BUFFER)
{
r_pipeline_data.vk_descriptor_set = descriptor_sets.vk_descriptor_set;
return;
}
vk_descriptor_set_layout_ = shader_descriptor_set_layout;
state_manager.is_dirty = false;
VKDevice &device = VKBackend::get().device;
VkDescriptorSetLayout vk_descriptor_set_layout = shader.vk_descriptor_set_layout_get();
descriptor_sets.allocate_new_descriptor_set(
device, context, shader, vk_descriptor_set_layout, r_pipeline_data);
descriptor_sets.bind_shader_resources(device, state_manager, shader, push_constants_buffer);
}
/* -------------------------------------------------------------------- */
/** \name Update resource access info
* \{ */
void VKDescriptorSetTracker::update_resource_access_info_binding_uniform_buffer(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info)
{
VKUniformBuffer &uniform_buffer = *state_manager.uniform_buffers_.get(resource_binding.binding);
uniform_buffer.ensure_updated();
access_info.buffers.append({uniform_buffer.vk_handle(), resource_binding.access_mask});
}
void VKDescriptorSetTracker::update_resource_access_info_binding_storage_buffer(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info)
{
const BindSpaceStorageBuffers::Elem &elem = state_manager.storage_buffers_.get(
resource_binding.binding);
VkBuffer vk_buffer = VK_NULL_HANDLE;
switch (elem.resource_type) {
case BindSpaceStorageBuffers::Type::IndexBuffer: {
VKIndexBuffer *index_buffer = static_cast<VKIndexBuffer *>(elem.resource);
index_buffer->ensure_updated();
vk_buffer = index_buffer->vk_handle();
break;
}
case BindSpaceStorageBuffers::Type::VertexBuffer: {
VKVertexBuffer *vertex_buffer = static_cast<VKVertexBuffer *>(elem.resource);
vertex_buffer->ensure_updated();
vk_buffer = vertex_buffer->vk_handle();
break;
}
case BindSpaceStorageBuffers::Type::UniformBuffer: {
VKUniformBuffer *uniform_buffer = static_cast<VKUniformBuffer *>(elem.resource);
uniform_buffer->ensure_updated();
vk_buffer = uniform_buffer->vk_handle();
break;
}
case BindSpaceStorageBuffers::Type::StorageBuffer: {
VKStorageBuffer *storage_buffer = static_cast<VKStorageBuffer *>(elem.resource);
storage_buffer->ensure_allocated();
vk_buffer = storage_buffer->vk_handle();
break;
}
case BindSpaceStorageBuffers::Type::Buffer: {
VKBuffer *buffer = static_cast<VKBuffer *>(elem.resource);
vk_buffer = buffer->vk_handle();
break;
}
case BindSpaceStorageBuffers::Type::Unused: {
BLI_assert_unreachable();
}
}
if (vk_buffer != VK_NULL_HANDLE) {
access_info.buffers.append({vk_buffer, resource_binding.access_mask});
}
}
void VKDescriptorSetTracker::update_resource_access_info_binding_sampler(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info)
{
const BindSpaceTextures::Elem *elem_ptr = state_manager.textures_.get(resource_binding.binding);
if (!elem_ptr || elem_ptr->resource == nullptr) {
/* Unbound resource. */
if (bool(G.debug & G_DEBUG_GPU)) {
VKContext &context = *VKContext::get();
const VKShader &shader = *static_cast<const VKShader *>(context.shader);
const VKShaderInterface &interface = shader.interface_get();
const ShaderInput &shader_input = *interface.texture_get(resource_binding.binding);
CLOG_ERROR(&LOG,
"Missing Texture bind at slot %d : %s > %s",
shader_input.binding,
shader.name_get().c_str(),
interface.input_name_get(&shader_input));
}
return;
}
const BindSpaceTextures::Elem &elem = *elem_ptr;
switch (elem.resource_type) {
case BindSpaceTextures::Type::VertexBuffer: {
VKVertexBuffer &vertex_buffer = *static_cast<VKVertexBuffer *>(elem.resource);
vertex_buffer.ensure_updated();
access_info.buffers.append({vertex_buffer.vk_handle(), resource_binding.access_mask});
break;
}
case BindSpaceTextures::Type::Texture: {
VKTexture *texture = static_cast<VKTexture *>(elem.resource);
if (texture->type_ == GPU_TEXTURE_BUFFER) {
VKVertexBuffer &vertex_buffer = *texture->source_buffer_;
vertex_buffer.ensure_updated();
access_info.buffers.append({vertex_buffer.vk_handle(), resource_binding.access_mask});
}
else {
VKSubImageRange subimage = {};
if (texture->is_texture_view()) {
IndexRange layer_range = texture->layer_range();
IndexRange mipmap_range = texture->mip_map_range();
subimage = {uint32_t(mipmap_range.start()),
uint32_t(mipmap_range.size()),
uint32_t(layer_range.start()),
uint32_t(layer_range.size())};
}
access_info.images.append({texture->vk_image_handle(),
resource_binding.access_mask,
to_vk_image_aspect_flag_bits(texture->device_format_get()),
subimage});
}
break;
}
case BindSpaceTextures::Type::Unused: {
BLI_assert_unreachable();
}
}
}
void VKDescriptorSetTracker::update_resource_access_info_binding_image(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info)
{
VKTexture &texture = *state_manager.images_.get(resource_binding.binding);
VKSubImageRange subimage = {};
if (texture.is_texture_view()) {
IndexRange layer_range = texture.layer_range();
IndexRange mipmap_range = texture.mip_map_range();
subimage = {uint32_t(mipmap_range.start()),
uint32_t(mipmap_range.size()),
uint32_t(layer_range.start()),
uint32_t(layer_range.size())};
}
access_info.images.append({texture.vk_image_handle(),
resource_binding.access_mask,
to_vk_image_aspect_flag_bits(texture.device_format_get()),
subimage});
}
void VKDescriptorSetTracker::update_resource_access_info_binding_input_attachment(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info)
{
const VKDevice &device = VKBackend::get().device;
VKTexture *texture = nullptr;
if (device.extensions_get().dynamic_rendering_local_read) {
texture = state_manager.images_.get(resource_binding.binding);
}
else {
texture = static_cast<VKTexture *>(
state_manager.textures_.get(resource_binding.binding)->resource);
}
BLI_assert(texture);
VkImage vk_image = texture->vk_image_handle();
if (vk_image != VK_NULL_HANDLE) {
VKSubImageRange subimage = {};
if (texture->is_texture_view()) {
IndexRange layer_range = texture->layer_range();
IndexRange mipmap_range = texture->mip_map_range();
subimage = {uint32_t(mipmap_range.start()),
uint32_t(mipmap_range.size()),
uint32_t(layer_range.start()),
uint32_t(layer_range.size())};
}
access_info.images.append({texture->vk_image_handle(),
resource_binding.access_mask,
to_vk_image_aspect_flag_bits(texture->device_format_get()),
subimage});
}
}
void VKDescriptorSetTracker::update_resource_access_info_binding(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info)
{
switch (resource_binding.bind_type) {
case VKBindType::UNIFORM_BUFFER: {
update_resource_access_info_binding_uniform_buffer(
state_manager, resource_binding, access_info);
break;
}
case VKBindType::STORAGE_BUFFER: {
update_resource_access_info_binding_storage_buffer(
state_manager, resource_binding, access_info);
break;
}
case VKBindType::SAMPLER: {
update_resource_access_info_binding_sampler(state_manager, resource_binding, access_info);
break;
}
case VKBindType::IMAGE: {
update_resource_access_info_binding_image(state_manager, resource_binding, access_info);
break;
}
case VKBindType::INPUT_ATTACHMENT: {
update_resource_access_info_binding_input_attachment(
state_manager, resource_binding, access_info);
break;
}
}
}
void VKDescriptorSetTracker::update_resource_access_info(
VKContext &context,
render_graph::VKResourceAccessInfo &access_info,
const VKBufferWithOffset &push_constants_buffer)
{
VKShader &shader = *unwrap(context.shader);
VKStateManager &state_manager = context.state_manager_get();
const VKShaderInterface &shader_interface = shader.interface_get();
for (const VKResourceBinding &resource_binding : shader_interface.resource_bindings_get()) {
if (resource_binding.binding == -1) {
continue;
}
update_resource_access_info_binding(state_manager, resource_binding, access_info);
}
/* Bind uniform push constants to descriptor set. */
if (shader.push_constants.layout_get().storage_type_get() ==
VKPushConstants::StorageType::BUFFER &&
push_constants_buffer.buffer != VK_NULL_HANDLE)
{
access_info.buffers.append({push_constants_buffer.buffer, VK_ACCESS_UNIFORM_READ_BIT});
}
}
/** \} */
void VKDescriptorSetTracker::upload_descriptor_sets()
{
descriptor_sets.upload_descriptor_sets();
vk_descriptor_set_layout_ = VK_NULL_HANDLE;
}
/* -------------------------------------------------------------------- */
/** \name VKDescriptorSetUpdator
* \{ */
void VKDescriptorSetUpdator::bind_image_resource(const VKStateManager &state_manager,
const VKResourceBinding &resource_binding)
{
VKTexture &texture = *state_manager.images_.get(resource_binding.binding);
bind_image(
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_NULL_HANDLE,
texture.image_view_get(resource_binding.arrayed, VKImageViewFlags::NO_SWIZZLING).vk_handle(),
VK_IMAGE_LAYOUT_GENERAL,
resource_binding.location);
}
void VKDescriptorSetUpdator::bind_texture_resource(const VKDevice &device,
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding)
{
const BindSpaceTextures::Elem *elem_ptr = state_manager.textures_.get(resource_binding.binding);
if (!elem_ptr || elem_ptr->resource == nullptr) {
/* Unbound resource. */
return;
}
const BindSpaceTextures::Elem &elem = *elem_ptr;
switch (elem.resource_type) {
case BindSpaceTextures::Type::VertexBuffer: {
VKVertexBuffer &vertex_buffer = *static_cast<VKVertexBuffer *>(elem.resource);
bind_texel_buffer(vertex_buffer, resource_binding.location);
break;
}
case BindSpaceTextures::Type::Texture: {
VKTexture *texture = static_cast<VKTexture *>(elem.resource);
if (texture->type_ == GPU_TEXTURE_BUFFER) {
/* Texture buffers are no textures, but wrap around vertex buffers and need to be
* bound as texel buffers. */
/* TODO: Investigate if this can be improved in the API. */
VKVertexBuffer &vertex_buffer = *texture->source_buffer_;
bind_texel_buffer(vertex_buffer, resource_binding.location);
}
else {
const VKSampler &sampler = device.samplers().get(elem.sampler);
bind_image(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
sampler.vk_handle(),
texture->image_view_get(resource_binding.arrayed, VKImageViewFlags::DEFAULT)
.vk_handle(),
VK_IMAGE_LAYOUT_GENERAL,
resource_binding.location);
}
break;
}
case BindSpaceTextures::Type::Unused: {
BLI_assert_unreachable();
}
}
}
void VKDescriptorSetUpdator::bind_input_attachment_resource(
const VKDevice &device,
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding)
{
const bool supports_local_read = device.extensions_get().dynamic_rendering_local_read;
if (supports_local_read) {
VKTexture *texture = state_manager.images_.get(resource_binding.binding);
BLI_assert(texture);
bind_image(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
VK_NULL_HANDLE,
texture->image_view_get(resource_binding.arrayed, VKImageViewFlags::NO_SWIZZLING)
.vk_handle(),
VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR,
resource_binding.location);
}
else {
const BindSpaceTextures::Elem *elem_ptr = state_manager.textures_.get(
resource_binding.binding);
if (!elem_ptr) {
/* Unbound resource. */
BLI_assert_unreachable();
return;
}
const BindSpaceTextures::Elem &elem = *elem_ptr;
VKTexture *texture = static_cast<VKTexture *>(elem.resource);
BLI_assert(texture);
BLI_assert(elem.resource_type == BindSpaceTextures::Type::Texture);
const VKSampler &sampler = device.samplers().get(elem.sampler);
bind_image(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
sampler.vk_handle(),
texture->image_view_get(resource_binding.arrayed, VKImageViewFlags::DEFAULT).vk_handle(),
VK_IMAGE_LAYOUT_GENERAL,
resource_binding.location);
}
}
void VKDescriptorSetUpdator::bind_storage_buffer_resource(
const VKStateManager &state_manager, const VKResourceBinding &resource_binding)
{
const BindSpaceStorageBuffers::Elem &elem = state_manager.storage_buffers_.get(
resource_binding.binding);
VkBuffer vk_buffer = VK_NULL_HANDLE;
VkDeviceSize vk_device_size = 0;
switch (elem.resource_type) {
case BindSpaceStorageBuffers::Type::IndexBuffer: {
VKIndexBuffer *index_buffer = static_cast<VKIndexBuffer *>(elem.resource);
vk_buffer = index_buffer->vk_handle();
/* Using the allocated size of the buffer as conservative depth shader can read additional
* bytes when there are an uneven number of indices. */
vk_device_size = index_buffer->allocated_size_get() - elem.offset;
break;
}
case BindSpaceStorageBuffers::Type::VertexBuffer: {
VKVertexBuffer *vertex_buffer = static_cast<VKVertexBuffer *>(elem.resource);
vk_buffer = vertex_buffer->vk_handle();
vk_device_size = vertex_buffer->size_used_get() - elem.offset;
break;
}
case BindSpaceStorageBuffers::Type::UniformBuffer: {
VKUniformBuffer *uniform_buffer = static_cast<VKUniformBuffer *>(elem.resource);
vk_buffer = uniform_buffer->vk_handle();
vk_device_size = uniform_buffer->size_in_bytes() - elem.offset;
break;
}
case BindSpaceStorageBuffers::Type::StorageBuffer: {
VKStorageBuffer *storage_buffer = static_cast<VKStorageBuffer *>(elem.resource);
vk_buffer = storage_buffer->vk_handle();
vk_device_size = storage_buffer->usage_size_get();
break;
}
case BindSpaceStorageBuffers::Type::Buffer: {
VKBuffer *buffer = static_cast<VKBuffer *>(elem.resource);
vk_buffer = buffer->vk_handle();
vk_device_size = buffer->size_in_bytes() - elem.offset;
break;
}
case BindSpaceStorageBuffers::Type::Unused: {
BLI_assert_unreachable();
}
}
bind_buffer(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
vk_buffer,
elem.offset,
vk_device_size,
resource_binding.location);
}
void VKDescriptorSetUpdator::bind_uniform_buffer_resource(
const VKStateManager &state_manager, const VKResourceBinding &resource_binding)
{
VKUniformBuffer &uniform_buffer = *state_manager.uniform_buffers_.get(resource_binding.binding);
bind_buffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
uniform_buffer.vk_handle(),
0,
uniform_buffer.size_in_bytes(),
resource_binding.location);
}
void VKDescriptorSetUpdator::bind_push_constants(VKPushConstants &push_constants,
const VKBufferWithOffset &push_constants_buffer)
{
const VKPushConstants::Layout &push_constants_layout = push_constants.layout_get();
if (push_constants_layout.storage_type_get() != VKPushConstants::StorageType::BUFFER) {
return;
}
bind_buffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
push_constants_buffer.buffer,
push_constants_buffer.offset,
push_constants_layout.size_in_bytes(),
push_constants_layout.descriptor_set_location_get());
}
void VKDescriptorSetUpdator::bind_shader_resources(const VKDevice &device,
const VKStateManager &state_manager,
VKShader &shader,
const VKBufferWithOffset &push_constants_buffer)
{
const VKShaderInterface &shader_interface = shader.interface_get();
for (const VKResourceBinding &resource_binding : shader_interface.resource_bindings_get()) {
if (resource_binding.binding == -1) {
continue;
}
switch (resource_binding.bind_type) {
case VKBindType::UNIFORM_BUFFER:
bind_uniform_buffer_resource(state_manager, resource_binding);
break;
case VKBindType::STORAGE_BUFFER:
bind_storage_buffer_resource(state_manager, resource_binding);
break;
case VKBindType::SAMPLER:
bind_texture_resource(device, state_manager, resource_binding);
break;
case VKBindType::IMAGE:
bind_image_resource(state_manager, resource_binding);
break;
case VKBindType::INPUT_ATTACHMENT:
bind_input_attachment_resource(device, state_manager, resource_binding);
break;
}
}
/* Bind uniform push constants to descriptor set. */
bind_push_constants(shader.push_constants, push_constants_buffer);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name VKDescriptorSetPoolUpdator
* \{ */
void VKDescriptorSetPoolUpdator::allocate_new_descriptor_set(
VKDevice & /*device*/,
VKContext &context,
VKShader &shader,
VkDescriptorSetLayout vk_descriptor_set_layout,
render_graph::VKPipelineData &r_pipeline_data)
{
/* Use descriptor pools/sets. */
vk_descriptor_set = context.descriptor_pools_get().allocate(vk_descriptor_set_layout);
BLI_assert(vk_descriptor_set != VK_NULL_HANDLE);
debug::object_label(vk_descriptor_set, shader.name_get());
r_pipeline_data.vk_descriptor_set = vk_descriptor_set;
}
void VKDescriptorSetPoolUpdator::bind_buffer(VkDescriptorType vk_descriptor_type,
VkBuffer vk_buffer,
VkDeviceSize buffer_offset,
VkDeviceSize size_in_bytes,
VKDescriptorSet::Location location)
{
if (vk_buffer == VK_NULL_HANDLE) {
vk_descriptor_buffer_infos_.append({VK_NULL_HANDLE, 0, VK_WHOLE_SIZE});
}
else {
vk_descriptor_buffer_infos_.append({vk_buffer, buffer_offset, size_in_bytes});
}
vk_write_descriptor_sets_.append({VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
vk_descriptor_set,
location,
0,
1,
vk_descriptor_type,
nullptr,
nullptr,
nullptr});
}
void VKDescriptorSetPoolUpdator::bind_texel_buffer(VKVertexBuffer &vertex_buffer,
const VKDescriptorSet::Location location)
{
vertex_buffer.ensure_buffer_view();
vk_buffer_views_.append(vertex_buffer.vk_buffer_view_get());
vk_write_descriptor_sets_.append({VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
vk_descriptor_set,
location,
0,
1,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
nullptr,
nullptr,
nullptr});
}
void VKDescriptorSetPoolUpdator::bind_image(VkDescriptorType vk_descriptor_type,
VkSampler vk_sampler,
VkImageView vk_image_view,
VkImageLayout vk_image_layout,
VKDescriptorSet::Location location)
{
vk_descriptor_image_infos_.append({vk_sampler, vk_image_view, vk_image_layout});
vk_write_descriptor_sets_.append({VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
vk_descriptor_set,
location,
0,
1,
vk_descriptor_type,
nullptr,
nullptr,
nullptr});
}
void VKDescriptorSetPoolUpdator::upload_descriptor_sets()
{
if (vk_write_descriptor_sets_.is_empty()) {
return;
}
/* Finalize pointers that could have changed due to reallocations. */
int buffer_index = 0;
int buffer_view_index = 0;
int image_index = 0;
for (VkWriteDescriptorSet &vk_write_descriptor_set : vk_write_descriptor_sets_) {
switch (vk_write_descriptor_set.descriptorType) {
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
vk_write_descriptor_set.pImageInfo = &vk_descriptor_image_infos_[image_index++];
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
vk_write_descriptor_set.pTexelBufferView = &vk_buffer_views_[buffer_view_index++];
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
vk_write_descriptor_set.pBufferInfo = &vk_descriptor_buffer_infos_[buffer_index++];
break;
default:
BLI_assert_unreachable();
break;
}
}
#if 0
/* Uncomment this for rebalancing VKDescriptorPools::POOL_SIZE_* */
{
int storage_buffer_count = 0;
int storage_image_count = 0;
int combined_image_sampler_count = 0;
int uniform_buffer_count = 0;
int uniform_texel_buffer_count = 0;
int input_attachment_count = 0;
Set<VkDescriptorSet> descriptor_set_count;
for (VkWriteDescriptorSet &vk_write_descriptor_set : vk_write_descriptor_sets_) {
descriptor_set_count.add(vk_write_descriptor_set.dstSet);
switch (vk_write_descriptor_set.descriptorType) {
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
combined_image_sampler_count += 1;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
storage_image_count += 1;
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
uniform_texel_buffer_count += 1;
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
uniform_buffer_count += 1;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
storage_buffer_count += 1;
break;
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
input_attachment_count += 1;
break;
default:
BLI_assert_unreachable();
}
}
std::cout << __func__ << ": "
<< "descriptor_set=" << descriptor_set_count.size()
<< ", combined_image_sampler=" << combined_image_sampler_count
<< ", storage_image=" << storage_image_count
<< ", uniform_texel_buffer=" << uniform_texel_buffer_count
<< ", uniform_buffer=" << uniform_buffer_count
<< ", storage_buffer=" << storage_buffer_count
<< ", input_attachment=" << input_attachment_count << "\n";
}
#endif
/* Update the descriptor set on the device. */
const VKDevice &device = VKBackend::get().device;
vkUpdateDescriptorSets(device.vk_handle(),
vk_write_descriptor_sets_.size(),
vk_write_descriptor_sets_.data(),
0,
nullptr);
vk_descriptor_image_infos_.clear();
vk_descriptor_buffer_infos_.clear();
vk_buffer_views_.clear();
vk_write_descriptor_sets_.clear();
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,218 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "gpu_shader_private.hh"
#include "render_graph/nodes/vk_pipeline_data.hh"
#include "render_graph/vk_resource_access_info.hh"
#include "vk_buffer.hh"
#include "vk_common.hh"
#include "vk_descriptor_set_layouts.hh"
#include "vk_uniform_buffer.hh"
namespace blender::gpu {
struct VKResourceBinding;
class VKStateManager;
class VKDevice;
class VKPushConstants;
class VKShader;
class VKDescriptorSetTracker;
class VKVertexBuffer;
/**
* In vulkan shader resources (images and buffers) are grouped in descriptor sets.
*
* The resources inside a descriptor set can be updated and bound per set.
*
* Currently Blender only supports a single descriptor set per shader, but it is planned to be able
* to use 2 descriptor sets per shader. One for each #gpu::shader::Frequency.
*/
class VKDescriptorSet : NonCopyable {
public:
/**
* Binding location of a resource in a descriptor set.
*
* Locations and bindings are used for different reasons. In the Vulkan backend we use
* ShaderInput.location to store the descriptor set + the resource binding inside the descriptor
* set. To ease the development the VKDescriptorSet::Location will be used to hide this
* confusion.
*
* NOTE: [future development] When supporting multiple descriptor sets the encoding/decoding can
* be centralized here. Location will then also contain the descriptor set index.
*/
struct Location {
friend class VKDescriptorSetTracker;
friend class VKShaderInterface;
friend struct VKResourceBinding;
private:
/**
* References to a binding in the descriptor set.
*/
uint32_t binding;
Location(uint32_t binding) : binding(binding) {}
public:
Location() = default;
bool operator==(const Location &other) const
{
return binding == other.binding;
}
operator uint32_t() const
{
return binding;
}
};
};
class VKDescriptorSetUpdator {
public:
virtual ~VKDescriptorSetUpdator() {};
virtual void allocate_new_descriptor_set(VKDevice &device,
VKContext &context,
VKShader &shader,
VkDescriptorSetLayout vk_descriptor_set_layout,
render_graph::VKPipelineData &r_pipeline_data) = 0;
void bind_shader_resources(const VKDevice &device,
const VKStateManager &state_manager,
VKShader &shader,
const VKBufferWithOffset &push_constants_buffer);
virtual void upload_descriptor_sets() = 0;
private:
void bind_image_resource(const VKStateManager &state_manager,
const VKResourceBinding &resource_binding);
void bind_texture_resource(const VKDevice &device,
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding);
void bind_storage_buffer_resource(const VKStateManager &state_manager,
const VKResourceBinding &resource_binding);
void bind_uniform_buffer_resource(const VKStateManager &state_manager,
const VKResourceBinding &resource_binding);
void bind_input_attachment_resource(const VKDevice &device,
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding);
void bind_push_constants(VKPushConstants &push_constants,
const VKBufferWithOffset &push_constants_buffer);
protected:
virtual void bind_texel_buffer(VKVertexBuffer &vertex_buffer,
VKDescriptorSet::Location location) = 0;
virtual void bind_buffer(VkDescriptorType vk_descriptor_type,
VkBuffer vk_buffer,
VkDeviceSize buffer_offset,
VkDeviceSize size_in_bytes,
VKDescriptorSet::Location location) = 0;
virtual void bind_image(VkDescriptorType vk_descriptor_type,
VkSampler vk_sampler,
VkImageView vk_image_view,
VkImageLayout vk_image_layout,
VKDescriptorSet::Location location) = 0;
};
class VKDescriptorSetPoolUpdator : public VKDescriptorSetUpdator {
public:
VkDescriptorSet vk_descriptor_set = VK_NULL_HANDLE;
void allocate_new_descriptor_set(VKDevice &device,
VKContext &context,
VKShader &shader,
VkDescriptorSetLayout vk_descriptor_set_layout,
render_graph::VKPipelineData &r_pipeline_data) override;
void upload_descriptor_sets() override;
protected:
void bind_texel_buffer(VKVertexBuffer &vertex_buffer,
VKDescriptorSet::Location location) override;
void bind_buffer(VkDescriptorType vk_descriptor_type,
VkBuffer vk_buffer,
VkDeviceSize buffer_offset,
VkDeviceSize size_in_bytes,
VKDescriptorSet::Location location) override;
void bind_image(VkDescriptorType vk_descriptor_type,
VkSampler vk_sampler,
VkImageView vk_image_view,
VkImageLayout vk_image_layout,
VKDescriptorSet::Location location) override;
private:
Vector<VkBufferView> vk_buffer_views_;
Vector<VkDescriptorBufferInfo> vk_descriptor_buffer_infos_;
Vector<VkDescriptorImageInfo> vk_descriptor_image_infos_;
Vector<VkWriteDescriptorSet> vk_write_descriptor_sets_;
};
class VKDescriptorSetTracker {
friend class VKDescriptorSet;
/* Last used layout to identify changes. */
VkDescriptorSetLayout vk_descriptor_set_layout_ = VK_NULL_HANDLE;
public:
class VKDescriptorSetPoolUpdator descriptor_sets;
VKDescriptorSetTracker() {}
/**
* Update the descriptor set. Reuses previous descriptor set when no changes are detected. This
* improves performance when working with large grease pencil scenes.
*/
void update_descriptor_set(VKContext &context,
render_graph::VKResourceAccessInfo &resource_access_info,
render_graph::VKPipelineData &r_pipeline_data);
/**
* Upload all descriptor sets to the device.
*/
void upload_descriptor_sets();
private:
/**
* Add resources of the descriptor set to the resource access info.
*/
static void update_resource_access_info(VKContext &context,
render_graph::VKResourceAccessInfo &resource_access_info,
const VKBufferWithOffset &push_constants_buffer);
static void update_resource_access_info_binding(const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info);
static void update_resource_access_info_binding_uniform_buffer(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info);
static void update_resource_access_info_binding_image(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info);
static void update_resource_access_info_binding_sampler(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info);
static void update_resource_access_info_binding_storage_buffer(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info);
static void update_resource_access_info_binding_input_attachment(
const VKStateManager &state_manager,
const VKResourceBinding &resource_binding,
render_graph::VKResourceAccessInfo &access_info);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_descriptor_set_layouts.hh"
#include "vk_backend.hh"
namespace blender::gpu {
VKDescriptorSetLayouts::VKDescriptorSetLayouts()
{
vk_descriptor_set_layout_create_info_ = {};
vk_descriptor_set_layout_create_info_.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
}
VKDescriptorSetLayouts::~VKDescriptorSetLayouts()
{
deinit();
}
VkDescriptorSetLayout VKDescriptorSetLayouts::get_or_create(const VKDescriptorSetLayoutInfo &info,
bool &r_created,
bool &r_needed)
{
r_created = false;
r_needed = !info.bindings.is_empty();
if (r_needed == false) {
return VK_NULL_HANDLE;
}
std::scoped_lock mutex(mutex_);
VkDescriptorSetLayout *layout = vk_descriptor_set_layouts_.lookup_ptr(info);
if (layout) {
return *layout;
}
update_layout_bindings(info);
const VKDevice &device = VKBackend::get().device;
vk_descriptor_set_layout_create_info_.bindingCount = vk_descriptor_set_layout_bindings_.size();
vk_descriptor_set_layout_create_info_.pBindings = vk_descriptor_set_layout_bindings_.data();
VkDescriptorSetLayout vk_descriptor_set_layout = VK_NULL_HANDLE;
vkCreateDescriptorSetLayout(device.vk_handle(),
&vk_descriptor_set_layout_create_info_,
nullptr,
&vk_descriptor_set_layout);
BLI_assert(vk_descriptor_set_layout != VK_NULL_HANDLE);
vk_descriptor_set_layout_create_info_.bindingCount = 0;
vk_descriptor_set_layout_create_info_.pBindings = nullptr;
vk_descriptor_set_layout_bindings_.clear();
vk_descriptor_set_layouts_.add(info, vk_descriptor_set_layout);
r_created = true;
return vk_descriptor_set_layout;
}
void VKDescriptorSetLayouts::update_layout_bindings(const VKDescriptorSetLayoutInfo &info)
{
BLI_assert(vk_descriptor_set_layout_bindings_.is_empty());
vk_descriptor_set_layout_bindings_.reserve(info.bindings.size());
uint32_t index = 0;
for (const VkDescriptorType &vk_descriptor_type : info.bindings) {
VkDescriptorSetLayoutBinding binding = {};
binding.binding = index++;
binding.descriptorCount = 1;
binding.descriptorType = vk_descriptor_type;
binding.pImmutableSamplers = VK_NULL_HANDLE;
binding.stageFlags = vk_descriptor_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT ?
VkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT) :
info.vk_shader_stage_flags;
vk_descriptor_set_layout_bindings_.append(binding);
}
}
void VKDescriptorSetLayouts::deinit()
{
std::scoped_lock mutex(mutex_);
const VKDevice &device = VKBackend::get().device;
for (VkDescriptorSetLayout &vk_descriptor_set_layout : vk_descriptor_set_layouts_.values()) {
vkDestroyDescriptorSetLayout(device.vk_handle(), vk_descriptor_set_layout, nullptr);
}
vk_descriptor_set_layouts_.clear();
}
} // namespace blender::gpu

View File

@@ -0,0 +1,123 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Multiple shaders can use the same descriptor set layout. VKDescriptorSetLayouts has a mechanism
* to create and reuse existing descriptor set layouts.
*
* This makes it easier to detect layout changes between shaders. If the same layout is used, we
* will be able to reuse the descriptor set if the bindings are also the same.
*
* These resources are freed when the Vulkan backend is freed. Descriptor set layouts are Vulkan
* driver resources, but they are virtually unlimited.
*/
#pragma once
#include "BLI_map.hh"
#include "BLI_mutex.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "vk_common.hh"
namespace blender {
namespace gpu {
class VKDevice;
/**
* Key of descriptor set layout
*
* Contains information to identify same descriptor set layouts.
*/
struct VKDescriptorSetLayoutInfo {
using Bindings = Vector<VkDescriptorType>;
Bindings bindings;
VkShaderStageFlags vk_shader_stage_flags;
bool operator==(const VKDescriptorSetLayoutInfo &other) const
{
return vk_shader_stage_flags == other.vk_shader_stage_flags && bindings == other.bindings;
};
};
}; // namespace gpu
/**
* Default hash for gpu::VKDescriptorSetLayoutInfo.
*
* NOTE: DefaultHash needs to be implemented in namespace `blender`.
*/
template<> struct DefaultHash<gpu::VKDescriptorSetLayoutInfo> {
uint64_t operator()(const gpu::VKDescriptorSetLayoutInfo &key) const
{
uint64_t hash = uint64_t(key.vk_shader_stage_flags);
for (VkDescriptorType vk_descriptor_type : key.bindings) {
hash = hash * 33 ^ uint64_t(vk_descriptor_type);
}
return hash;
}
};
namespace gpu {
/**
* Registries of descriptor set layouts.
*/
class VKDescriptorSetLayouts : NonCopyable {
friend class VKDevice;
private:
/**
* Map containing all created descriptor set layouts.
*/
Map<VKDescriptorSetLayoutInfo, VkDescriptorSetLayout> vk_descriptor_set_layouts_;
/**
* Reusable descriptor set layout create info.
*/
VkDescriptorSetLayoutCreateInfo vk_descriptor_set_layout_create_info_;
Vector<VkDescriptorSetLayoutBinding> vk_descriptor_set_layout_bindings_;
Mutex mutex_;
public:
VKDescriptorSetLayouts();
virtual ~VKDescriptorSetLayouts();
/**
* Get an existing descriptor set layout, or create when not available.
* `r_created` is set to true when a new descriptor set layout was created, set to false when an
* existing descriptor set layout is returned.
* `r_needed` is set to true, when a descriptor set layout is needed
*/
VkDescriptorSetLayout get_or_create(const VKDescriptorSetLayoutInfo &info,
bool &r_created,
bool &r_needed);
/**
* Free all descriptor set layouts.
*
* This method is called when the VKDevice is destroyed.
*/
void deinit();
/**
* Return the number of descriptor set layouts.
*/
int64_t size() const
{
return vk_descriptor_set_layouts_.size();
}
private:
void update_layout_bindings(const VKDescriptorSetLayoutInfo &info);
};
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,616 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <sstream>
#include "CLG_log.h"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_device.hh"
#include "vk_state_manager.hh"
#include "vk_storage_buffer.hh"
#include "vk_texture.hh"
#include "vk_vertex_buffer.hh"
#include "gpu_shader_dependency_private.hh"
#include "GPU_capabilities.hh"
#include "gpu_capabilities_private.hh"
#include "BLI_math_matrix_types.hh"
namespace blender {
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
void VKExtensions::log() const
{
CLOG_DEBUG(&LOG,
"Device features\n"
" - [%c] shader output viewport index\n"
" - [%c] shader output layer\n"
" - [%c] fragment shader barycentric\n"
" - [%c] wide lines\n"
"Device extensions\n"
" - [%c] dynamic rendering local read\n"
" - [%c] dynamic rendering unused attachments\n"
" - [%c] extended dynamic state\n"
" - [%c] external memory\n"
" - [%c] graphics pipeline library\n"
" - [%c] host image copy\n"
" - [%c] line rasterization\n"
" - [%c] maintenance4\n"
" - [%c] memory priority\n"
" - [%c] pageable device local memory\n"
" - [%c] shader stencil export\n"
" - [%c] vertex input dynamic state",
shader_output_viewport_index ? 'X' : ' ',
shader_output_layer ? 'X' : ' ',
fragment_shader_barycentric ? 'X' : ' ',
wide_lines ? 'X' : ' ',
dynamic_rendering_local_read ? 'X' : ' ',
dynamic_rendering_unused_attachments ? 'X' : ' ',
extended_dynamic_state ? 'X' : ' ',
external_memory ? 'X' : ' ',
graphics_pipeline_library ? 'X' : ' ',
host_image_copy ? 'X' : ' ',
line_rasterization ? 'X' : ' ',
maintenance4 ? 'X' : ' ',
memory_priority ? 'X' : ' ',
pageable_device_local_memory ? 'X' : ' ',
GPU_stencil_export_support() ? 'X' : ' ',
vertex_input_dynamic_state ? 'X' : ' ');
}
void VKWorkarounds::log() const
{
CLOG_DEBUG(&LOG,
"Activated workarounds\n"
" - [%c] Not 16/32 bit aligned image formats\n"
" - [%c] No texture pool",
not_aligned_pixel_formats ? 'X' : ' ',
GCaps.texture_pool_workaround ? 'X' : ' ');
}
void VKDevice::reinit()
{
samplers_.free();
samplers_.init();
}
void VKDevice::deinit()
{
if (!is_initialized()) {
return;
}
deinit_submission_pool();
dummy_buffer.free();
samplers_.free();
GPU_SHADER_FREE_SAFE(vk_backbuffer_blit_sh_);
orphaned_data_render.deinit(*this);
orphaned_data.deinit(*this);
{
while (!thread_data_.is_empty()) {
VKThreadData *thread_data = thread_data_.pop_last();
delete thread_data;
}
thread_data_.clear();
}
pipelines.write_to_disk();
pipelines.free_data();
descriptor_set_layouts_.deinit();
vma_pools.deinit(*this);
mem_allocator_ = VK_NULL_HANDLE;
while (!render_graphs_.is_empty()) {
render_graph::VKRenderGraph *render_graph = render_graphs_.pop_last();
MEM_delete<render_graph::VKRenderGraph>(render_graph);
}
debugging_tools_.deinit(vk_instance_);
vk_instance_ = VK_NULL_HANDLE;
vk_physical_device_ = VK_NULL_HANDLE;
vk_device_ = VK_NULL_HANDLE;
vk_queue_family_ = 0;
vk_queue_ = VK_NULL_HANDLE;
vk_physical_device_properties_ = {};
glsl_vert_patch_.clear();
glsl_frag_patch_.clear();
glsl_geom_patch_.clear();
glsl_comp_patch_.clear();
is_initialized_ = false;
}
void VKDevice::init(GHOST_IContext *ghost_context)
{
BLI_assert(!is_initialized());
GHOST_VulkanHandles handles = {};
ghost_context->getVulkanHandles(handles);
vk_instance_ = handles.instance;
vk_physical_device_ = handles.physical_device;
vk_device_ = handles.device;
vk_queue_family_ = handles.graphic_queue_family;
vk_queue_ = handles.queue;
mem_allocator_ = handles.vma_allocator;
queue_mutex_ = static_cast<std::mutex *>(handles.queue_mutex);
init_physical_device_extensions();
init_physical_device_properties();
init_physical_device_memory_properties();
init_physical_device_features();
VKBackend::platform_init(*this);
VKBackend::capabilities_init(*this);
init_functions();
init_debug_callbacks();
vma_pools.init(*this);
pipelines.init();
pipelines.read_from_disk();
samplers_.init();
init_dummy_buffer();
debug::object_label(vk_handle(), "LogicalDevice");
debug::object_label(vk_queue_, "GenericQueue");
resources.use_dynamic_rendering_local_read = extensions_.dynamic_rendering_local_read;
orphaned_data.timeline_ = 0;
init_submission_pool();
is_initialized_ = true;
}
void VKDevice::init_functions()
{
#define LOAD_FUNCTION(name) (PFN_##name) vkGetInstanceProcAddr(vk_instance_, STRINGIFY(name))
/* VK_KHR_dynamic_rendering */
functions.vkCmdBeginRendering = LOAD_FUNCTION(vkCmdBeginRenderingKHR);
functions.vkCmdEndRendering = LOAD_FUNCTION(vkCmdEndRenderingKHR);
/* VK_EXT_debug_utils */
functions.vkCmdBeginDebugUtilsLabel = LOAD_FUNCTION(vkCmdBeginDebugUtilsLabelEXT);
functions.vkCmdEndDebugUtilsLabel = LOAD_FUNCTION(vkCmdEndDebugUtilsLabelEXT);
functions.vkSetDebugUtilsObjectName = LOAD_FUNCTION(vkSetDebugUtilsObjectNameEXT);
functions.vkCreateDebugUtilsMessenger = LOAD_FUNCTION(vkCreateDebugUtilsMessengerEXT);
functions.vkDestroyDebugUtilsMessenger = LOAD_FUNCTION(vkDestroyDebugUtilsMessengerEXT);
/* VK_EXT_extended_dynamic_state */
if (extensions_.extended_dynamic_state) {
functions.vkCmdSetFrontFace = LOAD_FUNCTION(vkCmdSetFrontFaceEXT);
}
/* VK_EXT_vertex_input_dynamic_state */
if (extensions_.vertex_input_dynamic_state) {
functions.vkCmdSetVertexInput = LOAD_FUNCTION(vkCmdSetVertexInputEXT);
}
/* VK_EXT_host_image_copy */
if (extensions_.host_image_copy) {
functions.vkCopyMemoryToImage = LOAD_FUNCTION(vkCopyMemoryToImageEXT);
functions.vkTransitionImageLayout = LOAD_FUNCTION(vkTransitionImageLayoutEXT);
}
/* VK_KHR_maintenance4 */
if (extensions_.maintenance4) {
functions.vkGetDeviceImageMemoryRequirements = LOAD_FUNCTION(
vkGetDeviceImageMemoryRequirementsKHR);
functions.vkGetDeviceBufferMemoryRequirements = LOAD_FUNCTION(
vkGetDeviceBufferMemoryRequirementsKHR);
}
if (extensions_.external_memory) {
#ifdef _WIN32
/* VK_KHR_external_memory_win32 */
functions.vkGetMemoryWin32Handle = LOAD_FUNCTION(vkGetMemoryWin32HandleKHR);
#elif not defined(__APPLE__)
/* VK_KHR_external_memory_fd */
functions.vkGetMemoryFd = LOAD_FUNCTION(vkGetMemoryFdKHR);
#endif
}
#undef LOAD_FUNCTION
}
void VKDevice::init_debug_callbacks()
{
debugging_tools_.init(vk_instance_);
}
void VKDevice::init_physical_device_properties()
{
BLI_assert(vk_physical_device_ != VK_NULL_HANDLE);
VkPhysicalDeviceProperties2 vk_physical_device_properties = {};
vk_physical_device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
vk_physical_device_driver_properties_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
vk_physical_device_id_properties_.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
vk_physical_device_properties.pNext = &vk_physical_device_driver_properties_;
vk_physical_device_driver_properties_.pNext = &vk_physical_device_id_properties_;
if (supports_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME)) {
vk_physical_device_maintenance4_properties_.pNext = vk_physical_device_properties.pNext;
vk_physical_device_properties.pNext = &vk_physical_device_maintenance4_properties_;
}
if (supports_extension(VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME)) {
vk_physical_device_graphics_pipeline_library_properties_.pNext =
vk_physical_device_properties.pNext;
vk_physical_device_properties.pNext =
&vk_physical_device_graphics_pipeline_library_properties_;
}
vkGetPhysicalDeviceProperties2(vk_physical_device_, &vk_physical_device_properties);
vk_physical_device_properties_ = vk_physical_device_properties.properties;
}
void VKDevice::init_physical_device_memory_properties()
{
BLI_assert(vk_physical_device_ != VK_NULL_HANDLE);
vkGetPhysicalDeviceMemoryProperties(vk_physical_device_, &vk_physical_device_memory_properties_);
}
void VKDevice::init_physical_device_features()
{
BLI_assert(vk_physical_device_ != VK_NULL_HANDLE);
VkPhysicalDeviceFeatures2 features = {};
features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
vk_physical_device_vulkan_11_features_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
vk_physical_device_vulkan_12_features_.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
features.pNext = &vk_physical_device_vulkan_11_features_;
vk_physical_device_vulkan_11_features_.pNext = &vk_physical_device_vulkan_12_features_;
vkGetPhysicalDeviceFeatures2(vk_physical_device_, &features);
vk_physical_device_features_ = features.features;
}
void VKDevice::init_physical_device_extensions()
{
uint32_t count = 0;
vkEnumerateDeviceExtensionProperties(vk_physical_device_, nullptr, &count, nullptr);
device_extensions_ = Array<VkExtensionProperties>(count);
vkEnumerateDeviceExtensionProperties(
vk_physical_device_, nullptr, &count, device_extensions_.data());
}
bool VKDevice::supports_extension(const char *extension_name) const
{
for (const VkExtensionProperties &vk_extension_properties : device_extensions_) {
if (STREQ(vk_extension_properties.extensionName, extension_name)) {
return true;
}
}
return false;
}
void VKDevice::init_dummy_buffer()
{
dummy_buffer.create(sizeof(float4x4),
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
1.0f,
false,
"DummyBuffer");
/* 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_immediately(static_cast<void *>(data));
}
shader::GeneratedSource VKDevice::extensions_define(StringRefNull stage_define) const
{
std::stringstream ss;
ss << "#version 450\n";
{
/* Required extension. */
ss << "#extension GL_ARB_shader_draw_parameters : enable\n";
ss << "#define GPU_ARB_shader_draw_parameters\n";
ss << "#define gpu_BaseInstance (gl_BaseInstanceARB)\n";
}
ss << "#define GPU_ARB_clip_control\n";
ss << "#define GPU_ARB_derivative_control\n";
ss << "#define gl_VertexID gl_VertexIndex\n";
ss << "#define gpu_InstanceIndex (gl_InstanceIndex)\n";
ss << "#define gl_InstanceID (gpu_InstanceIndex - gpu_BaseInstance)\n";
ss << "#extension GL_ARB_shader_viewport_layer_array: enable\n";
if (GPU_stencil_export_support()) {
ss << "#extension GL_ARB_shader_stencil_export: enable\n";
ss << "#define GPU_ARB_shader_stencil_export 1\n";
}
if (extensions_.fragment_shader_barycentric) {
ss << "#extension GL_EXT_fragment_shader_barycentric : require\n";
ss << "#define gpu_BaryCoord gl_BaryCoordEXT\n";
ss << "#define gpu_BaryCoordNoPersp gl_BaryCoordNoPerspEXT\n";
}
ss << stage_define;
return shader::GeneratedSource{"gpu_shader_glsl_extension.glsl", {}, ss.str()};
}
std::string VKDevice::glsl_vertex_patch_get() const
{
shader::GeneratedSourceList sources{extensions_define("#define GPU_VERTEX_SHADER\n")};
return fmt::to_string(fmt::join(
gpu_shader_dependency_get_resolved_source("gpu_shader_compat_glsl.glsl", sources), ""));
}
std::string VKDevice::glsl_geometry_patch_get() const
{
shader::GeneratedSourceList sources{extensions_define("#define GPU_GEOMETRY_SHADER\n")};
return fmt::to_string(fmt::join(
gpu_shader_dependency_get_resolved_source("gpu_shader_compat_glsl.glsl", sources), ""));
}
std::string VKDevice::glsl_fragment_patch_get() const
{
shader::GeneratedSourceList sources{extensions_define("#define GPU_FRAGMENT_SHADER\n")};
return fmt::to_string(fmt::join(
gpu_shader_dependency_get_resolved_source("gpu_shader_compat_glsl.glsl", sources), ""));
}
std::string VKDevice::glsl_compute_patch_get() const
{
shader::GeneratedSourceList sources{extensions_define("#define GPU_COMPUTE_SHADER\n")};
return fmt::to_string(fmt::join(
gpu_shader_dependency_get_resolved_source("gpu_shader_compat_glsl.glsl", sources), ""));
}
/* -------------------------------------------------------------------- */
/** \name Platform/driver/device information
* \{ */
constexpr int32_t PCI_ID_NVIDIA = 0x10de;
constexpr int32_t PCI_ID_INTEL = 0x8086;
constexpr int32_t PCI_ID_AMD = 0x1002;
constexpr int32_t PCI_ID_ATI = 0x1022;
constexpr int32_t PCI_ID_APPLE = 0x106b;
GPUDeviceType VKDevice::device_type() const
{
switch (vk_physical_device_driver_properties_.driverID) {
case VK_DRIVER_ID_AMD_PROPRIETARY:
case VK_DRIVER_ID_AMD_OPEN_SOURCE:
case VK_DRIVER_ID_MESA_RADV:
return GPU_DEVICE_ATI;
case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
case VK_DRIVER_ID_MESA_NVK:
return GPU_DEVICE_NVIDIA;
case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
return GPU_DEVICE_INTEL;
case VK_DRIVER_ID_QUALCOMM_PROPRIETARY:
return GPU_DEVICE_QUALCOMM;
case VK_DRIVER_ID_MOLTENVK:
return GPU_DEVICE_APPLE;
case VK_DRIVER_ID_MESA_LLVMPIPE:
return GPU_DEVICE_SOFTWARE;
default:
return GPU_DEVICE_UNKNOWN;
}
return GPU_DEVICE_UNKNOWN;
}
GPUDriverType VKDevice::driver_type() const
{
switch (vk_physical_device_driver_properties_.driverID) {
case VK_DRIVER_ID_AMD_PROPRIETARY:
case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
case VK_DRIVER_ID_QUALCOMM_PROPRIETARY:
/* NOTE: Marking AMDVLK as an official driver to make distinction between Mesa and AMD open
* source. AMDVLK is being replaced by Mesa in the official driver stack. */
case VK_DRIVER_ID_AMD_OPEN_SOURCE:
return GPU_DRIVER_OFFICIAL;
case VK_DRIVER_ID_MOLTENVK:
case VK_DRIVER_ID_MESA_RADV:
case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
case VK_DRIVER_ID_MESA_NVK:
return GPU_DRIVER_OPENSOURCE;
case VK_DRIVER_ID_MESA_LLVMPIPE:
return GPU_DRIVER_SOFTWARE;
default:
return GPU_DRIVER_ANY;
}
return GPU_DRIVER_ANY;
}
std::string VKDevice::vendor_name() const
{
/* Below 0x10000 are the PCI vendor IDs (https://pcisig.com/membership/member-companies) */
if (vk_physical_device_properties_.vendorID < 0x10000) {
switch (vk_physical_device_properties_.vendorID) {
case PCI_ID_AMD:
case PCI_ID_ATI:
return "Advanced Micro Devices";
case PCI_ID_NVIDIA:
return "NVIDIA Corporation";
case PCI_ID_INTEL:
return "Intel Corporation";
case PCI_ID_APPLE:
return "Apple";
default:
return std::to_string(vk_physical_device_properties_.vendorID);
}
}
else {
/* above 0x10000 should be vkVendorIDs
* NOTE: When debug_messaging landed we can use something similar to
* vk::to_string(vk::VendorId(properties.vendorID));
*/
return std::to_string(vk_physical_device_properties_.vendorID);
}
}
std::string VKDevice::driver_version() const
{
return StringRefNull(vk_physical_device_driver_properties_.driverName) + " " +
StringRefNull(vk_physical_device_driver_properties_.driverInfo);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name VKThreadData
* \{ */
VKThreadData::VKThreadData(VKDevice &device, pthread_t thread_id) : thread_id(thread_id)
{
descriptor_pools.init(device);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Resource management
* \{ */
VKThreadData &VKDevice::current_thread_data()
{
std::scoped_lock mutex(resources.mutex);
pthread_t current_thread_id = pthread_self();
for (VKThreadData *thread_data : thread_data_) {
if (pthread_equal(thread_data->thread_id, current_thread_id)) {
return *thread_data;
}
}
VKThreadData *thread_data = new VKThreadData(*this, current_thread_id);
thread_data_.append(thread_data);
return *thread_data;
}
void VKDevice::context_register(VKContext &context)
{
contexts_.append(std::reference_wrapper(context));
}
void VKDevice::context_unregister(VKContext &context)
{
if (context.render_graph_.has_value()) {
render_graph::VKRenderGraph &render_graph = context.render_graph();
context.render_graph_.reset();
BLI_assert_msg(render_graph.is_empty(),
"Unregistering a context that still has an unsubmitted render graph.");
render_graph.reset();
BLI_thread_queue_push(
unused_render_graphs_, &render_graph, BLI_THREAD_QUEUE_WORK_PRIORITY_NORMAL);
}
{
std::scoped_lock lock(orphaned_data.mutex_get());
orphaned_data.move_data(context.discard_pool, timeline_value_ + 1);
}
contexts_.remove(contexts_.first_index_of(std::reference_wrapper(context)));
}
Span<std::reference_wrapper<VKContext>> VKDevice::contexts_get() const
{
return contexts_;
};
void VKDevice::memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) const
{
VmaBudget budgets[VK_MAX_MEMORY_HEAPS];
vmaGetHeapBudgets(mem_allocator_get(), budgets);
VkDeviceSize total_mem = 0;
VkDeviceSize used_mem = 0;
for (int memory_heap_index : IndexRange(vk_physical_device_memory_properties_.memoryHeapCount)) {
const VkMemoryHeap &memory_heap =
vk_physical_device_memory_properties_.memoryHeaps[memory_heap_index];
const VmaBudget &budget = budgets[memory_heap_index];
/* Skip host memory-heaps. */
if (!bool(memory_heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)) {
continue;
}
total_mem += memory_heap.size;
used_mem += budget.usage;
}
*r_total_mem_kb = int(total_mem / 1024);
*r_free_mem_kb = int((total_mem - used_mem) / 1024);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging/statistics
* \{ */
void VKDevice::debug_print() const
{
BLI_assert_msg(BLI_thread_is_main(),
"VKDevice::debug_print can only be called from the main thread.");
resources.debug_print();
std::ostream &os = std::cout;
os << "Pipelines\n";
os << " Graphics: " << pipelines.graphics_.size() << "\n";
os << " Compute: " << pipelines.compute_.size() << "\n";
os << " VertexInLib: " << pipelines.vertex_input_libs_.size() << "\n";
os << " ShaderLib: " << pipelines.shaders_libs_.size() << "\n";
os << " FragmentOutLib: " << pipelines.fragment_output_libs_.size() << "\n";
os << "Descriptor sets\n";
os << " VkDescriptorSetLayouts: " << descriptor_set_layouts_.size() << "\n";
for (const VKThreadData *thread_data : thread_data_) {
/* NOTE: Assumption that this is always called form the main thread. This could be solved by
* keeping track of the main thread inside the thread data. */
const bool is_main = pthread_equal(thread_data->thread_id, pthread_self());
os << "ThreadData" << (is_main ? " (main-thread)" : "") << ")\n";
os << " Rendering_depth: " << thread_data->rendering_depth << "\n";
}
os << "Discard pool\n";
os << orphaned_data << "\n";
os << "Discard pool (render)\n";
os << orphaned_data_render << "\n";
os << "\n";
for (const std::reference_wrapper<VKContext> &context : contexts_) {
os << " VKContext \n";
os << context.get().discard_pool << "\n";
}
int total_mem_kb;
int free_mem_kb;
memory_statistics_get(&total_mem_kb, &free_mem_kb);
os << "\nMemory: total=" << total_mem_kb << ", free=" << free_mem_kb << "\n";
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,500 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <atomic>
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "render_graph/vk_render_graph.hh"
#include "render_graph/vk_resource_state_tracker.hh"
#include "vk_buffer.hh"
#include "vk_common.hh"
#include "vk_debug.hh"
#include "vk_descriptor_pools.hh"
#include "vk_descriptor_set_layouts.hh"
#include "vk_memory_pool.hh"
#include "vk_pipeline_pool.hh"
#include "vk_resource_pool.hh"
#include "vk_samplers.hh"
#include "vk_vertex_attribute_object.hh"
namespace blender::gpu {
class VKBackend;
struct VKExtensions {
/** Does the device support VkPhysicalDeviceVulkan12Features::shaderOutputViewportIndex. */
bool shader_output_viewport_index = false;
/** Does the device support VkPhysicalDeviceVulkan12Features::shaderOutputLayer. */
bool shader_output_layer = false;
/**
* Does the device support
* VkPhysicalDeviceFragmentShaderBarycentricFeaturesKHR::fragmentShaderBarycentric.
*/
bool fragment_shader_barycentric = false;
/**
* Does the device support wide line rendering
* VkPhysicalDeviceFeatures::wideLines
*/
bool wide_lines = false;
/**
* Does the device support VK_KHR_dynamic_rendering_local_read enabled.
*/
bool dynamic_rendering_local_read = false;
/**
* Does the device support VK_EXT_dynamic_rendering_unused_attachments.
*/
bool dynamic_rendering_unused_attachments = false;
/**
* Does the device support VK_EXT_external_memory_win32/VK_EXT_external_memory_fd
*/
bool external_memory = false;
/** VK_KHR_maintenance4 */
bool maintenance4 = false;
/**
* Does the device support logic ops.
*/
bool logic_ops = false;
/**
* Does the device support VK_EXT_memory_priority
*/
bool memory_priority = false;
/**
* Does the device support VK_EXT_pageable_device_local_memory
*/
bool pageable_device_local_memory = false;
/**
* Does the device support VK_EXT_graphics_pipeline_library
*/
bool graphics_pipeline_library = false;
/**
* Does the device support VK_EXT_line_rasterization
*/
bool line_rasterization = false;
/**
* Does the device support VK_EXT_extended_dynamic_state
*/
bool extended_dynamic_state = false;
/**
* Does the device support VK_EXT_vertex_input_dynamic_state
*/
bool vertex_input_dynamic_state = false;
/**
* Does the device support VK_EXT_host_image_copy
*/
bool host_image_copy = false;
/** Log enabled features and extensions. */
void log() const;
};
struct VKWorkarounds {
/**
* Some devices don't support pixel formats that are aligned to 24 and 48 bits.
* In this case we need to use a different texture format.
*
* If set to true we should work around this issue by using a different texture format.
*/
bool not_aligned_pixel_formats = false;
/** Log enabled workarounds. */
void log() const;
};
/**
* Shared resources between contexts that run in the same thread.
*/
class VKThreadData : public NonCopyable, NonMovable {
public:
/** Thread ID this instance belongs to. */
pthread_t thread_id;
VKDescriptorPools descriptor_pools;
VKDescriptorSetTracker descriptor_set;
/**
* The current rendering depth.
*
* GPU_rendering_begin can be called multiple times forming a hierarchy. The same resource pool
* should be used for the whole hierarchy. rendering_depth is increased for every
* GPU_rendering_begin and decreased when GPU_rendering_end is called. Resources pools are cycled
* when the rendering_depth set to 0.
*/
int32_t rendering_depth = 0;
VKThreadData(VKDevice &device, pthread_t thread_id);
};
class VKDevice : public NonCopyable {
private:
/** Copies of the handles owned by the GHOST context. */
VkInstance vk_instance_ = VK_NULL_HANDLE;
VkPhysicalDevice vk_physical_device_ = VK_NULL_HANDLE;
VkDevice vk_device_ = VK_NULL_HANDLE;
uint32_t vk_queue_family_ = 0;
VkQueue vk_queue_ = VK_NULL_HANDLE;
std::mutex *queue_mutex_ = nullptr;
bool is_initialized_ = false;
/**
* Task pool for render graph submission.
*
* Multiple threads in Blender can build a render graph. Building the command buffer for a render
* graph is faster when doing it in serial. Submission pool ensures that only one task is
* building at a time (background_serial).
*/
TaskPool *submission_pool_ = nullptr;
/**
* All created render graphs.
*/
Vector<render_graph::VKRenderGraph *> render_graphs_;
ThreadQueue *submitted_render_graphs_ = nullptr;
ThreadQueue *unused_render_graphs_ = nullptr;
VkSemaphore vk_timeline_semaphore_ = VK_NULL_HANDLE;
/**
* Last used timeline value.
*
* Must be externally synced by orphaned_data.mutex_get()
*/
TimelineValue timeline_value_ = 0;
VKSamplers samplers_;
VKDescriptorSetLayouts descriptor_set_layouts_;
/**
* Available Contexts for this device.
*
* Device keeps track of each contexts. When buffers/images are freed they need to be removed
* from all contexts state managers.
*
* The contexts inside this list aren't owned by the VKDevice. Caller of `GPU_context_create`
* holds the ownership.
*/
Vector<std::reference_wrapper<VKContext>> contexts_;
/** Allocator used for texture and buffers and other resources. */
VmaAllocator mem_allocator_ = VK_NULL_HANDLE;
/** Limits of the device linked to this context. */
VkPhysicalDeviceProperties vk_physical_device_properties_ = {};
VkPhysicalDeviceDriverProperties vk_physical_device_driver_properties_ = {};
VkPhysicalDeviceIDProperties vk_physical_device_id_properties_ = {};
VkPhysicalDeviceMemoryProperties vk_physical_device_memory_properties_ = {};
VkPhysicalDeviceMaintenance4Properties vk_physical_device_maintenance4_properties_ = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES};
VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT
vk_physical_device_graphics_pipeline_library_properties_ = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT};
/** Features support. */
VkPhysicalDeviceFeatures vk_physical_device_features_ = {};
VkPhysicalDeviceVulkan11Features vk_physical_device_vulkan_11_features_ = {};
VkPhysicalDeviceVulkan12Features vk_physical_device_vulkan_12_features_ = {};
Array<VkExtensionProperties> device_extensions_;
/** Functions of vk_ext_debugutils for this device/instance. */
debug::VKDebuggingTools debugging_tools_;
/* Workarounds */
VKWorkarounds workarounds_;
VKExtensions extensions_;
std::string glsl_vert_patch_;
std::string glsl_geom_patch_;
std::string glsl_frag_patch_;
std::string glsl_comp_patch_;
Vector<VKThreadData *> thread_data_;
Shader *vk_backbuffer_blit_sh_ = nullptr;
public:
render_graph::VKResourceStateTracker resources;
VKDiscardPool orphaned_data;
/** Discard pool for resources that could still be used during rendering. */
VKDiscardPool orphaned_data_render;
VKPipelinePool pipelines;
VKVertexInputDescriptionPool vertex_input_descriptions;
/** Buffer to bind to unbound resource locations. */
VKBuffer dummy_buffer;
/**
* This struct contains the functions pointer to extension provided functions.
*/
struct {
/* Extension: VK_KHR_dynamic_rendering */
PFN_vkCmdBeginRendering vkCmdBeginRendering = nullptr;
PFN_vkCmdEndRendering vkCmdEndRendering = nullptr;
/* Extension: VK_EXT_debug_utils */
PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabel = nullptr;
PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabel = nullptr;
PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectName = nullptr;
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessenger = nullptr;
PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessenger = nullptr;
/* Extension: VK_EXT_extended_dynamic_state */
PFN_vkCmdSetFrontFace vkCmdSetFrontFace = nullptr;
/* Extension: VK_EXT_vertex_input_dynamic_state */
PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInput = nullptr;
/* Extension: VK_KHR_external_memory_fd */
PFN_vkGetMemoryFdKHR vkGetMemoryFd = nullptr;
/* Extension: VK_EXT_host_image_copy */
PFN_vkCopyMemoryToImageEXT vkCopyMemoryToImage = nullptr;
PFN_vkTransitionImageLayoutEXT vkTransitionImageLayout = nullptr;
/* Extension: VK_KHR_maintenance4 */
PFN_vkGetDeviceImageMemoryRequirements vkGetDeviceImageMemoryRequirements = nullptr;
PFN_vkGetDeviceBufferMemoryRequirements vkGetDeviceBufferMemoryRequirements = nullptr;
#ifdef _WIN32
/* Extension: VK_KHR_external_memory_win32 */
PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32Handle = nullptr;
#endif
} functions;
VKMemoryPools vma_pools;
const char *extension_name_get(int index) const
{
return device_extensions_[index].extensionName;
}
VkPhysicalDevice physical_device_get() const
{
return vk_physical_device_;
}
const VkPhysicalDeviceProperties &physical_device_properties_get() const
{
return vk_physical_device_properties_;
}
inline const VkPhysicalDeviceMaintenance4Properties &
physical_device_maintenance4_properties_get() const
{
return vk_physical_device_maintenance4_properties_;
}
const VkPhysicalDeviceIDProperties &physical_device_id_properties_get() const
{
return vk_physical_device_id_properties_;
}
inline const VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT &
physical_device_graphics_pipeline_properties_get()
{
return vk_physical_device_graphics_pipeline_library_properties_;
}
const VkPhysicalDeviceFeatures &physical_device_features_get() const
{
return vk_physical_device_features_;
}
const VkPhysicalDeviceVulkan11Features &physical_device_vulkan_11_features_get() const
{
return vk_physical_device_vulkan_11_features_;
}
const VkPhysicalDeviceVulkan12Features &physical_device_vulkan_12_features_get() const
{
return vk_physical_device_vulkan_12_features_;
}
VkInstance instance_get() const
{
return vk_instance_;
};
VkDevice vk_handle() const
{
return vk_device_;
}
uint32_t queue_family_get() const
{
return vk_queue_family_;
}
inline VmaAllocator mem_allocator_get() const
{
return mem_allocator_;
}
VKDescriptorSetLayouts &descriptor_set_layouts_get()
{
return descriptor_set_layouts_;
}
debug::VKDebuggingTools &debugging_tools_get()
{
return debugging_tools_;
}
const debug::VKDebuggingTools &debugging_tools_get() const
{
return debugging_tools_;
}
const VKSamplers &samplers() const
{
return samplers_;
}
void init(GHOST_IContext *ghost_context);
void reinit();
void deinit();
bool is_initialized() const
{
return is_initialized_;
}
GPUDeviceType device_type() const;
GPUDriverType driver_type() const;
std::string vendor_name() const;
std::string driver_version() const;
/**
* Check if a specific extension is supported by the device.
*
* This should be called from vk_backend to set the correct capabilities and workarounds needed
* for this device.
*/
bool supports_extension(const char *extension_name) const;
const VKWorkarounds &workarounds_get() const
{
return workarounds_;
}
inline const VKExtensions &extensions_get() const
{
return extensions_;
}
std::string glsl_vertex_patch_get() const;
std::string glsl_geometry_patch_get() const;
std::string glsl_fragment_patch_get() const;
std::string glsl_compute_patch_get() const;
shader::GeneratedSource extensions_define(StringRefNull stage_define) const;
/* -------------------------------------------------------------------- */
/** \name Render graph
* \{ */
static void submission_runner(TaskPool *__restrict pool, void *task_data);
render_graph::VKRenderGraph *render_graph_new();
TimelineValue render_graph_submit(render_graph::VKRenderGraph *render_graph,
VKDiscardPool &context_discard_pool,
bool submit_to_device,
bool wait_for_submission,
bool wait_for_completion,
VkPipelineStageFlags wait_dst_stage_mask,
VkSemaphore wait_semaphore,
VkSemaphore signal_semaphore,
VkFence signal_fence);
void wait_for_timeline(TimelineValue timeline);
void wait_queue_idle();
/**
* Retrieve the last finished submission timeline.
*/
TimelineValue submission_finished_timeline_get() const
{
BLI_assert(vk_timeline_semaphore_ != VK_NULL_HANDLE);
TimelineValue current_timeline;
VkResult result = vkGetSemaphoreCounterValue(
vk_device_, vk_timeline_semaphore_, &current_timeline);
UNUSED_VARS(result);
BLI_assert_msg(
result == VK_SUCCESS && current_timeline != UINT64_MAX,
"Potential driver crash has happened. Several drivers will report UINT64_MAX when "
"requesting a counter value of an timeline semaphore right after/during a driver reset. "
"If this happen we should investigate what makes the driver crash. In the past this has "
"been detected on QUALCOMM and NVIDIA drivers. The result code of the call is "
"VK_SUCCESS.");
return current_timeline;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Resource management
* \{ */
/**
* Get or create current thread data.
*/
VKThreadData &current_thread_data();
void context_register(VKContext &context);
void context_unregister(VKContext &context);
Span<std::reference_wrapper<VKContext>> contexts_get() const;
void memory_statistics_get(int *r_total_mem_kb, int *r_free_mem_kb) const;
void debug_print() const;
/** \} */
Shader *vk_backbuffer_blit_sh_get()
{
if (vk_backbuffer_blit_sh_ == nullptr) {
/* See #system_extended_srgb_transfer_function in libocio_display_processor.cc for
* details on this choice. */
#if defined(_WIN32) || defined(__APPLE__)
vk_backbuffer_blit_sh_ = GPU_shader_create_from_info_name("vk_backbuffer_blit");
#else
vk_backbuffer_blit_sh_ = GPU_shader_create_from_info_name("vk_backbuffer_blit_gamma22");
#endif
}
return vk_backbuffer_blit_sh_;
}
private:
void init_physical_device_properties();
void init_physical_device_memory_properties();
void init_physical_device_features();
void init_physical_device_extensions();
void init_debug_callbacks();
void init_submission_pool();
void deinit_submission_pool();
/**
* Initialize the functions struct with extension specific function pointer.
*/
void init_functions();
/**
* Initialize a dummy buffer that can be bound for missing attributes.
*/
void init_dummy_buffer();
/* During initialization the backend requires access to update the workarounds. */
friend VKBackend;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,325 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <chrono>
#include <condition_variable>
#include <thread>
#include "BLI_mutex.hh"
#include "BLI_task.h"
#include "vk_device.hh"
#include "vk_to_string.hh"
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
/* -------------------------------------------------------------------- */
/** \name Render graph
* \{ */
struct VKRenderGraphWait {
Mutex is_submitted_mutex;
std::condition_variable_any is_submitted_condition;
bool is_submitted;
};
struct VKRenderGraphSubmitTask {
render_graph::VKRenderGraph *render_graph;
uint64_t timeline;
bool submit_to_device;
VkPipelineStageFlags wait_dst_stage_mask;
VkSemaphore wait_semaphore;
VkSemaphore signal_semaphore;
VkFence signal_fence;
VKRenderGraphWait *wait_for_submission;
};
TimelineValue VKDevice::render_graph_submit(render_graph::VKRenderGraph *render_graph,
VKDiscardPool &context_discard_pool,
bool submit_to_device,
bool wait_for_submission,
bool wait_for_completion,
VkPipelineStageFlags wait_dst_stage_mask,
VkSemaphore wait_semaphore,
VkSemaphore signal_semaphore,
VkFence signal_fence)
{
/* Syncing input flags. */
/* When we wait for completion/submission we must submit to device. */
submit_to_device |= wait_for_completion;
submit_to_device |= wait_for_submission;
/* We don't need to wait for submission when waiting for completion. */
wait_for_submission &= !wait_for_completion;
VKRenderGraphSubmitTask *submit_task = MEM_new<VKRenderGraphSubmitTask>(__func__);
submit_task->render_graph = render_graph;
submit_task->submit_to_device = submit_to_device;
submit_task->wait_dst_stage_mask = wait_dst_stage_mask;
submit_task->wait_semaphore = wait_semaphore;
submit_task->signal_semaphore = signal_semaphore;
submit_task->signal_fence = signal_fence;
submit_task->wait_for_submission = nullptr;
VKRenderGraphWait wait_condition{};
if (wait_for_submission) {
submit_task->wait_for_submission = &wait_condition;
}
TimelineValue timeline = 0;
{
std::scoped_lock lock(orphaned_data.mutex_get());
timeline = submit_task->timeline = submit_to_device ? ++timeline_value_ : timeline_value_ + 1;
orphaned_data.timeline_ = timeline;
orphaned_data.move_data(context_discard_pool, timeline);
BLI_thread_queue_push(
submitted_render_graphs_, submit_task, BLI_THREAD_QUEUE_WORK_PRIORITY_NORMAL);
}
submit_task = nullptr;
if (wait_for_submission) {
std::unique_lock<Mutex> lock(wait_condition.is_submitted_mutex);
wait_condition.is_submitted_condition.wait(lock, [&] { return wait_condition.is_submitted; });
}
if (wait_for_completion) {
wait_for_timeline(timeline);
}
return timeline;
}
void VKDevice::wait_for_timeline(TimelineValue timeline)
{
if (timeline == 0) {
return;
}
VkSemaphoreWaitInfo vk_semaphore_wait_info = {
VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, nullptr, 0, 1, &vk_timeline_semaphore_, &timeline};
VkResult wait_result = vkWaitSemaphores(vk_device_, &vk_semaphore_wait_info, UINT64_MAX);
if (wait_result != VK_SUCCESS) {
CLOG_ERROR(
&LOG, "Vulkan: failed to wait for synchronization timeline [%s]", to_string(wait_result));
}
}
void VKDevice::wait_queue_idle()
{
std::scoped_lock lock(*queue_mutex_);
vkQueueWaitIdle(vk_queue_);
}
render_graph::VKRenderGraph *VKDevice::render_graph_new()
{
render_graph::VKRenderGraph *render_graph = static_cast<render_graph::VKRenderGraph *>(
BLI_thread_queue_pop_timeout(unused_render_graphs_, 0));
if (render_graph) {
return render_graph;
}
std::scoped_lock lock(resources.mutex);
render_graph = MEM_new<render_graph::VKRenderGraph>(__func__, resources);
render_graphs_.append(render_graph);
return render_graph;
}
void VKDevice::submission_runner(TaskPool *__restrict pool, void *task_data)
{
CLOG_TRACE(&LOG, "Submission runner has started");
UNUSED_VARS(task_data);
VKDevice *device = static_cast<VKDevice *>(BLI_task_pool_user_data(pool));
VkCommandPool vk_command_pool = VK_NULL_HANDLE;
VkCommandPoolCreateInfo vk_command_pool_create_info = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
nullptr,
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
device->vk_queue_family_};
vkCreateCommandPool(device->vk_device_, &vk_command_pool_create_info, nullptr, &vk_command_pool);
render_graph::VKScheduler scheduler;
render_graph::VKCommandBuilder command_builder;
Vector<VkCommandBuffer> command_buffers_unused;
TimelineResources<VkCommandBuffer> command_buffers_in_use;
VkCommandBuffer vk_command_buffer = VK_NULL_HANDLE;
std::optional<render_graph::VKCommandBufferWrapper> command_buffer;
uint64_t previous_gc_timeline = 0;
uint64_t num_nodes = 0;
CLOG_TRACE(&LOG, "Submission runner initialized");
while (!BLI_task_pool_current_canceled(pool)) {
VKRenderGraphSubmitTask *submit_task = static_cast<VKRenderGraphSubmitTask *>(
BLI_thread_queue_pop_timeout(device->submitted_render_graphs_, 1));
if (submit_task == nullptr) {
continue;
}
uint64_t current_timeline = device->submission_finished_timeline_get();
if (assign_if_different(previous_gc_timeline, current_timeline)) {
device->orphaned_data.destroy_discarded_resources(*device, current_timeline);
}
if (!command_buffer.has_value()) {
/* Check for completed command buffers that can be reused. */
if (command_buffers_unused.is_empty()) {
command_buffers_in_use.remove_old(current_timeline,
[&](VkCommandBuffer vk_command_buffer) {
command_buffers_unused.append(vk_command_buffer);
});
}
/* Create new command buffers when there are no left to be reused. */
if (command_buffers_unused.is_empty()) {
command_buffers_unused.resize(10, VK_NULL_HANDLE);
VkCommandBufferAllocateInfo vk_command_buffer_allocate_info = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
nullptr,
vk_command_pool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
10};
vkAllocateCommandBuffers(
device->vk_device_, &vk_command_buffer_allocate_info, command_buffers_unused.data());
};
vk_command_buffer = command_buffers_unused.pop_last();
command_buffer = std::make_optional<render_graph::VKCommandBufferWrapper>(
vk_command_buffer, device->extensions_);
command_buffer->begin_recording();
}
BLI_assert(vk_command_buffer != VK_NULL_HANDLE);
render_graph::VKRenderGraph &render_graph = *submit_task->render_graph;
Span<render_graph::NodeHandle> node_handles = scheduler.select_nodes(render_graph);
{
std::scoped_lock lock_resources(device->resources.mutex);
command_builder.build_nodes(render_graph, *command_buffer, node_handles);
}
command_builder.record_commands(render_graph, *command_buffer, node_handles);
num_nodes += node_handles.size();
if (submit_task->submit_to_device) {
/* Finalize current command buffer. */
command_buffer->end_recording();
uint32_t wait_semaphore_len = submit_task->wait_semaphore == VK_NULL_HANDLE ? 1 : 2;
VkSemaphore wait_semaphores[2] = {device->vk_timeline_semaphore_,
submit_task->wait_semaphore};
uint64_t wait_semaphore_values[2] = {submit_task->timeline - 1, 0};
uint32_t signal_semaphore_len = submit_task->signal_semaphore == VK_NULL_HANDLE ? 1 : 2;
VkSemaphore signal_semaphores[2] = {device->vk_timeline_semaphore_,
submit_task->signal_semaphore};
uint64_t signal_semaphore_values[2] = {submit_task->timeline, 0};
VkPipelineStageFlags pipeline_stage_flags[2] = {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
submit_task->wait_dst_stage_mask};
VkTimelineSemaphoreSubmitInfo vk_timeline_semaphore_submit_info = {
VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
nullptr,
wait_semaphore_len,
wait_semaphore_values,
signal_semaphore_len,
signal_semaphore_values};
VkSubmitInfo vk_submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO,
&vk_timeline_semaphore_submit_info,
wait_semaphore_len,
wait_semaphores,
pipeline_stage_flags,
1,
&vk_command_buffer,
signal_semaphore_len,
signal_semaphores};
CLOG_TRACE(&LOG, "Submitting %u render graph nodes to device.", uint32_t(num_nodes));
num_nodes = 0;
{
std::scoped_lock lock_queue(*device->queue_mutex_);
vkQueueSubmit(device->vk_queue_, 1, &vk_submit_info, submit_task->signal_fence);
}
if (submit_task->wait_for_submission != nullptr) {
std::unique_lock<Mutex> lock(submit_task->wait_for_submission->is_submitted_mutex);
submit_task->wait_for_submission->is_submitted = true;
submit_task->wait_for_submission->is_submitted_condition.notify_one();
}
command_buffers_in_use.append_timeline(submit_task->timeline, vk_command_buffer);
vk_command_buffer = VK_NULL_HANDLE;
command_buffer.reset();
}
render_graph.reset();
BLI_thread_queue_push(device->unused_render_graphs_,
std::move(submit_task->render_graph),
BLI_THREAD_QUEUE_WORK_PRIORITY_NORMAL);
MEM_delete<VKRenderGraphSubmitTask>(submit_task);
}
CLOG_TRACE(&LOG, "Submission runner is being canceled");
/* Clear command buffers and pool */
{
std::scoped_lock lock(*device->queue_mutex_);
vkDeviceWaitIdle(device->vk_device_);
}
command_buffers_in_use.remove_old(UINT64_MAX, [&](VkCommandBuffer vk_command_buffer) {
command_buffers_unused.append(vk_command_buffer);
});
vkFreeCommandBuffers(device->vk_device_,
vk_command_pool,
command_buffers_unused.size(),
command_buffers_unused.data());
vkDestroyCommandPool(device->vk_device_, vk_command_pool, nullptr);
CLOG_TRACE(&LOG, "Submission runner finished");
}
void VKDevice::init_submission_pool()
{
CLOG_TRACE(&LOG, "Create submission pool");
submission_pool_ = BLI_task_pool_create_background_serial(this, TASK_PRIORITY_HIGH);
submitted_render_graphs_ = BLI_thread_queue_init();
unused_render_graphs_ = BLI_thread_queue_init();
VkSemaphoreTypeCreateInfo vk_semaphore_type_create_info = {
VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, nullptr, VK_SEMAPHORE_TYPE_TIMELINE, 0};
VkSemaphoreCreateInfo vk_semaphore_create_info = {
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, &vk_semaphore_type_create_info, 0};
vkCreateSemaphore(vk_device_, &vk_semaphore_create_info, nullptr, &vk_timeline_semaphore_);
BLI_task_pool_push(submission_pool_, VKDevice::submission_runner, nullptr, false, nullptr);
}
void VKDevice::deinit_submission_pool()
{
CLOG_TRACE(&LOG, "Cancelling submission pool");
BLI_task_pool_cancel(submission_pool_);
CLOG_TRACE(&LOG, "Waiting for completion");
BLI_task_pool_work_and_wait(submission_pool_);
CLOG_TRACE(&LOG, "Freeing submission pool");
BLI_task_pool_free(submission_pool_);
submission_pool_ = nullptr;
while (!BLI_thread_queue_is_empty(submitted_render_graphs_)) {
VKRenderGraphSubmitTask *submit_task = static_cast<VKRenderGraphSubmitTask *>(
BLI_thread_queue_pop(submitted_render_graphs_));
MEM_delete<VKRenderGraphSubmitTask>(submit_task);
}
BLI_thread_queue_free(submitted_render_graphs_);
submitted_render_graphs_ = nullptr;
BLI_thread_queue_free(unused_render_graphs_);
unused_render_graphs_ = nullptr;
vkDestroySemaphore(vk_device_, vk_timeline_semaphore_, nullptr);
vk_timeline_semaphore_ = VK_NULL_HANDLE;
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_fence.hh"
#include "vk_backend.hh"
#include "vk_common.hh"
#include "vk_context.hh"
namespace blender::gpu {
void VKFence::signal()
{
VKContext &context = *VKContext::get();
timeline_value_ = context.flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
signalled_ = true;
}
void VKFence::wait()
{
if (!signalled_) {
return;
}
VKDevice &device = VKBackend::get().device;
device.wait_for_timeline(timeline_value_);
timeline_value_ = 0;
signalled_ = false;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_state_private.hh"
#include "vk_common.hh"
namespace blender::gpu {
class VKFence : public Fence {
private:
TimelineValue timeline_value_;
public:
void signal() override;
void wait() override;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,792 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_framebuffer.hh"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_state_manager.hh"
#include "vk_texture.hh"
namespace blender::gpu {
/**
* The default load store action when not using load stores.
*/
constexpr GPULoadStore default_load_store()
{
return {GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE, {0.0f, 0.0f, 0.0f, 0.0f}};
}
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
VKFrameBuffer::VKFrameBuffer(const char *name)
: FrameBuffer(name),
load_stores(GPU_FB_MAX_ATTACHMENT, default_load_store()),
attachment_states_(GPU_FB_MAX_ATTACHMENT, GPU_ATTACHMENT_WRITE)
{
size_set(1, 1);
srgb_ = false;
enabled_srgb_ = false;
}
VKFrameBuffer::~VKFrameBuffer()
{
VKContext *context = VKContext::get();
if (context && context->active_framebuffer_get() == this) {
context->deactivate_framebuffer();
}
}
/** \} */
void VKFrameBuffer::bind(bool enabled_srgb)
{
VKContext &context = *VKContext::get();
/* Updating attachments can issue pipeline barriers, this should be done outside the render pass.
* When done inside a render pass there should be a self-dependency between sub-passes on the
* active render pass. As the active render pass isn't aware of the new render pass (and should
* not) it is better to deactivate it before updating the attachments. For more information check
* `VkSubpassDependency`. */
if (context.has_active_framebuffer()) {
context.deactivate_framebuffer();
}
context.activate_framebuffer(*this);
update_size();
viewport_reset();
scissor_reset();
enabled_srgb_ = enabled_srgb;
Shader::set_framebuffer_srgb_target(enabled_srgb && srgb_);
load_stores.fill(default_load_store());
attachment_states_.fill(GPU_ATTACHMENT_WRITE);
}
uint32_t VKFrameBuffer::viewport_size() const
{
return this->multi_viewport_ ? GPU_MAX_VIEWPORTS : 1;
}
void VKFrameBuffer::vk_viewports_append(Vector<VkViewport> &r_viewports) const
{
BLI_assert(r_viewports.is_empty());
for (int64_t index : IndexRange(this->multi_viewport_ ? GPU_MAX_VIEWPORTS : 1)) {
VkViewport viewport;
viewport.x = viewport_[index][0];
viewport.y = viewport_[index][1];
viewport.width = viewport_[index][2];
viewport.height = viewport_[index][3];
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
r_viewports.append(viewport);
}
}
void VKFrameBuffer::render_area_update(VkRect2D &render_area) const
{
if (scissor_test_get()) {
int scissor_rect[4];
scissor_get(scissor_rect);
render_area.offset.x = clamp_i(scissor_rect[0], 0, width_);
render_area.offset.y = clamp_i(scissor_rect[1], 0, height_);
render_area.extent.width = clamp_i(scissor_rect[2], 1, width_ - scissor_rect[0]);
render_area.extent.height = clamp_i(scissor_rect[3], 1, height_ - scissor_rect[1]);
}
else {
render_area.offset.x = 0;
render_area.offset.y = 0;
render_area.extent.width = width_;
render_area.extent.height = height_;
}
#ifndef NDEBUG
const VKDevice &device = VKBackend::get().device;
BLI_assert(render_area.offset.x + render_area.extent.width <=
device.physical_device_properties_get().limits.maxFramebufferWidth);
BLI_assert(render_area.offset.y + render_area.extent.height <=
device.physical_device_properties_get().limits.maxFramebufferHeight);
#endif
}
void VKFrameBuffer::vk_render_areas_append(Vector<VkRect2D> &r_render_areas) const
{
BLI_assert(r_render_areas.is_empty());
VkRect2D render_area;
render_area_update(render_area);
r_render_areas.append_n_times(render_area, this->multi_viewport_ ? GPU_MAX_VIEWPORTS : 1);
}
bool VKFrameBuffer::check(char /*err_out*/[256])
{
return true;
}
void VKFrameBuffer::build_clear_attachments_depth_stencil(
const GPUFrameBufferBits buffers,
float clear_depth,
uint32_t clear_stencil,
render_graph::VKClearAttachmentsNode::CreateInfo &clear_attachments) const
{
VkImageAspectFlags aspect_mask = (buffers & GPU_DEPTH_BIT ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) |
(buffers & GPU_STENCIL_BIT ? VK_IMAGE_ASPECT_STENCIL_BIT : 0);
VkClearAttachment &clear_attachment =
clear_attachments.attachments[clear_attachments.attachment_count++];
clear_attachment.aspectMask = aspect_mask;
clear_attachment.clearValue.depthStencil.depth = clear_depth;
clear_attachment.clearValue.depthStencil.stencil = clear_stencil;
clear_attachment.colorAttachment = 0;
}
void VKFrameBuffer::build_clear_attachments_color(
Span<double4> clear_colors,
const bool multi_clear_colors,
render_graph::VKClearAttachmentsNode::CreateInfo &clear_attachments) const
{
int color_index = 0;
for (int color_slot = 0; color_slot < GPU_FB_MAX_COLOR_ATTACHMENT; color_slot++) {
const GPUAttachment &attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + color_slot];
if (attachment.tex == nullptr) {
continue;
}
VkClearAttachment &clear_attachment =
clear_attachments.attachments[clear_attachments.attachment_count++];
clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clear_attachment.colorAttachment = color_slot;
eGPUDataFormat data_format = to_texture_data_format(GPU_texture_format(attachment.tex));
clear_attachment.clearValue.color = to_vk_clear_color_value(data_format,
clear_colors[color_index]);
color_index += multi_clear_colors ? 1 : 0;
}
}
/* -------------------------------------------------------------------- */
/** \name Clear
* \{ */
void VKFrameBuffer::clear(render_graph::VKClearAttachmentsNode::CreateInfo &clear_attachments)
{
VKContext &context = *VKContext::get();
rendering_ensure(context);
context.render_graph().add_node(clear_attachments);
}
void VKFrameBuffer::clear(const GPUFrameBufferBits buffers,
const double4 clear_color,
float clear_depth,
uint clear_stencil)
{
render_graph::VKClearAttachmentsNode::CreateInfo clear_attachments = {};
render_area_update(clear_attachments.vk_clear_rect.rect);
clear_attachments.vk_clear_rect.baseArrayLayer = 0;
clear_attachments.vk_clear_rect.layerCount = 1;
if (buffers & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)) {
VKContext &context = *VKContext::get();
GPUWriteMask needed_mask = GPU_WRITE_NONE;
if (buffers & GPU_DEPTH_BIT) {
needed_mask |= GPU_WRITE_DEPTH;
}
if (buffers & GPU_STENCIL_BIT) {
needed_mask |= GPU_WRITE_STENCIL;
}
/* Clearing depth via #vkCmdClearAttachments requires a render pass with write depth or stencil
* enabled. When not enabled, clearing should be done via texture directly. */
/* WORKAROUND: Clearing depth attachment when using dynamic rendering are not working on AMD
* official drivers.
* See #129265 */
if ((context.state_manager_get().state.write_mask & needed_mask) == needed_mask &&
!GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_OFFICIAL))
{
build_clear_attachments_depth_stencil(
buffers, clear_depth, clear_stencil, clear_attachments);
}
else {
const GPUAttachment &attachment = depth_attachment();
VKTexture *depth_texture = unwrap(unwrap(attachment.tex));
if (depth_texture != nullptr) {
depth_texture->clear_depth_stencil(
buffers,
clear_depth,
clear_stencil,
attachment.layer == -1 ? std::nullopt : std::make_optional(attachment.layer));
}
}
}
if (buffers & GPU_COLOR_BIT) {
double4 clear_color_single = double4(clear_color);
build_clear_attachments_color({&clear_color_single, 1}, false, clear_attachments);
}
if (clear_attachments.attachment_count) {
clear(clear_attachments);
}
}
void VKFrameBuffer::clear_multi(Span<double4> clear_cols)
{
render_graph::VKClearAttachmentsNode::CreateInfo clear_attachments = {};
render_area_update(clear_attachments.vk_clear_rect.rect);
clear_attachments.vk_clear_rect.baseArrayLayer = 0;
clear_attachments.vk_clear_rect.layerCount = 1;
build_clear_attachments_color(clear_cols, true, clear_attachments);
if (clear_attachments.attachment_count) {
clear(clear_attachments);
}
}
void VKFrameBuffer::clear_attachment(GPUAttachmentType /*type*/, const double4 /*clear_value*/)
{
/* Clearing of a single attachment was added to implement `clear_multi` in OpenGL. As
* `clear_multi` is supported in Vulkan it isn't needed to implement this method.
*/
BLI_assert_unreachable();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Load/Store operations
* \{ */
void VKFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls)
{
load_stores[type] = ls;
}
static VkAttachmentLoadOp to_vk_attachment_load_op(GPULoadOp load_op)
{
switch (load_op) {
case GPU_LOADACTION_DONT_CARE:
return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
case GPU_LOADACTION_CLEAR:
return VK_ATTACHMENT_LOAD_OP_CLEAR;
case GPU_LOADACTION_LOAD:
return VK_ATTACHMENT_LOAD_OP_LOAD;
}
BLI_assert_unreachable();
return VK_ATTACHMENT_LOAD_OP_LOAD;
}
static VkAttachmentStoreOp to_vk_attachment_store_op(GPUStoreOp store_op)
{
switch (store_op) {
case GPU_STOREACTION_DONT_CARE:
return VK_ATTACHMENT_STORE_OP_DONT_CARE;
case GPU_STOREACTION_STORE:
return VK_ATTACHMENT_STORE_OP_STORE;
}
BLI_assert_unreachable();
return VK_ATTACHMENT_STORE_OP_STORE;
}
static void set_load_store(VkRenderingAttachmentInfo &r_rendering_attachment,
eGPUDataFormat data_format,
const GPULoadStore &ls)
{
r_rendering_attachment.clearValue.color = to_vk_clear_color_value(data_format, ls.clear_value);
r_rendering_attachment.loadOp = to_vk_attachment_load_op(ls.load_action);
r_rendering_attachment.storeOp = to_vk_attachment_store_op(ls.store_action);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sub-pass transition
* \{ */
void VKFrameBuffer::subpass_transition_impl(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states)
{
const VKDevice &device = VKBackend::get().device;
const bool supports_local_read = device.extensions_get().dynamic_rendering_local_read;
attachment_states_[GPU_FB_DEPTH_ATTACHMENT] = depth_attachment_state;
attachment_states_.as_mutable_span()
.slice(GPU_FB_COLOR_ATTACHMENT0, color_attachment_states.size())
.copy_from(color_attachment_states);
if (supports_local_read) {
VKContext &context = *VKContext::get();
rendering_ensure(context);
for (int index : IndexRange(color_attachment_states.size())) {
if (color_attachment_states[index] == GPU_ATTACHMENT_READ) {
VKTexture *texture = unwrap(unwrap(color_tex(index)));
if (texture) {
context.state_manager_get().image_bind(texture, index);
}
}
}
is_rendering_ = false;
}
else {
VKContext &context = *VKContext::get();
rendering_end(context);
for (int index : IndexRange(color_attachment_states.size())) {
if (color_attachment_states[index] == GPU_ATTACHMENT_READ) {
VKTexture *texture = unwrap(unwrap(color_tex(index)));
if (texture) {
context.state_manager_get().texture_bind(
texture, GPUSamplerState::default_sampler(), index);
}
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Read back
* \{ */
void VKFrameBuffer::read(GPUFrameBufferBits plane,
eGPUDataFormat format,
const int area[4],
int /*channel_len*/,
int slot,
void *r_data)
{
GPUAttachment *attachment = nullptr;
switch (plane) {
case GPU_COLOR_BIT:
attachment = &attachments_[GPU_FB_COLOR_ATTACHMENT0 + slot];
break;
case GPU_DEPTH_BIT:
attachment = attachments_[GPU_FB_DEPTH_ATTACHMENT].tex ?
&attachments_[GPU_FB_DEPTH_ATTACHMENT] :
&attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT];
break;
default:
BLI_assert_unreachable();
return;
}
VKTexture *texture = unwrap(unwrap(attachment->tex));
BLI_assert_msg(texture,
"Trying to read back texture from framebuffer, but no texture is available in "
"requested slot.");
if (texture == nullptr) {
return;
}
const int region[6] = {area[0], area[1], 0, area[0] + area[2], area[1] + area[3], 1};
IndexRange layers(max_ii(attachment->layer, 0), 1);
texture->read_sub(0, format, region, layers, r_data);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Blit operations
* \{ */
static void blit_aspect(VKContext &context,
VKTexture &dst_texture,
VKTexture &src_texture,
int dst_offset_x,
int dst_offset_y,
VkImageAspectFlags image_aspect)
{
/* Prefer texture copy, as some platforms don't support using D32_SFLOAT_S8_UINT to be used as
* a blit destination. */
if (dst_offset_x == 0 && dst_offset_y == 0 &&
dst_texture.device_format_get() == src_texture.device_format_get() &&
src_texture.width_get() == dst_texture.width_get() &&
src_texture.height_get() == dst_texture.height_get())
{
src_texture.copy_to(dst_texture, IndexRange(1), image_aspect);
return;
}
render_graph::VKBlitImageNode::CreateInfo blit_image = {};
blit_image.src_image = src_texture.vk_image_handle();
blit_image.dst_image = dst_texture.vk_image_handle();
blit_image.filter = VK_FILTER_NEAREST;
VkImageBlit &region = blit_image.region;
region.srcSubresource.aspectMask = image_aspect;
region.srcSubresource.mipLevel = 0;
region.srcSubresource.baseArrayLayer = 0;
region.srcSubresource.layerCount = 1;
region.srcOffsets[0].x = 0;
region.srcOffsets[0].y = 0;
region.srcOffsets[0].z = 0;
region.srcOffsets[1].x = src_texture.width_get();
region.srcOffsets[1].y = src_texture.height_get();
region.srcOffsets[1].z = 1;
region.dstSubresource.aspectMask = image_aspect;
region.dstSubresource.mipLevel = 0;
region.dstSubresource.baseArrayLayer = 0;
region.dstSubresource.layerCount = 1;
region.dstOffsets[0].x = clamp_i(dst_offset_x, 0, dst_texture.width_get());
region.dstOffsets[0].y = clamp_i(dst_offset_y, 0, dst_texture.height_get());
region.dstOffsets[0].z = 0;
region.dstOffsets[1].x = clamp_i(
dst_offset_x + src_texture.width_get(), 0, dst_texture.width_get());
region.dstOffsets[1].y = clamp_i(
dst_offset_y + src_texture.height_get(), 0, dst_texture.height_get());
region.dstOffsets[1].z = 1;
/* Early exit when no pixels needs to be blitted. This should never occur, but has happened
* during development as retina displays are not yet detected and the intermediate back-buffer
* would be to small, resulting in cropping the full blit source image. */
if (region.dstOffsets[0].x == region.dstOffsets[1].x ||
region.dstOffsets[0].y == region.dstOffsets[1].y ||
region.dstOffsets[0].z == region.dstOffsets[1].z)
{
return;
}
context.render_graph().add_node(blit_image);
}
void VKFrameBuffer::blit_to(GPUFrameBufferBits planes,
int src_slot,
FrameBuffer *dst,
int dst_slot,
int dst_offset_x,
int dst_offset_y)
{
BLI_assert(dst);
BLI_assert_msg(ELEM(planes, GPU_COLOR_BIT, GPU_DEPTH_BIT),
"VKFrameBuffer::blit_to only supports a single color or depth aspect.");
UNUSED_VARS_NDEBUG(planes);
VKContext &context = *VKContext::get();
if (!context.has_active_framebuffer()) {
BLI_assert_unreachable();
return;
}
VKFrameBuffer &dst_framebuffer = *unwrap(dst);
if (planes & GPU_COLOR_BIT) {
const GPUAttachment &src_attachment = attachments_[GPU_FB_COLOR_ATTACHMENT0 + src_slot];
const GPUAttachment &dst_attachment =
dst_framebuffer.attachments_[GPU_FB_COLOR_ATTACHMENT0 + dst_slot];
if (src_attachment.tex && dst_attachment.tex) {
VKTexture &src_texture = *unwrap(unwrap(src_attachment.tex));
VKTexture &dst_texture = *unwrap(unwrap(dst_attachment.tex));
blit_aspect(context,
dst_texture,
src_texture,
dst_offset_x,
dst_offset_y,
VK_IMAGE_ASPECT_COLOR_BIT);
}
}
if (planes & GPU_DEPTH_BIT) {
/* Retrieve source texture. */
const GPUAttachment &src_attachment = attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex ?
attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT] :
attachments_[GPU_FB_DEPTH_ATTACHMENT];
const GPUAttachment &dst_attachment =
dst_framebuffer.attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex ?
dst_framebuffer.attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT] :
dst_framebuffer.attachments_[GPU_FB_DEPTH_ATTACHMENT];
if (src_attachment.tex && dst_attachment.tex) {
VKTexture &src_texture = *unwrap(unwrap(src_attachment.tex));
VKTexture &dst_texture = *unwrap(unwrap(dst_attachment.tex));
blit_aspect(context,
dst_texture,
src_texture,
dst_offset_x,
dst_offset_y,
VK_IMAGE_ASPECT_DEPTH_BIT);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Update attachments
* \{ */
void VKFrameBuffer::update_size()
{
if (!dirty_attachments_) {
return;
}
for (int i = 0; i < GPU_FB_MAX_ATTACHMENT; i++) {
GPUAttachment &attachment = attachments_[i];
if (attachment.tex) {
int size[3];
GPU_texture_get_mipmap_size(attachment.tex, attachment.mip, size);
size_set(size[0], size[1]);
return;
}
}
}
void VKFrameBuffer::update_srgb()
{
for (int i : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) {
VKTexture *texture = unwrap(unwrap(color_tex(i)));
if (texture) {
srgb_ = (texture->format_flag_get() & GPU_FORMAT_SRGB) != 0;
return;
}
}
}
int VKFrameBuffer::color_attachments_resource_size() const
{
int size = 0;
for (int color_slot : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) {
if (color_tex(color_slot) != nullptr) {
size = max_ii(color_slot + 1, size);
}
}
return size;
}
/** \} */
void VKFrameBuffer::rendering_reset()
{
is_rendering_ = false;
}
void VKFrameBuffer::rendering_ensure_dynamic_rendering(VKContext &context,
const VKExtensions &extensions)
{
const VKDevice &device = VKBackend::get().device;
const bool supports_local_read = device.extensions_get().dynamic_rendering_local_read;
depth_attachment_format_ = VK_FORMAT_UNDEFINED;
stencil_attachment_format_ = VK_FORMAT_UNDEFINED;
render_graph::VKResourceAccessInfo access_info;
render_graph::VKBeginRenderingNode::CreateInfo begin_rendering(access_info);
begin_rendering.node_data.vk_rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
begin_rendering.node_data.vk_rendering_info.layerCount = 1;
render_area_update(begin_rendering.node_data.vk_rendering_info.renderArea);
color_attachment_formats_.clear();
int32_t max_filled_slot_index = -1;
for (int color_attachment_index :
IndexRange(GPU_FB_COLOR_ATTACHMENT0, GPU_FB_MAX_COLOR_ATTACHMENT))
{
const GPUAttachment &attachment = attachments_[color_attachment_index];
if (attachment.tex == nullptr) {
color_attachment_formats_.append(VK_FORMAT_UNDEFINED);
VkRenderingAttachmentInfo &attachment_info =
begin_rendering.node_data.color_attachments[begin_rendering.node_data.vk_rendering_info
.colorAttachmentCount++];
attachment_info = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
nullptr,
VK_NULL_HANDLE,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_RESOLVE_MODE_NONE,
VK_NULL_HANDLE,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE};
continue;
}
max_filled_slot_index = color_attachment_index - GPU_FB_COLOR_ATTACHMENT0;
VKTexture &color_texture = *unwrap(unwrap(attachment.tex));
BLI_assert_msg(color_texture.usage_get() & GPU_TEXTURE_USAGE_ATTACHMENT,
"Texture is used as an attachment, but doesn't have the "
"GPU_TEXTURE_USAGE_ATTACHMENT flag.");
/* To support `gpu_Layer` we need to set the layerCount to the number of layers it can
* access.
*/
int layer_count = color_texture.layer_count();
if (attachment.layer == -1 && layer_count != 1) {
begin_rendering.node_data.vk_rendering_info.layerCount = max_ii(
begin_rendering.node_data.vk_rendering_info.layerCount, layer_count);
}
VkRenderingAttachmentInfo &attachment_info =
begin_rendering.node_data
.color_attachments[begin_rendering.node_data.vk_rendering_info.colorAttachmentCount++];
attachment_info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
VkImageView vk_image_view = VK_NULL_HANDLE;
uint32_t layer_base = max_ii(attachment.layer, 0);
GPUAttachmentState attachment_state = attachment_states_[color_attachment_index];
VkFormat vk_format = to_vk_format(color_texture.device_format_get());
if (attachment_state == GPU_ATTACHMENT_WRITE) {
VKImageViewInfo image_view_info = {
eImageViewUsage::Attachment,
IndexRange(layer_base,
layer_count != 1 ? max_ii(layer_count - layer_base, 1) : layer_count),
IndexRange(attachment.mip, 1),
{{'r', 'g', 'b', 'a'}},
false,
srgb_ && enabled_srgb_,
VKImageViewArrayed::DONT_CARE};
const VKImageView &image_view = color_texture.image_view_get(image_view_info);
vk_image_view = image_view.vk_handle();
vk_format = image_view.vk_format();
}
attachment_info.imageView = vk_image_view;
attachment_info.imageLayout = supports_local_read ? VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ_KHR :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
eGPUDataFormat data_format = to_texture_data_format(color_texture.format_get());
set_load_store(attachment_info, data_format, load_stores[color_attachment_index]);
access_info.images.append(
{color_texture.vk_image_handle(),
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT,
{uint32_t(attachment.mip),
1,
layer_base,
uint32_t(max_ii(layer_count - layer_base, 1))}});
color_attachment_formats_.append(
(!extensions.dynamic_rendering_unused_attachments && vk_image_view == VK_NULL_HANDLE) ?
VK_FORMAT_UNDEFINED :
vk_format);
}
uint32_t color_attachment_size = uint32_t(max_filled_slot_index + 1);
color_attachment_formats_.resize(color_attachment_size);
begin_rendering.node_data.vk_rendering_info.colorAttachmentCount = color_attachment_size;
begin_rendering.node_data.vk_rendering_info.pColorAttachments =
begin_rendering.node_data.color_attachments;
for (int depth_attachment_index : IndexRange(GPU_FB_DEPTH_ATTACHMENT, 2)) {
const GPUAttachment &attachment = attachments_[depth_attachment_index];
if (attachment.tex == nullptr) {
continue;
}
bool is_stencil_attachment = depth_attachment_index == GPU_FB_DEPTH_STENCIL_ATTACHMENT;
VKTexture &depth_texture = *unwrap(unwrap(attachment.tex));
BLI_assert_msg(depth_texture.usage_get() & GPU_TEXTURE_USAGE_ATTACHMENT,
"Texture is used as an attachment, but doesn't have the "
"GPU_TEXTURE_USAGE_ATTACHMENT flag.");
bool is_depth_stencil_attachment = to_vk_image_aspect_flag_bits(
depth_texture.device_format_get()) &
VK_IMAGE_ASPECT_STENCIL_BIT;
VkImageLayout vk_image_layout = is_depth_stencil_attachment ?
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL :
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
GPUAttachmentState attachment_state = attachment_states_[GPU_FB_DEPTH_ATTACHMENT];
VkImageView depth_image_view = VK_NULL_HANDLE;
if (attachment_state == GPU_ATTACHMENT_WRITE) {
VKImageViewInfo image_view_info = {eImageViewUsage::Attachment,
IndexRange(max_ii(attachment.layer, 0), 1),
IndexRange(attachment.mip, 1),
{{'r', 'g', 'b', 'a'}},
is_stencil_attachment,
false,
VKImageViewArrayed::DONT_CARE};
depth_image_view = depth_texture.image_view_get(image_view_info).vk_handle();
}
VkFormat vk_format = (!extensions.dynamic_rendering_unused_attachments &&
depth_image_view == VK_NULL_HANDLE) ?
VK_FORMAT_UNDEFINED :
to_vk_format(depth_texture.device_format_get());
/* TODO: we should be able to use a single attachment info and only set the
* #pDepthAttachment/#pStencilAttachment to the same struct.
* But perhaps the stencil clear op might be different. */
{
VkRenderingAttachmentInfo &attachment_info = begin_rendering.node_data.depth_attachment;
attachment_info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment_info.imageView = depth_image_view;
attachment_info.imageLayout = vk_image_layout;
set_load_store(attachment_info, GPU_DATA_FLOAT, load_stores[depth_attachment_index]);
depth_attachment_format_ = vk_format;
begin_rendering.node_data.vk_rendering_info.pDepthAttachment =
&begin_rendering.node_data.depth_attachment;
}
if (is_stencil_attachment) {
VkRenderingAttachmentInfo &attachment_info = begin_rendering.node_data.stencil_attachment;
attachment_info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment_info.imageView = depth_image_view;
attachment_info.imageLayout = vk_image_layout;
set_load_store(attachment_info, GPU_DATA_UINT, load_stores[depth_attachment_index]);
stencil_attachment_format_ = vk_format;
begin_rendering.node_data.vk_rendering_info.pStencilAttachment =
&begin_rendering.node_data.stencil_attachment;
}
access_info.images.append(
{depth_texture.vk_image_handle(),
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
is_depth_stencil_attachment ?
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT |
VK_IMAGE_ASPECT_STENCIL_BIT) :
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT),
{uint32_t(attachment.mip), 1, uint32_t(max_ii(attachment.layer, 0)), 1}});
break;
}
context.render_graph().add_node(begin_rendering);
}
void VKFrameBuffer::rendering_ensure(VKContext &context)
{
if (!dirty_state_ && is_rendering_) {
return;
}
if (is_rendering_) {
rendering_end(context);
}
const VKExtensions &extensions = VKBackend::get().device.extensions_get();
is_rendering_ = true;
rendering_ensure_dynamic_rendering(context, extensions);
dirty_attachments_ = false;
dirty_state_ = false;
use_explicit_load_store_ = false;
load_stores.fill(default_load_store());
}
VkFormat VKFrameBuffer::depth_attachment_format_get() const
{
return depth_attachment_format_;
}
VkFormat VKFrameBuffer::stencil_attachment_format_get() const
{
return stencil_attachment_format_;
};
Span<VkFormat> VKFrameBuffer::color_attachment_formats_get() const
{
return color_attachment_formats_.as_span();
}
void VKFrameBuffer::rendering_end(VKContext &context)
{
if (!is_rendering_ && use_explicit_load_store_) {
rendering_ensure(context);
}
if (is_rendering_) {
render_graph::VKEndRenderingNode::CreateInfo end_rendering = {};
context.render_graph().add_node(end_rendering);
is_rendering_ = false;
}
}
} // namespace blender::gpu

View File

@@ -0,0 +1,154 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_array.hh"
#include "BLI_math_vector.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include "gpu_framebuffer_private.hh"
#include "render_graph/vk_render_graph.hh"
#include "vk_common.hh"
#include "vk_image_view.hh"
namespace blender::gpu {
class VKContext;
class VKFrameBuffer : public FrameBuffer {
private:
/** Is the first attachment an SRGB texture. */
bool srgb_;
bool enabled_srgb_;
bool is_rendering_ = false;
VkFormat depth_attachment_format_ = VK_FORMAT_UNDEFINED;
VkFormat stencil_attachment_format_ = VK_FORMAT_UNDEFINED;
Vector<VkFormat> color_attachment_formats_;
Array<GPULoadStore, GPU_FB_MAX_ATTACHMENT> load_stores;
Array<GPUAttachmentState, GPU_FB_MAX_ATTACHMENT> attachment_states_;
public:
/**
* Create a conventional frame-buffer to attach texture to.
*/
VKFrameBuffer(const char *name);
virtual ~VKFrameBuffer();
void bind(bool enabled_srgb) override;
bool check(char err_out[256]) override;
void clear(GPUFrameBufferBits buffers,
const double4 clear_color,
float clear_depth,
uint clear_stencil) override;
void clear_multi(Span<double4> clear_cols) override;
void clear_attachment(GPUAttachmentType type, const double4 clear_value) override;
void attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore /*ls*/) override;
protected:
void subpass_transition_impl(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states) override;
public:
void read(GPUFrameBufferBits planes,
eGPUDataFormat format,
const int area[4],
int channel_len,
int slot,
void *r_data) override;
void blit_to(GPUFrameBufferBits planes,
int src_slot,
FrameBuffer *dst,
int dst_slot,
int dst_offset_x,
int dst_offset_y) override;
uint32_t viewport_size() const;
void vk_viewports_append(Vector<VkViewport> &r_viewports) const;
void vk_render_areas_append(Vector<VkRect2D> &r_render_areas) const;
void render_area_update(VkRect2D &render_area) const;
VkFormat depth_attachment_format_get() const;
VkFormat stencil_attachment_format_get() const;
/**
* \brief Get color attachment formats in used by the framebuffer.
*/
Span<VkFormat> color_attachment_formats_get() const;
/**
* Ensure that the size of the frame-buffer matches the first attachment resolution.
*
* Frame buffers attachments are updated when actually used as the image layout has to be
* correct. After binding frame-buffers the layout of images can still be modified.
*
* But for correct behavior of blit/clear operation the size of the frame-buffer should be
* set, when activating the frame buffer.
*/
void update_size();
void update_srgb();
/**
* Mark this framebuffer to be not being rendered on.
*
* Between binding a framebuffer and actually using it the state and clear operations can change.
* The rendering state is used to find out if the framebuffer begin rendering command should be
* recorded
*/
void rendering_reset();
/**
* Ensure that the framebuffer is ready to be rendered on and that its state is up to date with
* the latest changes that can happen between drawing commands inside `VKStateManager`.
*/
void rendering_ensure(VKContext &context);
void rendering_ensure_dynamic_rendering(VKContext &context, const VKExtensions &extensions);
/**
* End the rendering on this framebuffer.
* Is being triggered when framebuffer is deactivated or when
*/
void rendering_end(VKContext &context);
bool is_rendering() const
{
return is_rendering_;
}
/**
* Return the number of color attachments of this frame buffer, including unused color
* attachments.
*
* Frame-buffers can have unused attachments. When higher attachment slots are being used, unused
* lower attachment slots will be counted as they are required resources in render-passes.
*/
int color_attachments_resource_size() const;
private:
/* Clearing attachments */
void build_clear_attachments_depth_stencil(
GPUFrameBufferBits buffers,
float clear_depth,
uint32_t clear_stencil,
render_graph::VKClearAttachmentsNode::CreateInfo &clear_attachments) const;
void build_clear_attachments_color(
Span<double4> clear_colors,
const bool multi_clear_colors,
render_graph::VKClearAttachmentsNode::CreateInfo &clear_attachments) const;
void clear(render_graph::VKClearAttachmentsNode::CreateInfo &clear_attachments);
};
static inline VKFrameBuffer *unwrap(gpu::FrameBuffer *framebuffer)
{
return static_cast<VKFrameBuffer *>(framebuffer);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
/** This file contains API that the GHOST_ContextVK can invoke directly. */
namespace blender::gpu {
/**
* Is the driver of the given physical device supported?
*
* There are some drivers that have known issues and should not be used. This check needs to be
* identical between GPU module and GHOST, otherwise GHOST can still select a device which isn't
* supported.
*
* For example on a Linux machine where LLVMPIPE is installed and an not supported NVIDIA driver
* Blender would detect a supported configuration using LLVMPIPE, but GHOST could still select the
* unsupported NVIDIA driver.
*
* Returns true when supported, false when not supported.
*/
bool GPU_vulkan_is_supported_driver(VkPhysicalDevice vk_physical_device);
const char *to_string(VkResult result);
} // namespace blender::gpu

View File

@@ -0,0 +1,766 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* To create graphics pipelines multiple structs are needed. With graphics pipeline libraries only
* parts of these structs doesn't need to be filled. This header file ensures that all code to
* create `VkGraphicsPipelineCreateInfo` and related structs are grouped and the different
* configurations can be created.
*/
/* TODO: separate in the different configuration and add a main configuration that includes all.
* Unsure yet if how to organize this in structs to keep stack allocation small. */
#pragma once
#include "vk_common.hh"
#include "vk_device.hh"
#include "vk_pipeline_pool.hh"
#include "vk_vertex_attribute_object.hh"
namespace blender::gpu {
struct VKGraphicsPipelineCreateInfoBuilder {
VkPipelineRenderingCreateInfo vk_pipeline_rendering_create_info;
std::array<VkPipelineShaderStageCreateInfo, 3> vk_pipeline_shader_stage_create_info;
VkSpecializationInfo vk_specialization_info;
Array<VkSpecializationMapEntry> vk_specialization_map_entries;
VkPipelineInputAssemblyStateCreateInfo vk_pipeline_input_assembly_state_create_info;
VkPipelineVertexInputStateCreateInfo vk_pipeline_vertex_input_state_create_info;
VkPipelineRasterizationStateCreateInfo vk_pipeline_rasterization_state_create_info;
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT
vk_pipeline_rasterization_provoking_vertex_state_info;
VkPipelineRasterizationLineStateCreateInfoEXT vk_pipeline_rasterization_line_state_info;
Vector<VkDynamicState, 7> vk_dynamic_states;
VkPipelineDynamicStateCreateInfo vk_pipeline_dynamic_state_create_info;
VkPipelineViewportStateCreateInfo vk_pipeline_viewport_state_create_info;
VkPipelineDepthStencilStateCreateInfo vk_pipeline_depth_stencil_state_create_info;
VkPipelineMultisampleStateCreateInfo vk_pipeline_multisample_state_create_info;
Vector<VkPipelineColorBlendAttachmentState> vk_pipeline_color_blend_attachment_states;
VkPipelineColorBlendStateCreateInfo vk_pipeline_color_blend_state_create_info;
VkGraphicsPipelineCreateInfo vk_graphics_pipeline_create_info;
VkGraphicsPipelineLibraryCreateInfoEXT vk_graphics_pipeline_library_create_info;
/* Only used when VK_EXT_vertex_input_dynamic_state isn't supported. */
Vector<VkVertexInputBindingDescription> vk_vertex_input_binding_descriptions;
Vector<VkVertexInputAttributeDescription> vk_vertex_input_attribute_descriptions;
/**
* When VK_EXT_dynamic_rendering_local_read is used and the shader declares input attachments,
* the shaders library pipeline must declare enough color attachments and provide
* VkRenderingInputAttachmentIndexInfo to satisfy the input attachment index constraint.
*/
VkRenderingInputAttachmentIndexInfo vk_rendering_input_attachment_index_info_;
Vector<VkFormat> dummy_color_attachment_formats_;
/**
* Initialize graphics pipeline create info and related structs for a full pipeline build.
*/
void build_full(VKDevice &device,
const VKGraphicsInfo &graphics_info,
VkPipeline vk_pipeline_base)
{
const VKExtensions &extensions = device.extensions_get();
build_graphics_pipeline(extensions, graphics_info, vk_pipeline_base);
build_input_assembly_state(graphics_info.vertex_in);
if (!extensions.vertex_input_dynamic_state) {
build_vertex_input_state(device, graphics_info.vertex_in);
}
build_shader_stages(graphics_info.shaders);
const bool do_specialization_constants =
!graphics_info.shaders.specialization_constants.is_empty();
if (do_specialization_constants) {
build_specialization_constants(graphics_info.shaders);
}
build_dynamic_state(graphics_info.shaders, extensions);
build_multisample_state();
build_viewport_state(graphics_info.shaders);
build_rasterization_state(graphics_info.shaders, extensions);
build_depth_stencil_state(graphics_info.shaders);
build_color_blend_attachment_states(graphics_info.fragment_out);
build_color_blend_state(graphics_info.fragment_out, extensions);
build_dynamic_rendering(graphics_info.fragment_out);
}
/**
* Initialize graphics pipeline create info and related structs for a vertex input library build.
*/
void build_vertex_input_lib(VKDevice &device,
const VKGraphicsInfo::VertexIn &vertex_input_info,
VkPipeline vk_pipeline_base)
{
const VKExtensions &extensions = device.extensions_get();
build_graphics_pipeline_library(VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT);
build_graphics_pipeline_vertex_input_lib(vk_pipeline_base, extensions);
build_input_assembly_state(vertex_input_info);
build_dynamic_state_for_vertex_input(extensions);
if (!extensions.vertex_input_dynamic_state) {
build_vertex_input_state(device, vertex_input_info);
}
}
/**
* Initialize graphics pipeline create info and related structs for a shaders library build.
*/
void build_shaders_lib(const VKGraphicsInfo::Shaders &shaders_info,
const VKExtensions &extensions,
VkPipeline vk_pipeline_base)
{
build_graphics_pipeline_library(
VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT |
VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT);
build_graphics_pipeline_shaders_lib(shaders_info, vk_pipeline_base);
build_shader_stages(shaders_info);
const bool do_specialization_constants = !shaders_info.specialization_constants.is_empty();
if (do_specialization_constants) {
build_specialization_constants(shaders_info);
}
build_dynamic_state(shaders_info, extensions);
build_multisample_state();
build_viewport_state(shaders_info);
build_rasterization_state(shaders_info, extensions);
build_depth_stencil_state(shaders_info);
build_dynamic_rendering_shaders_lib(extensions, shaders_info.max_input_attachment_index);
}
/**
* Initialize graphics pipeline create info and related structs for a fragment output library
* build.
*/
void build_fragment_output_lib(const VKGraphicsInfo::FragmentOut &fragment_output_info,
const VKExtensions &extensions,
VkPipeline vk_pipeline_base)
{
build_graphics_pipeline_library(
VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT);
build_graphics_pipeline_fragment_output_lib(vk_pipeline_base);
build_multisample_state();
build_color_blend_attachment_states(fragment_output_info);
build_color_blend_state(fragment_output_info, extensions);
build_dynamic_rendering(fragment_output_info);
}
private:
void build_graphics_pipeline(const VKExtensions &extensions,
const VKGraphicsInfo &graphics_info,
VkPipeline vk_pipeline_base)
{
vk_graphics_pipeline_create_info = {};
vk_graphics_pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
vk_graphics_pipeline_create_info.pNext = &vk_pipeline_rendering_create_info;
vk_graphics_pipeline_create_info.stageCount = graphics_info.shaders.vk_geometry_module ==
VK_NULL_HANDLE ?
2 :
3;
vk_graphics_pipeline_create_info.pStages = vk_pipeline_shader_stage_create_info.data();
vk_graphics_pipeline_create_info.pInputAssemblyState =
&vk_pipeline_input_assembly_state_create_info;
vk_graphics_pipeline_create_info.pVertexInputState =
extensions.vertex_input_dynamic_state ? nullptr :
&vk_pipeline_vertex_input_state_create_info;
vk_graphics_pipeline_create_info.pRasterizationState =
&vk_pipeline_rasterization_state_create_info;
vk_graphics_pipeline_create_info.pDepthStencilState =
&vk_pipeline_depth_stencil_state_create_info;
vk_graphics_pipeline_create_info.pDynamicState = &vk_pipeline_dynamic_state_create_info;
vk_graphics_pipeline_create_info.pViewportState = &vk_pipeline_viewport_state_create_info;
vk_graphics_pipeline_create_info.pMultisampleState =
&vk_pipeline_multisample_state_create_info;
vk_graphics_pipeline_create_info.pColorBlendState = &vk_pipeline_color_blend_state_create_info;
vk_graphics_pipeline_create_info.layout = graphics_info.shaders.vk_pipeline_layout;
vk_graphics_pipeline_create_info.basePipelineHandle = vk_pipeline_base;
}
void build_graphics_pipeline_vertex_input_lib(VkPipeline vk_pipeline_base,
const VKExtensions &extensions)
{
vk_graphics_pipeline_create_info = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
&vk_graphics_pipeline_library_create_info,
VK_PIPELINE_CREATE_LIBRARY_BIT_KHR |
VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT,
0,
nullptr,
extensions.vertex_input_dynamic_state ? nullptr :
&vk_pipeline_vertex_input_state_create_info,
&vk_pipeline_input_assembly_state_create_info,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&vk_pipeline_dynamic_state_create_info,
VK_NULL_HANDLE,
VK_NULL_HANDLE,
0,
vk_pipeline_base,
0};
}
void build_graphics_pipeline_shaders_lib(const VKGraphicsInfo::Shaders &shaders_info,
VkPipeline vk_pipeline_base)
{
vk_graphics_pipeline_create_info = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
&vk_graphics_pipeline_library_create_info,
VK_PIPELINE_CREATE_LIBRARY_BIT_KHR |
VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT,
shaders_info.vk_geometry_module == VK_NULL_HANDLE ? 2u : 3u,
vk_pipeline_shader_stage_create_info.data(),
nullptr,
nullptr,
nullptr,
&vk_pipeline_viewport_state_create_info,
&vk_pipeline_rasterization_state_create_info,
&vk_pipeline_multisample_state_create_info,
&vk_pipeline_depth_stencil_state_create_info,
nullptr,
&vk_pipeline_dynamic_state_create_info,
shaders_info.vk_pipeline_layout,
VK_NULL_HANDLE,
0,
vk_pipeline_base,
0};
vk_graphics_pipeline_library_create_info.pNext = &vk_pipeline_rendering_create_info;
}
void build_graphics_pipeline_fragment_output_lib(VkPipeline vk_pipeline_base)
{
vk_graphics_pipeline_create_info = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
&vk_graphics_pipeline_library_create_info,
VK_PIPELINE_CREATE_LIBRARY_BIT_KHR |
VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT,
0,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&vk_pipeline_multisample_state_create_info,
nullptr,
&vk_pipeline_color_blend_state_create_info,
nullptr,
VK_NULL_HANDLE,
VK_NULL_HANDLE,
0,
vk_pipeline_base,
0};
vk_graphics_pipeline_library_create_info.pNext = &vk_pipeline_rendering_create_info;
}
void build_graphics_pipeline_library(VkGraphicsPipelineLibraryFlagsEXT flags)
{
vk_graphics_pipeline_library_create_info = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, nullptr, flags};
}
void build_shader_stages(const VKGraphicsInfo::Shaders &shaders_info)
{
vk_pipeline_shader_stage_create_info[0] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr,
0,
VK_SHADER_STAGE_VERTEX_BIT,
shaders_info.vk_vertex_module,
"main",
nullptr};
vk_pipeline_shader_stage_create_info[1] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr,
0,
VK_SHADER_STAGE_FRAGMENT_BIT,
shaders_info.vk_fragment_module,
"main",
nullptr};
vk_pipeline_shader_stage_create_info[2] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr,
0,
VK_SHADER_STAGE_GEOMETRY_BIT,
shaders_info.vk_geometry_module,
"main",
nullptr};
}
void build_specialization_constants(const VKGraphicsInfo::Shaders &shaders_info)
{
vk_specialization_map_entries.reinitialize(shaders_info.specialization_constants.size());
for (int index : IndexRange(3)) {
vk_pipeline_shader_stage_create_info[index].pSpecializationInfo = &vk_specialization_info;
}
for (uint32_t index : IndexRange(shaders_info.specialization_constants.size())) {
vk_specialization_map_entries[index] = {
index, uint32_t(index * sizeof(uint32_t)), sizeof(uint32_t)};
}
vk_specialization_info = {uint32_t(vk_specialization_map_entries.size()),
vk_specialization_map_entries.data(),
shaders_info.specialization_constants.size() * sizeof(uint32_t),
shaders_info.specialization_constants.data()};
}
void build_multisample_state()
{
vk_pipeline_multisample_state_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
nullptr,
0,
VK_SAMPLE_COUNT_1_BIT,
VK_FALSE,
1.0f,
nullptr,
VK_FALSE,
VK_FALSE};
}
void build_viewport_state(const VKGraphicsInfo::Shaders &shaders_info)
{
vk_pipeline_viewport_state_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
nullptr,
0,
shaders_info.viewport_count,
nullptr,
shaders_info.viewport_count,
nullptr};
}
void build_input_assembly_state(const VKGraphicsInfo::VertexIn &vertex_input_info)
{
vk_pipeline_input_assembly_state_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
vk_pipeline_input_assembly_state_create_info.topology = vertex_input_info.vk_topology;
vk_pipeline_input_assembly_state_create_info.primitiveRestartEnable =
ELEM(vertex_input_info.vk_topology,
VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY) ?
VK_FALSE :
VK_TRUE;
}
void build_vertex_input_state(VKDevice &device,
const VKGraphicsInfo::VertexIn &vertex_input_info)
{
BLI_assert_msg(!device.extensions_get().vertex_input_dynamic_state,
"No need to set vertex input state as dynamic state is supported.");
const VKVertexInputDescription &description = device.vertex_input_descriptions.get(
vertex_input_info.vertex_input_key);
vk_pipeline_vertex_input_state_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
vk_vertex_input_attribute_descriptions.reserve(description.attributes.size());
vk_vertex_input_binding_descriptions.reserve(description.bindings.size());
for (const VkVertexInputAttributeDescription2EXT &attr : description.attributes) {
vk_vertex_input_attribute_descriptions.append(
{attr.location, attr.binding, attr.format, attr.offset});
}
for (const VkVertexInputBindingDescription2EXT &bind : description.bindings) {
vk_vertex_input_binding_descriptions.append({bind.binding, bind.stride, bind.inputRate});
}
vk_pipeline_vertex_input_state_create_info.pVertexAttributeDescriptions =
vk_vertex_input_attribute_descriptions.data();
vk_pipeline_vertex_input_state_create_info.vertexAttributeDescriptionCount =
vk_vertex_input_attribute_descriptions.size();
vk_pipeline_vertex_input_state_create_info.pVertexBindingDescriptions =
vk_vertex_input_binding_descriptions.data();
vk_pipeline_vertex_input_state_create_info.vertexBindingDescriptionCount =
vk_vertex_input_binding_descriptions.size();
}
void build_rasterization_state(const VKGraphicsInfo::Shaders &shaders_info,
const VKExtensions &extensions)
{
vk_pipeline_rasterization_provoking_vertex_state_info = {};
vk_pipeline_rasterization_provoking_vertex_state_info.sType =
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT;
vk_pipeline_rasterization_provoking_vertex_state_info.provokingVertexMode =
VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;
vk_pipeline_rasterization_provoking_vertex_state_info.provokingVertexMode =
shaders_info.state.provoking_vert == GPU_VERTEX_LAST ?
VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT :
VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
vk_pipeline_rasterization_state_create_info = {};
vk_pipeline_rasterization_state_create_info.sType =
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
vk_pipeline_rasterization_state_create_info.lineWidth = 1.0f;
vk_pipeline_rasterization_state_create_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
vk_pipeline_rasterization_state_create_info.pNext =
&vk_pipeline_rasterization_provoking_vertex_state_info;
vk_pipeline_rasterization_state_create_info.cullMode = to_vk_cull_mode_flags(
static_cast<GPUFaceCullTest>(shaders_info.state.culling_test));
if (!extensions.extended_dynamic_state && !shaders_info.state.invert_facing) {
vk_pipeline_rasterization_state_create_info.frontFace = VK_FRONT_FACE_CLOCKWISE;
}
if (extensions.line_rasterization) {
/* Request use of Bresenham algorithm if supported. */
vk_pipeline_rasterization_line_state_info = {};
vk_pipeline_rasterization_line_state_info.sType =
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT;
vk_pipeline_rasterization_line_state_info.lineRasterizationMode =
VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT;
vk_pipeline_rasterization_line_state_info.stippledLineEnable = VK_FALSE;
vk_pipeline_rasterization_line_state_info.lineStippleFactor = 0u;
vk_pipeline_rasterization_line_state_info.lineStipplePattern = 0u;
vk_pipeline_rasterization_line_state_info.pNext =
vk_pipeline_rasterization_state_create_info.pNext;
vk_pipeline_rasterization_state_create_info.pNext =
&vk_pipeline_rasterization_line_state_info;
}
}
void build_dynamic_state(const VKGraphicsInfo::Shaders &shaders_info,
const VKExtensions &extensions)
{
vk_dynamic_states = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
const bool is_line_topology = ELEM(shaders_info.vk_topology,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP);
if (is_line_topology) {
vk_dynamic_states.append(VK_DYNAMIC_STATE_LINE_WIDTH);
}
if (shaders_info.has_stencil && shaders_info.state.stencil_test != GPU_STENCIL_NONE) {
vk_dynamic_states.append(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK);
vk_dynamic_states.append(VK_DYNAMIC_STATE_STENCIL_REFERENCE);
vk_dynamic_states.append(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK);
}
if (extensions.extended_dynamic_state) {
vk_dynamic_states.append(VK_DYNAMIC_STATE_FRONT_FACE);
}
if (extensions.vertex_input_dynamic_state) {
vk_dynamic_states.append(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
}
vk_pipeline_dynamic_state_create_info = {VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
nullptr,
0,
uint32_t(vk_dynamic_states.size()),
vk_dynamic_states.data()};
}
void build_dynamic_state_for_vertex_input(const VKExtensions &extensions)
{
if (extensions.vertex_input_dynamic_state) {
vk_dynamic_states = {VK_DYNAMIC_STATE_VERTEX_INPUT_EXT};
}
vk_pipeline_dynamic_state_create_info = {VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
nullptr,
0,
uint32_t(vk_dynamic_states.size()),
vk_dynamic_states.data()};
}
void build_depth_stencil_state(const VKGraphicsInfo::Shaders &shaders_info)
{
vk_pipeline_depth_stencil_state_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
if (shaders_info.has_depth) {
vk_graphics_pipeline_create_info.pDepthStencilState =
&vk_pipeline_depth_stencil_state_create_info;
vk_pipeline_depth_stencil_state_create_info.depthWriteEnable =
(shaders_info.state.write_mask & GPU_WRITE_DEPTH) ? VK_TRUE : VK_FALSE;
vk_pipeline_depth_stencil_state_create_info.depthTestEnable = VK_TRUE;
switch (shaders_info.state.depth_test) {
case GPU_DEPTH_LESS:
vk_pipeline_depth_stencil_state_create_info.depthCompareOp = VK_COMPARE_OP_LESS;
break;
case GPU_DEPTH_LESS_EQUAL:
vk_pipeline_depth_stencil_state_create_info.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
break;
case GPU_DEPTH_EQUAL:
vk_pipeline_depth_stencil_state_create_info.depthCompareOp = VK_COMPARE_OP_EQUAL;
break;
case GPU_DEPTH_GREATER:
vk_pipeline_depth_stencil_state_create_info.depthCompareOp = VK_COMPARE_OP_GREATER;
break;
case GPU_DEPTH_GREATER_EQUAL:
vk_pipeline_depth_stencil_state_create_info.depthCompareOp =
VK_COMPARE_OP_GREATER_OR_EQUAL;
break;
case GPU_DEPTH_ALWAYS:
vk_pipeline_depth_stencil_state_create_info.depthCompareOp = VK_COMPARE_OP_ALWAYS;
break;
case GPU_DEPTH_NONE:
vk_pipeline_depth_stencil_state_create_info.depthTestEnable = VK_FALSE;
vk_pipeline_depth_stencil_state_create_info.depthCompareOp = VK_COMPARE_OP_NEVER;
break;
}
}
if (shaders_info.has_stencil) {
vk_graphics_pipeline_create_info.pDepthStencilState =
&vk_pipeline_depth_stencil_state_create_info;
switch (shaders_info.state.stencil_test) {
case GPU_STENCIL_NEQUAL:
vk_pipeline_depth_stencil_state_create_info.stencilTestEnable = VK_TRUE;
vk_pipeline_depth_stencil_state_create_info.front.compareOp = VK_COMPARE_OP_NOT_EQUAL;
break;
case GPU_STENCIL_EQUAL:
vk_pipeline_depth_stencil_state_create_info.stencilTestEnable = VK_TRUE;
vk_pipeline_depth_stencil_state_create_info.front.compareOp = VK_COMPARE_OP_EQUAL;
break;
case GPU_STENCIL_ALWAYS:
vk_pipeline_depth_stencil_state_create_info.stencilTestEnable = VK_TRUE;
vk_pipeline_depth_stencil_state_create_info.front.compareOp = VK_COMPARE_OP_ALWAYS;
break;
case GPU_STENCIL_NONE:
vk_pipeline_depth_stencil_state_create_info.stencilTestEnable = VK_FALSE;
vk_pipeline_depth_stencil_state_create_info.front.compareOp = VK_COMPARE_OP_ALWAYS;
break;
}
switch (shaders_info.state.stencil_op) {
case GPU_STENCIL_OP_REPLACE:
vk_pipeline_depth_stencil_state_create_info.front.failOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.front.passOp = VK_STENCIL_OP_REPLACE;
vk_pipeline_depth_stencil_state_create_info.front.depthFailOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.back =
vk_pipeline_depth_stencil_state_create_info.front;
break;
case GPU_STENCIL_OP_COUNT_DEPTH_PASS:
vk_pipeline_depth_stencil_state_create_info.front.failOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.front.passOp =
VK_STENCIL_OP_DECREMENT_AND_WRAP;
vk_pipeline_depth_stencil_state_create_info.front.depthFailOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.back =
vk_pipeline_depth_stencil_state_create_info.front;
vk_pipeline_depth_stencil_state_create_info.back.passOp =
VK_STENCIL_OP_INCREMENT_AND_WRAP;
break;
case GPU_STENCIL_OP_COUNT_DEPTH_FAIL:
vk_pipeline_depth_stencil_state_create_info.front.failOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.front.passOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.front.depthFailOp =
VK_STENCIL_OP_INCREMENT_AND_WRAP;
vk_pipeline_depth_stencil_state_create_info.back =
vk_pipeline_depth_stencil_state_create_info.front;
vk_pipeline_depth_stencil_state_create_info.back.depthFailOp =
VK_STENCIL_OP_DECREMENT_AND_WRAP;
break;
case GPU_STENCIL_OP_NONE:
default:
vk_pipeline_depth_stencil_state_create_info.front.failOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.front.passOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.front.depthFailOp = VK_STENCIL_OP_KEEP;
vk_pipeline_depth_stencil_state_create_info.back =
vk_pipeline_depth_stencil_state_create_info.front;
break;
}
}
}
void build_dynamic_rendering(const VKGraphicsInfo::FragmentOut &fragment_output_info)
{
vk_pipeline_rendering_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
nullptr,
0,
uint32_t(fragment_output_info.color_attachment_formats.size()),
fragment_output_info.color_attachment_formats.data(),
fragment_output_info.depth_attachment_format,
fragment_output_info.stencil_attachment_format};
}
/* Shaders lib only requires the view-mask to be set. When dynamic rendering local read is
* used and the shader declares input attachments, we must set colorAttachmentCount to cover
* the input attachment indices and provide VkRenderingInputAttachmentIndexInfo to satisfy the
* VUID-VkGraphicsPipelineCreateInfo-renderPass-09652 constraint. */
void build_dynamic_rendering_shaders_lib(const VKExtensions &extensions,
uint32_t max_input_attachment_index)
{
vk_pipeline_rendering_create_info = {VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO};
if (extensions.dynamic_rendering_local_read && max_input_attachment_index > 0) {
vk_pipeline_rendering_create_info.colorAttachmentCount = max_input_attachment_index + 1;
dummy_color_attachment_formats_.resize(max_input_attachment_index + 1, VK_FORMAT_UNDEFINED);
vk_pipeline_rendering_create_info.pColorAttachmentFormats =
dummy_color_attachment_formats_.data();
vk_rendering_input_attachment_index_info_ = {};
vk_rendering_input_attachment_index_info_.sType =
VK_STRUCTURE_TYPE_RENDERING_INPUT_ATTACHMENT_INDEX_INFO;
vk_rendering_input_attachment_index_info_.colorAttachmentCount = max_input_attachment_index +
1;
vk_pipeline_rendering_create_info.pNext = &vk_rendering_input_attachment_index_info_;
}
}
void build_color_blend_attachment_states(const VKGraphicsInfo::FragmentOut &fragment_output_info)
{
VkPipelineColorBlendAttachmentState attachment_state = {VK_TRUE,
VK_BLEND_FACTOR_DST_ALPHA,
VK_BLEND_FACTOR_ONE,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
VK_BLEND_OP_ADD,
0};
switch (fragment_output_info.state.blend) {
default:
case GPU_BLEND_ALPHA:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
break;
case GPU_BLEND_ALPHA_PREMULT:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
break;
case GPU_BLEND_ADDITIVE:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
break;
/* Factors are not use in min or max mode, but avoid uninitialized values. */;
case GPU_BLEND_MIN:
case GPU_BLEND_MAX:
case GPU_BLEND_SUBTRACT:
case GPU_BLEND_ADDITIVE_PREMULT:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
break;
case GPU_BLEND_MULTIPLY:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
break;
case GPU_BLEND_INVERT:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
break;
case GPU_BLEND_OIT:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
break;
case GPU_BLEND_BACKGROUND:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
break;
case GPU_BLEND_ALPHA_UNDER_PREMUL:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
break;
case GPU_BLEND_CUSTOM:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_SRC1_COLOR;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC1_ALPHA;
break;
case GPU_BLEND_OVERLAY_MASK_FROM_ALPHA:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
break;
case GPU_BLEND_TRANSPARENCY:
attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
break;
}
if (fragment_output_info.state.blend == GPU_BLEND_MIN) {
attachment_state.alphaBlendOp = VK_BLEND_OP_MIN;
attachment_state.colorBlendOp = VK_BLEND_OP_MIN;
}
else if (fragment_output_info.state.blend == GPU_BLEND_MAX) {
attachment_state.alphaBlendOp = VK_BLEND_OP_MAX;
attachment_state.colorBlendOp = VK_BLEND_OP_MAX;
}
else if (fragment_output_info.state.blend == GPU_BLEND_SUBTRACT) {
attachment_state.alphaBlendOp = VK_BLEND_OP_REVERSE_SUBTRACT;
attachment_state.colorBlendOp = VK_BLEND_OP_REVERSE_SUBTRACT;
}
else {
attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
}
if (fragment_output_info.state.blend != GPU_BLEND_NONE) {
attachment_state.blendEnable = VK_TRUE;
}
else {
attachment_state.blendEnable = VK_FALSE;
}
/* Adjust the template with the color components in the write mask. */
if ((fragment_output_info.state.write_mask & GPU_WRITE_RED) != 0) {
attachment_state.colorWriteMask |= VK_COLOR_COMPONENT_R_BIT;
}
if ((fragment_output_info.state.write_mask & GPU_WRITE_GREEN) != 0) {
attachment_state.colorWriteMask |= VK_COLOR_COMPONENT_G_BIT;
}
if ((fragment_output_info.state.write_mask & GPU_WRITE_BLUE) != 0) {
attachment_state.colorWriteMask |= VK_COLOR_COMPONENT_B_BIT;
}
if ((fragment_output_info.state.write_mask & GPU_WRITE_ALPHA) != 0) {
attachment_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT;
}
vk_pipeline_color_blend_attachment_states.append_n_times(
attachment_state, fragment_output_info.color_attachment_formats.size());
}
void build_color_blend_state(const VKGraphicsInfo::FragmentOut &fragment_output_info,
const VKExtensions &extensions)
{
vk_pipeline_color_blend_state_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
nullptr,
0,
VK_FALSE,
VK_LOGIC_OP_CLEAR,
uint32_t(vk_pipeline_color_blend_attachment_states.size()),
vk_pipeline_color_blend_attachment_states.data(),
{1.0f, 1.0f, 1.0f, 1.0f}};
/* Logic ops. */
if (fragment_output_info.state.logic_op_xor && extensions.logic_ops) {
vk_pipeline_color_blend_state_create_info.logicOpEnable = VK_TRUE;
vk_pipeline_color_blend_state_create_info.logicOp = VK_LOGIC_OP_XOR;
}
}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,82 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_image_view.hh"
#include "vk_backend.hh"
#include "vk_debug.hh"
#include "vk_device.hh"
#include "vk_texture.hh"
namespace blender::gpu {
static VkFormat to_non_srgb_format(const VkFormat format)
{
switch (format) {
case VK_FORMAT_R8G8B8_SRGB:
return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_UNORM;
default:
break;
}
return format;
}
VKImageView::VKImageView(VKTexture &texture, const VKImageViewInfo &info, StringRefNull name)
: info(info)
{
const VkImageAspectFlags allowed_bits = VK_IMAGE_ASPECT_COLOR_BIT |
(info.use_stencil ? VK_IMAGE_ASPECT_STENCIL_BIT :
VK_IMAGE_ASPECT_DEPTH_BIT);
TextureFormat device_format = texture.device_format_get();
VkImageAspectFlags image_aspect = to_vk_image_aspect_flag_bits(device_format) & allowed_bits;
vk_format_ = to_vk_format(device_format);
if (texture.format_flag_get() & GPU_FORMAT_SRGB && !info.use_srgb) {
vk_format_ = to_non_srgb_format(vk_format_);
}
VkImageViewCreateInfo image_view_info = {};
image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_info.image = texture.vk_image_handle();
image_view_info.viewType = to_vk_image_view_type(texture.type_get(), info.usage, info.arrayed);
image_view_info.format = vk_format_;
image_view_info.components.r = to_vk_component_swizzle(info.swizzle[0]);
image_view_info.components.g = to_vk_component_swizzle(info.swizzle[1]);
image_view_info.components.b = to_vk_component_swizzle(info.swizzle[2]);
image_view_info.components.a = to_vk_component_swizzle(info.swizzle[3]);
image_view_info.subresourceRange.aspectMask = image_aspect;
image_view_info.subresourceRange.baseMipLevel = info.mip_range.first();
image_view_info.subresourceRange.levelCount = info.mip_range.size();
image_view_info.subresourceRange.baseArrayLayer = info.layer_range.first();
image_view_info.subresourceRange.layerCount = info.layer_range.size();
const VKDevice &device = VKBackend::get().device;
vkCreateImageView(device.vk_handle(), &image_view_info, nullptr, &vk_image_view_);
debug::object_label(vk_image_view_, name.c_str());
}
VKImageView::VKImageView(VKImageView &&other) : info(other.info)
{
vk_image_view_ = other.vk_image_view_;
other.vk_image_view_ = VK_NULL_HANDLE;
vk_format_ = other.vk_format_;
other.vk_format_ = VK_FORMAT_UNDEFINED;
}
VKImageView::~VKImageView()
{
if (vk_image_view_ != VK_NULL_HANDLE) {
VKDiscardPool::discard_pool_get().discard_image_view(vk_image_view_);
vk_image_view_ = VK_NULL_HANDLE;
}
vk_format_ = VK_FORMAT_UNDEFINED;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_common.hh"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
namespace blender::gpu {
class VKTexture;
struct VKImageViewInfo {
eImageViewUsage usage;
IndexRange layer_range;
IndexRange mip_range;
union {
char swizzle[4];
uint32_t swizzle_data;
};
bool use_stencil;
bool use_srgb;
/**
* When binding an image to a shader it needs to match the operations used inside the shader.
*
* If an shader accesses an image via an image view using the operation should match the view.
* arrayed will ensure the right image view is created.
*/
VKImageViewArrayed arrayed;
bool operator==(const VKImageViewInfo &other) const
{
return usage == other.usage && layer_range == other.layer_range &&
mip_range == other.mip_range && swizzle_data == other.swizzle_data &&
use_stencil == other.use_stencil && use_srgb == other.use_srgb &&
arrayed == other.arrayed;
}
};
class VKImageView : NonCopyable {
VkImageView vk_image_view_ = VK_NULL_HANDLE;
VkFormat vk_format_ = VK_FORMAT_UNDEFINED;
public:
const VKImageViewInfo info;
VKImageView(VKTexture &texture, const VKImageViewInfo &info, StringRefNull name);
VKImageView(VKImageView &&other);
~VKImageView();
VkImageView vk_handle() const
{
BLI_assert(vk_image_view_ != VK_NULL_HANDLE);
return vk_image_view_;
}
VkFormat vk_format() const
{
return vk_format_;
}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,153 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Mimics old style OpenGL immediate mode drawing.
*/
#include "GPU_capabilities.hh"
#include "GPU_matrix.hh"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "vk_framebuffer.hh"
#include "vk_immediate.hh"
#include "vk_state_manager.hh"
#include "CLG_log.h"
namespace blender::gpu {
static CLG_LogRef LOG = {"gpu.vulkan"};
uchar *VKImmediate::begin()
{
uint add_vertex = prim_type == GPU_PRIM_LINE_LOOP ? 1 : 0;
const size_t bytes_needed = vertex_buffer_size(&vertex_format, vertex_len + add_vertex);
size_t offset_alignment = GPU_storage_buffer_alignment();
VKBuffer &buffer = ensure_space(bytes_needed, offset_alignment);
/* Apply alignment when allocating new sub buffer, to reduce signed/unsigned data conversion
* later on. */
buffer_offset_ += offset_alignment - 1;
buffer_offset_ &= ~(offset_alignment - 1);
BLI_assert((buffer_offset_ & (offset_alignment - 1)) == 0);
current_subbuffer_len_ = bytes_needed;
uchar *data = static_cast<uchar *>(buffer.mapped_memory_get());
return data + buffer_offset_;
}
void VKImmediate::end()
{
BLI_assert_msg(prim_type != GPU_PRIM_NONE, "Illegal state: not between an immBegin/End pair.");
if (vertex_idx == 0) {
return;
}
if (prim_type == GPU_PRIM_LINE_LOOP) {
uchar *first_vertex_ptr = static_cast<uchar *>(active_buffer_->mapped_memory_get()) +
buffer_offset_;
size_t vertex_stride = current_subbuffer_len_ / (vertex_len + 1);
uchar *last_vertex_ptr = first_vertex_ptr + vertex_stride * vertex_len;
memcpy(last_vertex_ptr, first_vertex_ptr, vertex_stride);
prim_type = GPU_PRIM_LINE_STRIP;
vertex_idx += 1;
}
VKContext &context = *VKContext::get();
BLI_assert(context.shader == shader);
Shader &shader = *this->shader;
if (shader.is_polyline) {
VKBuffer &buffer = active_buffer_.value();
VKStateManager &state_manager = context.state_manager_get();
state_manager.storage_buffer_bind(BindSpaceStorageBuffers::Type::Buffer,
&buffer,
GPU_SSBO_POLYLINE_POS_BUF_SLOT,
buffer_offset_);
state_manager.storage_buffer_bind(BindSpaceStorageBuffers::Type::Buffer,
&buffer,
GPU_SSBO_POLYLINE_COL_BUF_SLOT,
buffer_offset_);
/* Not used. Satisfy the binding. */
state_manager.storage_buffer_bind(
BindSpaceStorageBuffers::Type::Buffer, &buffer, GPU_SSBO_INDEX_BUF_SLOT, buffer_offset_);
this->polyline_draw_workaround(0);
}
else {
GPU_matrix_bind(context.shader);
render_graph::VKResourceAccessInfo &resource_access_info = context.reset_and_get_access_info();
vertex_attributes_.update_bindings(*this);
VKFrameBuffer &framebuffer = *context.active_framebuffer_get();
framebuffer.rendering_ensure(context);
render_graph::VKDrawNode::CreateInfo draw(resource_access_info);
draw.node_data.vertex_count = vertex_idx;
draw.node_data.instance_count = 1;
draw.node_data.first_vertex = 0;
draw.node_data.first_instance = 0;
vertex_attributes_.bind(draw.node_data.vertex_buffers);
context.update_pipeline_data(
framebuffer, prim_type, vertex_attributes_, draw.node_data.graphics);
context.render_graph().add_node(draw);
}
buffer_offset_ += current_subbuffer_len_;
current_subbuffer_len_ = 0;
}
VKBufferWithOffset VKImmediate::active_buffer() const
{
VKBufferWithOffset result = {active_buffer_->vk_handle(), buffer_offset_};
return result;
}
VkDeviceSize VKImmediate::buffer_bytes_free()
{
return active_buffer_->size_in_bytes() - buffer_offset_;
}
static VkDeviceSize new_buffer_size(VkDeviceSize sub_buffer_size)
{
return max_ulul(sub_buffer_size, DEFAULT_INTERNAL_BUFFER_SIZE);
}
VKBuffer &VKImmediate::ensure_space(VkDeviceSize bytes_needed, VkDeviceSize offset_alignment)
{
VkDeviceSize bytes_required = bytes_needed + offset_alignment;
/* Last used buffer still has space. */
if (active_buffer_.has_value() && buffer_bytes_free() >= bytes_required) {
return active_buffer_.value();
}
/* Offset alignment isn't needed when creating buffers as it is managed by VMA. */
VkDeviceSize alloc_size = new_buffer_size(bytes_needed);
CLOG_TRACE(&LOG,
"Immediate buffer cannot hold another %d bytes, it contains %d bytes. A new "
"buffer will be allocated (size=%d)",
int(bytes_required),
int(buffer_offset_),
int(alloc_size));
buffer_offset_ = 0;
VKBuffer &result = active_buffer_.emplace();
result.create(alloc_size,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
0.8,
false,
"Immediate");
return result;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Mimics old style OpenGL immediate mode drawing.
*/
#pragma once
#include "MEM_guardedalloc.h"
#include "gpu_immediate_private.hh"
#include "gpu_vertex_format_private.hh"
#include "vk_buffer.hh"
#include "vk_data_conversion.hh"
#include "vk_mem_alloc.h"
#include "vk_vertex_attribute_object.hh"
namespace blender::gpu {
class VKDevice;
/* Size of internal buffer. */
constexpr size_t DEFAULT_INTERNAL_BUFFER_SIZE = 4 * 1024 * 1024;
class VKImmediate : public Immediate {
private:
VKVertexAttributeObject vertex_attributes_;
VkDeviceSize buffer_offset_ = 0;
VkDeviceSize current_subbuffer_len_ = 0;
std::optional<VKBuffer> active_buffer_;
public:
uchar *begin() override;
void end() override;
friend class VKVertexAttributeObject;
private:
VKBufferWithOffset active_buffer() const;
VkDeviceSize buffer_bytes_free();
VKBuffer &ensure_space(VkDeviceSize bytes_needed, VkDeviceSize offset_alignment);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_index_buffer.hh"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "vk_staging_buffer.hh"
#include "vk_state_manager.hh"
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
void VKIndexBuffer::ensure_updated()
{
if (is_subrange_) {
src_->upload_data();
return;
}
if (!buffer_.is_allocated()) {
allocate();
if (!buffer_.is_allocated()) {
CLOG_ERROR(&LOG, "Unable to allocate index buffer. Most likely an out of memory issue.");
return;
}
}
if (data_ == nullptr) {
return;
}
if (!data_uploaded_ && buffer_.is_mapped()) {
buffer_.update_immediately(data_);
MEM_SAFE_DELETE_VOID(data_);
}
else {
VKContext &context = *VKContext::get();
VKStagingBuffer staging_buffer(buffer_, VKStagingBuffer::Direction::HostToDevice);
VKBuffer &buffer = staging_buffer.host_buffer_get();
if (buffer.is_allocated()) {
staging_buffer.host_buffer_get().update_immediately(data_);
staging_buffer.copy_to_device(context);
}
else {
buffer_.clear(context, 0u);
CLOG_ERROR(
&LOG,
"Unable to upload data to index buffer via a staging buffer as the staging buffer "
"could not be allocated. Index buffer will be filled with on zeros to reduce "
"drawing artifacts due to read from uninitialized memory.");
buffer_.clear(context, 0u);
}
MEM_SAFE_DELETE_VOID(data_);
}
data_uploaded_ = true;
}
void VKIndexBuffer::upload_data()
{
ensure_updated();
}
void VKIndexBuffer::bind_as_ssbo(uint binding)
{
if (is_subrange_) {
src_->bind_as_ssbo(binding);
return;
}
VKContext::get()->state_manager_get().storage_buffer_bind(
BindSpaceStorageBuffers::Type::IndexBuffer, this, binding);
}
void VKIndexBuffer::read(uint32_t *data) const
{
VKContext &context = *VKContext::get();
VKStagingBuffer staging_buffer(buffer_, VKStagingBuffer::Direction::DeviceToHost);
VKBuffer &buffer = staging_buffer.host_buffer_get();
if (buffer.is_mapped()) {
staging_buffer.copy_from_device(context);
staging_buffer.host_buffer_get().read(context, data);
}
else {
CLOG_ERROR(&LOG,
"Unable to read data from index buffer via a staging buffer as the staging buffer "
"could not be allocated. ");
}
}
void VKIndexBuffer::update_sub(uint /*start*/, uint /*len*/, const void * /*data*/)
{
NOT_YET_IMPLEMENTED
}
void VKIndexBuffer::strip_restart_indices()
{
NOT_YET_IMPLEMENTED
}
void VKIndexBuffer::allocate()
{
buffer_.create(size_get(),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO,
VmaAllocationCreateFlags(0),
0.8f,
false,
"IndexBuffer");
}
const VKBuffer &VKIndexBuffer::buffer_get() const
{
return is_subrange_ ? unwrap(src_)->buffer_ : buffer_;
}
VKBuffer &VKIndexBuffer::buffer_get()
{
return is_subrange_ ? unwrap(src_)->buffer_ : buffer_;
}
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,62 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "GPU_index_buffer.hh"
#include "vk_buffer.hh"
namespace blender::gpu {
class VKIndexBuffer : public IndexBuf {
VKBuffer buffer_;
bool data_uploaded_ = false;
public:
void upload_data() override;
void bind_as_ssbo(uint binding) override;
void read(uint32_t *data) const override;
void update_sub(uint start, uint len, const void *data) override;
VkBuffer vk_handle() const
{
return buffer_get().vk_handle();
}
inline VkDeviceAddress device_address_get() const
{
return buffer_get().device_address_get();
}
VkIndexType vk_index_type() const
{
return to_vk_index_type(index_type_);
}
/** \brief Return the allocated size of the buffer in bytes. */
inline VkDeviceSize allocated_size_get() const
{
return buffer_get().allocated_size_in_bytes();
}
void ensure_updated();
private:
void strip_restart_indices() override;
void allocate();
VKBuffer &buffer_get();
const VKBuffer &buffer_get() const;
};
static inline VKIndexBuffer *unwrap(IndexBuf *index_buffer)
{
return static_cast<VKIndexBuffer *>(index_buffer);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_common.hh"
namespace blender::gpu {
/** Information about an exported buffer/image. */
struct VKMemoryExport {
/** Handle that has been exported. */
uint64_t handle;
/**
* Allocated memory size. Allocation size can be larger than actually requested due to memory
* alignment/allocation rules.
*/
VkDeviceSize memory_size;
/**
* Actually content offset inside the exported memory. A memory allocation can contain multiple
* buffers or images. The offset points to the specific buffer/image that is exported.
*/
VkDeviceSize memory_offset;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,218 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_memory_layout.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Std430 memory layout
* \{ */
uint32_t Std430::component_mem_size(const shader::Type /*type*/)
{
return 4;
}
uint32_t Std430::element_alignment(const shader::Type type, const bool /*is_array*/)
{
switch (type) {
case shader::Type::float_t:
case shader::Type::uint_t:
case shader::Type::int_t:
case shader::Type::bool_t:
return 4;
case shader::Type::float2_t:
case shader::Type::uint2_t:
case shader::Type::int2_t:
return 8;
case shader::Type::float3_t:
case shader::Type::uint3_t:
case shader::Type::int3_t:
case shader::Type::float4_t:
case shader::Type::uint4_t:
case shader::Type::int4_t:
case shader::Type::float3x3_t:
case shader::Type::float4x4_t:
return 16;
default:
BLI_assert_msg(false, "Type not supported in dynamic structs.");
}
return 0;
}
uint32_t Std430::element_components_len(const shader::Type type)
{
switch (type) {
case shader::Type::float_t:
case shader::Type::uint_t:
case shader::Type::int_t:
case shader::Type::bool_t:
return 1;
case shader::Type::float2_t:
case shader::Type::uint2_t:
case shader::Type::int2_t:
return 2;
case shader::Type::float3_t:
case shader::Type::uint3_t:
case shader::Type::int3_t:
return 3;
case shader::Type::float4_t:
case shader::Type::uint4_t:
case shader::Type::int4_t:
return 4;
case shader::Type::float3x3_t:
return 12;
case shader::Type::float4x4_t:
return 16;
default:
BLI_assert_msg(false, "Type not supported in dynamic structs.");
}
return 0;
}
uint32_t Std430::array_components_len(const shader::Type type)
{
switch (type) {
case shader::Type::float_t:
case shader::Type::uint_t:
case shader::Type::int_t:
case shader::Type::bool_t:
return 1;
case shader::Type::float2_t:
case shader::Type::uint2_t:
case shader::Type::int2_t:
return 2;
case shader::Type::float3_t:
case shader::Type::uint3_t:
case shader::Type::int3_t:
case shader::Type::float4_t:
case shader::Type::uint4_t:
case shader::Type::int4_t:
return 4;
case shader::Type::float3x3_t:
return 12;
case shader::Type::float4x4_t:
return 16;
default:
BLI_assert_msg(false, "Type not supported in dynamic structs.");
}
return 0;
}
uint32_t Std430::inner_row_padding(const shader::Type type)
{
return type == shader::Type::float3x3_t ? 3 : 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Std140 memory layout
* \{ */
uint32_t Std140::component_mem_size(const shader::Type /*type*/)
{
return 4;
}
uint32_t Std140::element_alignment(const shader::Type type, const bool is_array)
{
if (is_array) {
return 16;
}
switch (type) {
case shader::Type::float_t:
case shader::Type::uint_t:
case shader::Type::int_t:
case shader::Type::bool_t:
return 4;
case shader::Type::float2_t:
case shader::Type::uint2_t:
case shader::Type::int2_t:
return 8;
case shader::Type::float3_t:
case shader::Type::uint3_t:
case shader::Type::int3_t:
case shader::Type::float4_t:
case shader::Type::uint4_t:
case shader::Type::int4_t:
case shader::Type::float3x3_t:
case shader::Type::float4x4_t:
return 16;
default:
BLI_assert_msg(false, "Type not supported in dynamic structs.");
}
return 0;
}
uint32_t Std140::element_components_len(const shader::Type type)
{
switch (type) {
case shader::Type::float_t:
case shader::Type::uint_t:
case shader::Type::int_t:
case shader::Type::bool_t:
return 1;
case shader::Type::float2_t:
case shader::Type::uint2_t:
case shader::Type::int2_t:
return 2;
case shader::Type::float3_t:
case shader::Type::uint3_t:
case shader::Type::int3_t:
return 3;
case shader::Type::float4_t:
case shader::Type::uint4_t:
case shader::Type::int4_t:
return 4;
case shader::Type::float3x3_t:
return 12;
case shader::Type::float4x4_t:
return 16;
default:
BLI_assert_msg(false, "Type not supported in dynamic structs.");
}
return 0;
}
uint32_t Std140::array_components_len(const shader::Type type)
{
switch (type) {
case shader::Type::float_t:
case shader::Type::uint_t:
case shader::Type::int_t:
case shader::Type::bool_t:
case shader::Type::float2_t:
case shader::Type::uint2_t:
case shader::Type::int2_t:
case shader::Type::float3_t:
case shader::Type::uint3_t:
case shader::Type::int3_t:
case shader::Type::float4_t:
case shader::Type::uint4_t:
case shader::Type::int4_t:
return 4;
case shader::Type::float3x3_t:
return 12;
case shader::Type::float4x4_t:
return 16;
default:
BLI_assert_msg(false, "Type not supported in dynamic structs.");
}
return 0;
}
uint32_t Std140::inner_row_padding(const shader::Type /*type*/)
{
return 0;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,150 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_shader_create_info.hh"
#include "BLI_math_base.h"
#include "BLI_math_vector_types.hh"
namespace blender::gpu {
/**
* Information about alignment/components and memory size for types when using std140 layout.
*/
struct Std140 {
/** Get the memory size in bytes of a single component using by the given type. */
static uint32_t component_mem_size(const shader::Type type);
/** Get to alignment of the given type in bytes. */
static uint32_t element_alignment(const shader::Type type, bool is_array);
/** Get the number of components that should be allocated for the given type. */
static uint32_t element_components_len(const shader::Type type);
/** Get the number of components of the given type when used in an array. */
static uint32_t array_components_len(const shader::Type type);
/**
* After how many input values do we expect row padding.
*
* Mat3 in Std430 needs to be padded so every row would contain 4 floats.
* inner_row_padding would contain `3` in this case, otherwise this is set to 0.
*/
static uint32_t inner_row_padding(const shader::Type type);
};
/**
* Information about alignment/components and memory size for types when using std430 layout.
*/
struct Std430 {
/** Get the memory size in bytes of a single component using by the given type. */
static uint32_t component_mem_size(const shader::Type type);
/** Get to alignment of the given type in bytes. */
static uint32_t element_alignment(const shader::Type type, bool is_array);
/** Get the number of components that should be allocated for the given type. */
static uint32_t element_components_len(const shader::Type type);
/** Get the number of components of the given type when used in an array. */
static uint32_t array_components_len(const shader::Type type);
/**
* After how many input values do we expect row padding.
*
* Mat3 in Std430 needs to be padded so every row would contain 4 floats.
* inner_row_padding would contain `3` in this case, otherwise this is set to 0.
*/
static uint32_t inner_row_padding(const shader::Type type);
};
template<typename LayoutT> static uint32_t element_stride(const shader::Type type)
{
return LayoutT::element_components_len(type) * LayoutT::component_mem_size(type);
}
template<typename LayoutT> static uint32_t array_stride(const shader::Type type)
{
return LayoutT::array_components_len(type) * LayoutT::component_mem_size(type);
}
/**
* Move the r_offset to the next alignment where the given type+array_size can be
* reserved.
*
* 'type': The type that needs to be aligned.
* 'array_size': The array_size that needs to be aligned. (0=no array).
* 'r_offset': After the call it will point to the byte where the reservation
* can happen.
*/
template<typename LayoutT>
static void align(const shader::Type &type, const int32_t array_size, uint32_t *r_offset)
{
uint32_t alignment = LayoutT::element_alignment(type, array_size != 0);
uint32_t alignment_mask = alignment - 1;
uint32_t offset = *r_offset;
if ((offset & alignment_mask) != 0) {
offset &= ~alignment_mask;
offset += alignment;
*r_offset = offset;
}
}
/**
* Reserve space for the given type and array size.
*
* This function doesn't handle alignment this needs to be done up front by calling
* 'align<Layout>' function. Caller is responsible for this.
*
* 'type': The type that needs to be reserved.
* 'array_size': The array_size that needs to be reserved. (0=no array).
* 'r_offset': When calling needs to be pointing to the aligned location where to
* reserve space. After the call it will point to the byte just after reserved
* space.
*/
template<typename LayoutT>
static void reserve(const shader::Type type, int32_t array_size, uint32_t *r_offset)
{
uint32_t size = array_size == 0 ? element_stride<LayoutT>(type) :
array_stride<LayoutT>(type) * array_size;
*r_offset += size;
}
/**
* Update 'r_offset' to be aligned to the end of the struct.
*
* Call this function when all attributes have been added to make sure that the struct size is
* correct.
*/
template<typename LayoutT> static void align_end_of_struct(uint32_t *r_offset)
{
align<LayoutT>(shader::Type::float4_t, 0, r_offset);
}
/**
* Image transfers can use multiple transfer buffers for a single request.
*
* This helper class helps in splitting the transfers in smaller chunks.
*/
struct TransferRegion {
int3 offset;
int3 extent;
IndexRange layers;
int64_t sample_count() const
{
return int64_t(extent.x) * int64_t(extent.y) * int64_t(extent.z) * layers.size();
}
int64_t result_offset(int3 sub_offset, int layer) const
{
int64_t diff_x = sub_offset.x - offset.x;
int64_t diff_y = sub_offset.y - offset.y;
int64_t diff_z = sub_offset.z - offset.z;
int64_t diff_l = layer - layers.start();
return (diff_l * extent.x * extent.y * extent.z) + (diff_z * extent.x * extent.y) +
(diff_y * extent.x) + diff_x;
}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_memory_pool.hh"
#include "vk_device.hh"
namespace blender::gpu {
void VKMemoryPools::init(VKDevice &device)
{
if (device.extensions_get().external_memory) {
init_external_memory_image(device);
init_external_memory_pixel_buffer(device);
}
}
void VKMemoryPools::init_external_memory_image(VKDevice &device)
{
VkExternalMemoryImageCreateInfo external_image_create_info = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
nullptr,
vk_external_memory_handle_type()};
VkImageCreateInfo image_create_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
&external_image_create_info,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{1024, 1024, 1},
1,
1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
nullptr,
VK_IMAGE_LAYOUT_UNDEFINED};
VmaAllocationCreateInfo allocation_create_info = {};
allocation_create_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
uint32_t memory_type_index;
vmaFindMemoryTypeIndexForImageInfo(
device.mem_allocator_get(), &image_create_info, &allocation_create_info, &memory_type_index);
external_memory_image.info.handleTypes = vk_external_memory_handle_type();
VmaPoolCreateInfo pool_create_info = {};
pool_create_info.memoryTypeIndex = memory_type_index;
pool_create_info.pMemoryAllocateNext = &external_memory_image.info;
pool_create_info.priority = 1.0f;
vmaCreatePool(device.mem_allocator_get(), &pool_create_info, &external_memory_image.pool);
}
void VKMemoryPools::init_external_memory_pixel_buffer(VKDevice &device)
{
VkExternalMemoryBufferCreateInfo external_buffer_create_info = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
nullptr,
vk_external_memory_handle_type()};
VkBufferCreateInfo buffer_create_info = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
&external_buffer_create_info,
0,
1024,
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
nullptr};
VmaAllocationCreateInfo allocation_create_info = {};
allocation_create_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
uint32_t memory_type_index;
vmaFindMemoryTypeIndexForBufferInfo(device.mem_allocator_get(),
&buffer_create_info,
&allocation_create_info,
&memory_type_index);
external_memory_pixel_buffer.info.handleTypes = vk_external_memory_handle_type();
VmaPoolCreateInfo pool_create_info = {};
pool_create_info.memoryTypeIndex = memory_type_index;
pool_create_info.pMemoryAllocateNext = &external_memory_pixel_buffer.info;
pool_create_info.priority = 1.0f;
vmaCreatePool(device.mem_allocator_get(), &pool_create_info, &external_memory_pixel_buffer.pool);
}
void VKMemoryPools::deinit(VKDevice &device)
{
external_memory_image.deinit(device);
external_memory_pixel_buffer.deinit(device);
}
void VKMemoryPool::deinit(VKDevice &device)
{
vmaDestroyPool(device.mem_allocator_get(), pool);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "vk_common.hh"
namespace blender::gpu {
class VKDevice;
constexpr VkExternalMemoryHandleTypeFlags vk_external_memory_handle_type()
{
#ifdef _WIN32
return VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
#else
return VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
#endif
}
/**
* VMA related data for a memory pool.
*/
struct VKMemoryPool {
/* NOTE: This attribute needs to be kept alive as it will be read by VMA when allocating inside
* the pool. */
VkExportMemoryAllocateInfoKHR info = {VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR};
VmaPool pool = VK_NULL_HANDLE;
void deinit(VKDevice &device);
};
struct VKMemoryPools {
VKMemoryPool external_memory_image = {};
VKMemoryPool external_memory_pixel_buffer = {};
void init(VKDevice &device);
void deinit(VKDevice &device);
private:
void init_external_memory_image(VKDevice &device);
void init_external_memory_pixel_buffer(VKDevice &device);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,755 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include <sstream>
#include "BKE_appdir.hh"
#include "BKE_blender_version.h"
#include "BLI_fileops.hh"
#include "BLI_path_utils.hh"
#include "BLI_time.h"
#include "CLG_log.h"
#include "vk_backend.hh"
#include "vk_graphics_pipeline.hh"
#include "vk_pipeline_pool.hh"
namespace blender {
#ifdef WITH_BUILDINFO
extern "C" char build_hash[];
#endif
static CLG_LogRef LOG = {"gpu.vulkan"};
namespace gpu {
void VKPipelinePool::init()
{
VKDevice &device = VKBackend::get().device;
VkPipelineCacheCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
vkCreatePipelineCache(device.vk_handle(), &create_info, nullptr, &vk_pipeline_cache_static_);
debug::object_label(vk_pipeline_cache_static_, "VkPipelineCache.Static");
vkCreatePipelineCache(device.vk_handle(), &create_info, nullptr, &vk_pipeline_cache_non_static_);
debug::object_label(vk_pipeline_cache_non_static_, "VkPipelineCache.Dynamic");
}
/* -------------------------------------------------------------------- */
/** \name Compute pipelines
* \{ */
VkPipeline VKPipelinePool::get_or_create_compute_pipeline(const VKComputeInfo &compute_info,
const bool is_static_shader,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
bool created = false;
VkPipelineCache vk_pipeline_cache = is_static_shader ? vk_pipeline_cache_static_ :
vk_pipeline_cache_non_static_;
return compute_.get_or_create(compute_info, vk_pipeline_cache, vk_pipeline_base, name, created);
}
template<>
VkPipeline VKPipelineMap<VKComputeInfo>::create(const VKComputeInfo &compute_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
/* Building compute pipeline create info */
const bool do_specialization_constants = !compute_info.specialization_constants.is_empty();
VkComputePipelineCreateInfo vk_compute_pipeline_create_info = {
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
nullptr,
0,
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
nullptr,
0,
VK_SHADER_STAGE_COMPUTE_BIT,
compute_info.vk_shader_module,
"main",
nullptr},
compute_info.vk_pipeline_layout,
vk_pipeline_base,
0};
/* Specialization constants */
VkSpecializationInfo vk_specialization_info;
Array<VkSpecializationMapEntry> vk_specialization_map_entries(
compute_info.specialization_constants.size());
if (do_specialization_constants) {
vk_compute_pipeline_create_info.stage.pSpecializationInfo = &vk_specialization_info;
for (uint32_t index : IndexRange(compute_info.specialization_constants.size())) {
vk_specialization_map_entries[index] = {
index, uint32_t(index * sizeof(uint32_t)), sizeof(uint32_t)};
}
vk_specialization_info = {uint32_t(vk_specialization_map_entries.size()),
vk_specialization_map_entries.data(),
compute_info.specialization_constants.size() * sizeof(uint32_t),
compute_info.specialization_constants.data()};
}
/* Create pipeline. */
VKBackend &backend = VKBackend::get();
VKDevice &device = backend.device;
double start_time = BLI_time_now_seconds();
VkPipeline pipeline = VK_NULL_HANDLE;
vkCreateComputePipelines(device.vk_handle(),
vk_pipeline_cache,
1,
&vk_compute_pipeline_create_info,
nullptr,
&pipeline);
double end_time = BLI_time_now_seconds();
debug::object_label(pipeline, name);
CLOG_DEBUG(&LOG,
"Compiled compute pipeline %s in %fms ",
name.c_str(),
(end_time - start_time) * 1000.0);
return pipeline;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Graphics pipelines
* \{ */
VkPipeline VKPipelinePool::get_or_create_graphics_pipeline(const VKGraphicsInfo &graphics_info,
const bool is_static_shader,
VkPipeline vk_pipeline_base,
StringRefNull name,
bool &r_created)
{
BLI_assert_msg(
graphics_info.shaders.state == graphics_info.fragment_out.state,
"VKGraphicsInfo.shader.state and VKGraphicsInfo.fragment_out.state should be identical, "
"otherwise an incorrect fragment output library will be linked.");
VkPipelineCache vk_pipeline_cache = is_static_shader ? vk_pipeline_cache_static_ :
vk_pipeline_cache_non_static_;
return graphics_.get_or_create(
graphics_info, vk_pipeline_cache, vk_pipeline_base, name, r_created);
}
static VkPipeline create_graphics_pipeline_no_libs(const VKGraphicsInfo &graphics_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
VKDevice &device = VKBackend::get().device;
VKGraphicsPipelineCreateInfoBuilder builder;
builder.build_full(device, graphics_info, vk_pipeline_base);
/* Build pipeline. */
VkPipeline pipeline = VK_NULL_HANDLE;
double start_time = BLI_time_now_seconds();
vkCreateGraphicsPipelines(device.vk_handle(),
vk_pipeline_cache,
1,
&builder.vk_graphics_pipeline_create_info,
nullptr,
&pipeline);
double end_time = BLI_time_now_seconds();
debug::object_label(pipeline, name);
CLOG_DEBUG(&LOG,
"Compiled graphics pipeline %s in %fms ",
name.c_str(),
(end_time - start_time) * 1000.0);
return pipeline;
}
static VkPipeline create_graphics_pipeline_libs(const VKGraphicsInfo &graphics_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
double start_time = BLI_time_now_seconds();
VKDevice &device = VKBackend::get().device;
VkPipeline vertex_input_lib = device.pipelines.get_or_create_vertex_input_lib(
graphics_info.vertex_in);
VkPipeline shaders_lib = device.pipelines.get_or_create_shaders_lib(graphics_info.shaders);
VkPipeline fragment_output_lib = device.pipelines.get_or_create_fragment_output_lib(
graphics_info.fragment_out);
std::array<VkPipeline, 3> pipeline_libraries = {
vertex_input_lib, shaders_lib, fragment_output_lib};
/* Linking */
VkPipeline pipeline = VK_NULL_HANDLE;
VkPipelineLibraryCreateInfoKHR vk_pipeline_library_create_info = {
VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR,
nullptr,
uint32_t(pipeline_libraries.size()),
pipeline_libraries.data()};
VkGraphicsPipelineCreateInfo linking_pipeline_create_info = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
&vk_pipeline_library_create_info,
VK_PIPELINE_CREATE_LINK_TIME_OPTIMIZATION_BIT_EXT,
0,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
graphics_info.shaders.vk_pipeline_layout,
VK_NULL_HANDLE,
0,
vk_pipeline_base,
0};
double start_link_time = BLI_time_now_seconds();
vkCreateGraphicsPipelines(
device.vk_handle(), vk_pipeline_cache, 1, &linking_pipeline_create_info, nullptr, &pipeline);
double end_time = BLI_time_now_seconds();
debug::object_label(pipeline, name);
CLOG_TRACE(&LOG,
"Linking graphics pipeline %s in %fms ",
name.c_str(),
(end_time - start_link_time) * 1000.0);
CLOG_DEBUG(&LOG,
"Compiling graphics pipeline %s in %fms ",
name.c_str(),
(end_time - start_time) * 1000.0);
return pipeline;
}
template<>
VkPipeline VKPipelineMap<VKGraphicsInfo>::create(const VKGraphicsInfo &graphics_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
VKDevice &device = VKBackend::get().device;
const VKExtensions &extensions = device.extensions_get();
if (extensions.graphics_pipeline_library) {
return create_graphics_pipeline_libs(graphics_info, vk_pipeline_cache, vk_pipeline_base, name);
}
return create_graphics_pipeline_no_libs(
graphics_info, vk_pipeline_cache, vk_pipeline_base, name);
}
std::string VKGraphicsInfo::pipeline_info_source() const
{
std::stringstream result;
result << "info.pipeline_state()\n";
result << " .primitive(";
switch (vertex_in.vk_topology) {
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
result << "GPU_PRIM_POINTS";
break;
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
result << "GPU_PRIM_LINES";
break;
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
result << "GPU_PRIM_LINE_STRIP";
break;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
result << "GPU_PRIM_TRIS";
break;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
result << "GPU_PRIM_TRI_STRIP";
break;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
result << "GPU_PRIM_TRI_FAN";
break;
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY:
result << "GPU_PRIM_LINES_ADJ";
break;
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY:
result << "GPU_PRIM_TRIS_ADJ";
break;
default:
BLI_assert_unreachable();
};
result << ")\n";
result << " .state(";
/* Write mask */
Vector<std::string> write_masks;
if (fragment_out.state.write_mask & GPU_WRITE_COLOR) {
if ((fragment_out.state.write_mask & GPU_WRITE_COLOR) == GPU_WRITE_COLOR) {
write_masks.append("GPU_WRITE_COLOR");
}
else {
if (fragment_out.state.write_mask & GPU_WRITE_RED) {
write_masks.append("GPU_WRITE_RED");
}
if (fragment_out.state.write_mask & GPU_WRITE_GREEN) {
write_masks.append("GPU_WRITE_GREEN");
}
if (fragment_out.state.write_mask & GPU_WRITE_BLUE) {
write_masks.append("GPU_WRITE_BLUE");
}
if (fragment_out.state.write_mask & GPU_WRITE_ALPHA) {
write_masks.append("GPU_WRITE_ALPHA");
}
}
}
if (fragment_out.state.write_mask & GPU_WRITE_DEPTH) {
write_masks.append("GPU_WRITE_DEPTH");
}
if (fragment_out.state.write_mask & GPU_WRITE_STENCIL) {
write_masks.append("GPU_WRITE_STENCIL");
}
if (write_masks.is_empty()) {
write_masks.append("GPU_WRITE_NONE");
}
for (const std::string &write_mask : write_masks) {
result << write_mask;
if (&write_mask != &write_masks.last()) {
result << " | ";
}
}
/* Blending mode */
result << ",\n ";
switch (fragment_out.state.blend) {
case GPU_BLEND_NONE:
result << "GPU_BLEND_NONE";
break;
case GPU_BLEND_ALPHA:
result << "GPU_BLEND_ALPHA";
break;
case GPU_BLEND_ALPHA_PREMULT:
result << "GPU_BLEND_ALPHA_PREMULT";
break;
case GPU_BLEND_ADDITIVE:
result << "GPU_BLEND_ADDITIVE";
break;
case GPU_BLEND_ADDITIVE_PREMULT:
result << "GPU_BLEND_ADDITIVE_PREMULT";
break;
case GPU_BLEND_MULTIPLY:
result << "GPU_BLEND_MULTIPLY";
break;
case GPU_BLEND_SUBTRACT:
result << "GPU_BLEND_SUBTRACT";
break;
case GPU_BLEND_INVERT:
result << "GPU_BLEND_INVERT";
break;
case GPU_BLEND_MIN:
result << "GPU_BLEND_MIN";
break;
case GPU_BLEND_MAX:
result << "GPU_BLEND_MAX";
break;
case GPU_BLEND_OIT:
result << "GPU_BLEND_OIT";
break;
case GPU_BLEND_BACKGROUND:
result << "GPU_BLEND_BACKGROUND";
break;
case GPU_BLEND_CUSTOM:
result << "GPU_BLEND_CUSTOM";
break;
case GPU_BLEND_ALPHA_UNDER_PREMUL:
result << "GPU_BLEND_ALPHA_UNDER_PREMUL";
break;
case GPU_BLEND_OVERLAY_MASK_FROM_ALPHA:
result << "GPU_BLEND_OVERLAY_MASK_FROM_ALPHA";
break;
case GPU_BLEND_TRANSPARENCY:
result << "GPU_BLEND_TRANSPARENCY";
break;
default:
BLI_assert_unreachable();
}
/* Culling test */
result << ",\n ";
switch (shaders.state.culling_test) {
case GPU_CULL_NONE:
result << "GPU_CULL_NONE";
break;
case GPU_CULL_FRONT:
result << "GPU_CULL_FRONT";
break;
case GPU_CULL_BACK:
result << "GPU_CULL_BACK";
break;
default:
BLI_assert_unreachable();
}
/* Depth test */
result << ",\n ";
switch (shaders.state.depth_test) {
case GPU_DEPTH_NONE:
result << "GPU_DEPTH_NONE";
break;
case GPU_DEPTH_LESS:
result << "GPU_DEPTH_LESS";
break;
case GPU_DEPTH_LESS_EQUAL:
result << "GPU_DEPTH_LESS_EQUAL";
break;
case GPU_DEPTH_EQUAL:
result << "GPU_DEPTH_EQUAL";
break;
case GPU_DEPTH_GREATER:
result << "GPU_DEPTH_GREATER";
break;
case GPU_DEPTH_GREATER_EQUAL:
result << "GPU_DEPTH_GREATER_EQUAL";
break;
case GPU_DEPTH_ALWAYS:
result << "GPU_DEPTH_ALWAYS";
break;
default:
BLI_assert_unreachable();
}
/* Stencil test */
result << ",\n ";
switch (shaders.state.stencil_test) {
case GPU_STENCIL_NONE:
result << "GPU_STENCIL_NONE";
break;
case GPU_STENCIL_ALWAYS:
result << "GPU_STENCIL_ALWAYS";
break;
case GPU_STENCIL_EQUAL:
result << "GPU_STENCIL_EQUAL";
break;
case GPU_STENCIL_NEQUAL:
result << "GPU_STENCIL_NEQUAL";
break;
default:
BLI_assert_unreachable();
}
/* Stencil operation */
result << ",\n ";
switch (shaders.state.stencil_op) {
case GPU_STENCIL_OP_NONE:
result << "GPU_STENCIL_OP_NONE";
break;
case GPU_STENCIL_OP_REPLACE:
result << "GPU_STENCIL_OP_REPLACE";
break;
case GPU_STENCIL_OP_COUNT_DEPTH_PASS:
result << "GPU_STENCIL_OP_COUNT_DEPTH_PASS";
break;
case GPU_STENCIL_OP_COUNT_DEPTH_FAIL:
result << "GPU_STENCIL_OP_COUNT_DEPTH_FAIL";
break;
default:
BLI_assert_unreachable();
}
/* Provoking vertex */
result << ",\n ";
switch (shaders.state.provoking_vert) {
case GPU_VERTEX_FIRST:
result << "GPU_VERTEX_FIRST";
break;
case GPU_VERTEX_LAST:
result << "GPU_VERTEX_LAST";
break;
default:
BLI_assert_unreachable();
}
result << ")\n";
/* viewports */
result << " .viewports(" << shaders.viewport_count << ")\n";
/* Depth format */
if (fragment_out.depth_attachment_format != VK_FORMAT_UNDEFINED) {
result << " .depth_format(gpu::TextureTargetFormat::"
<< to_gpu_format_string(fragment_out.depth_attachment_format) << ")\n";
}
/* Stencil format */
if (fragment_out.stencil_attachment_format != VK_FORMAT_UNDEFINED) {
result << " .stencil_format(gpu::TextureTargetFormat::"
<< to_gpu_format_string(fragment_out.stencil_attachment_format) << ")\n";
}
/* Color formats */
for (const VkFormat format : fragment_out.color_attachment_formats) {
result << " .color_format(gpu::TextureTargetFormat::" << to_gpu_format_string(format)
<< ")\n";
}
result << ";";
return result.str();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Vertex input library
* \{ */
VkPipeline VKPipelinePool::get_or_create_vertex_input_lib(
const VKGraphicsInfo::VertexIn &vertex_input_info)
{
bool created = false;
return vertex_input_libs_.get_or_create(
vertex_input_info, vk_pipeline_cache_static_, VK_NULL_HANDLE, "VertexInLib", created);
}
template<>
VkPipeline VKPipelineMap<VKGraphicsInfo::VertexIn>::create(
const VKGraphicsInfo::VertexIn &vertex_input_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
VKDevice &device = VKBackend::get().device;
VKGraphicsPipelineCreateInfoBuilder builder;
builder.build_vertex_input_lib(device, vertex_input_info, vk_pipeline_base);
/* Build pipeline. */
VkPipeline pipeline = VK_NULL_HANDLE;
double start_time = BLI_time_now_seconds();
vkCreateGraphicsPipelines(device.vk_handle(),
vk_pipeline_cache,
1,
&builder.vk_graphics_pipeline_create_info,
nullptr,
&pipeline);
double end_time = BLI_time_now_seconds();
debug::object_label(pipeline, name);
CLOG_TRACE(&LOG, "Compiled vertex input library in %fms ", (end_time - start_time) * 1000.0);
return pipeline;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shaders library
* \{ */
VkPipeline VKPipelinePool::get_or_create_shaders_lib(const VKGraphicsInfo::Shaders &shaders_info)
{
bool created = false;
return shaders_libs_.get_or_create(
shaders_info, vk_pipeline_cache_non_static_, VK_NULL_HANDLE, "ShadersLib", created);
}
template<>
VkPipeline VKPipelineMap<VKGraphicsInfo::Shaders>::create(
const VKGraphicsInfo::Shaders &shaders_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
VKDevice &device = VKBackend::get().device;
const VKExtensions &extensions = device.extensions_get();
VKGraphicsPipelineCreateInfoBuilder builder;
builder.build_shaders_lib(shaders_info, extensions, vk_pipeline_base);
/* Build pipeline. */
VkPipeline pipeline = VK_NULL_HANDLE;
double start_time = BLI_time_now_seconds();
vkCreateGraphicsPipelines(device.vk_handle(),
vk_pipeline_cache,
1,
&builder.vk_graphics_pipeline_create_info,
nullptr,
&pipeline);
double end_time = BLI_time_now_seconds();
debug::object_label(pipeline, name);
CLOG_TRACE(&LOG, "Compiled shaders library in %fms ", (end_time - start_time) * 1000.0);
return pipeline;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Fragment output library
* \{ */
VkPipeline VKPipelinePool::get_or_create_fragment_output_lib(
const VKGraphicsInfo::FragmentOut &fragment_output_info)
{
bool created = false;
return fragment_output_libs_.get_or_create(
fragment_output_info, vk_pipeline_cache_static_, VK_NULL_HANDLE, "FragmentOutLib", created);
}
template<>
VkPipeline VKPipelineMap<VKGraphicsInfo::FragmentOut>::create(
const VKGraphicsInfo::FragmentOut &fragment_output_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name)
{
VKDevice &device = VKBackend::get().device;
const VKExtensions &extensions = device.extensions_get();
VKGraphicsPipelineCreateInfoBuilder builder;
builder.build_fragment_output_lib(fragment_output_info, extensions, vk_pipeline_base);
/* Build pipeline. */
VkPipeline pipeline = VK_NULL_HANDLE;
double start_time = BLI_time_now_seconds();
vkCreateGraphicsPipelines(device.vk_handle(),
vk_pipeline_cache,
1,
&builder.vk_graphics_pipeline_create_info,
nullptr,
&pipeline);
double end_time = BLI_time_now_seconds();
debug::object_label(pipeline, name);
CLOG_TRACE(&LOG, "Compiled fragment output library in %fms ", (end_time - start_time) * 1000.0);
return pipeline;
}
/** \} */
void VKPipelinePool::discard(VKDiscardPool &discard_pool, VkPipelineLayout vk_pipeline_layout)
{
graphics_.discard(discard_pool, vk_pipeline_layout);
compute_.discard(discard_pool, vk_pipeline_layout);
shaders_libs_.discard(discard_pool, vk_pipeline_layout);
/* vertex_input_libs_ and fragment_output_libs_ are NOT dependent on vk_pipeline_layout. */
}
void VKPipelinePool::free_data()
{
const VKDevice &device = VKBackend::get().device;
const VkDevice vk_device = device.vk_handle();
graphics_.free_data(vk_device);
compute_.free_data(vk_device);
vertex_input_libs_.free_data(vk_device);
shaders_libs_.free_data(vk_device);
fragment_output_libs_.free_data(vk_device);
vkDestroyPipelineCache(device.vk_handle(), vk_pipeline_cache_static_, nullptr);
vkDestroyPipelineCache(device.vk_handle(), vk_pipeline_cache_non_static_, nullptr);
}
/* -------------------------------------------------------------------- */
/** \name Persistent cache
* \{ */
#ifdef WITH_BUILDINFO
struct VKPipelineCachePrefixHeader {
/* `BC` stands for "Blender Cache" + 2 bytes for file versioning. */
uint32_t magic = 0xBC00;
uint32_t blender_version = BLENDER_VERSION;
uint32_t blender_version_patch = BLENDER_VERSION_PATCH;
char commit_hash[8];
uint32_t data_size;
uint32_t vendor_id;
uint32_t device_id;
uint32_t driver_version;
uint8_t pipeline_cache_uuid[VK_UUID_SIZE];
VKPipelineCachePrefixHeader()
{
const VKDevice &device = VKBackend::get().device;
data_size = 0;
const VkPhysicalDeviceProperties &properties = device.physical_device_properties_get();
vendor_id = properties.vendorID;
device_id = properties.deviceID;
driver_version = properties.driverVersion;
memcpy(&pipeline_cache_uuid, &properties.pipelineCacheUUID, VK_UUID_SIZE);
memset(commit_hash, 0, sizeof(commit_hash));
STRNCPY(commit_hash, build_hash);
}
};
static std::string pipeline_cache_filepath_get()
{
static char tmp_dir_buffer[1024];
BKE_appdir_folder_caches(tmp_dir_buffer, sizeof(tmp_dir_buffer));
std::string cache_dir = std::string(tmp_dir_buffer) + "vk-pipeline-cache" + SEP_STR;
BLI_dir_create_recursive(cache_dir.c_str());
std::string cache_file = cache_dir + "static.bin";
return cache_file;
}
#endif
void VKPipelinePool::read_from_disk()
{
#ifdef WITH_BUILDINFO
/* Don't read the shader cache when GPU debugging is enabled. When enabled we use different
* shaders and compilation settings. Previous generated pipelines will not be used. */
if (bool(G.debug & G_DEBUG_GPU)) {
return;
}
std::string cache_file = pipeline_cache_filepath_get();
if (!BLI_exists(cache_file.c_str())) {
return;
}
/* Prevent old cache files from being deleted if they're still being used. */
BLI_file_touch(cache_file.c_str());
/* Read cached binary. */
fstream file(cache_file, std::ios::binary | std::ios::in | std::ios::ate);
std::streamsize data_size = file.tellg();
file.seekg(0, std::ios::beg);
Vector<char> buffer(data_size);
file.read(buffer.data(), data_size);
file.close();
/* Validate the prefix header. */
VKPipelineCachePrefixHeader prefix;
VKPipelineCachePrefixHeader &read_prefix = *reinterpret_cast<VKPipelineCachePrefixHeader *>(
buffer.data());
prefix.data_size = read_prefix.data_size;
if (memcmp(&read_prefix, &prefix, sizeof(VKPipelineCachePrefixHeader)) != 0) {
/* Headers are different, most likely the cache will not work and potentially crash the driver.
* [https://medium.com/@zeuxcg/creating-a-robust-pipeline-cache-with-vulkan-961d09416cda]
*/
CLOG_INFO(&LOG,
"Pipeline cache on disk [%s] is ignored as it was written by a different driver or "
"Blender version. Cache will be overwritten when exiting.",
cache_file.c_str());
return;
}
CLOG_INFO(&LOG, "Initialize static pipeline cache from disk [%s].", cache_file.c_str());
VKDevice &device = VKBackend::get().device;
VkPipelineCacheCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
create_info.initialDataSize = read_prefix.data_size;
create_info.pInitialData = buffer.data() + sizeof(VKPipelineCachePrefixHeader);
VkPipelineCache vk_pipeline_cache = VK_NULL_HANDLE;
vkCreatePipelineCache(device.vk_handle(), &create_info, nullptr, &vk_pipeline_cache);
vkMergePipelineCaches(device.vk_handle(), vk_pipeline_cache_static_, 1, &vk_pipeline_cache);
vkDestroyPipelineCache(device.vk_handle(), vk_pipeline_cache, nullptr);
#endif
}
void VKPipelinePool::write_to_disk()
{
#ifdef WITH_BUILDINFO
/* Don't write the pipeline cache when GPU debugging is enabled. When enabled we use different
* shaders and compilation settings. Writing them to disk will clutter the pipeline cache. */
if (bool(G.debug & G_DEBUG_GPU)) {
return;
}
VKDevice &device = VKBackend::get().device;
size_t data_size;
vkGetPipelineCacheData(device.vk_handle(), vk_pipeline_cache_static_, &data_size, nullptr);
Vector<char> buffer(data_size);
vkGetPipelineCacheData(device.vk_handle(), vk_pipeline_cache_static_, &data_size, buffer.data());
std::string cache_file = pipeline_cache_filepath_get();
CLOG_INFO(&LOG, "Writing static pipeline cache to disk [%s].", cache_file.c_str());
fstream file(cache_file, std::ios::binary | std::ios::out);
VKPipelineCachePrefixHeader header;
header.data_size = data_size;
file.write(reinterpret_cast<char *>(&header), sizeof(VKPipelineCachePrefixHeader));
file.write(buffer.data(), data_size);
#endif
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,488 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "xxhash.h"
#include "BLI_map.hh"
#include "BLI_mutex.hh"
#include "BLI_utility_mixins.hh"
#include "gpu_state_private.hh"
#include "vk_resource_pool.hh"
#include "vk_vertex_attribute_object.hh"
namespace blender::gpu {
class VKDevice;
/**
* Struct containing key information to identify a compute pipeline.
*/
struct VKComputeInfo {
VkShaderModule vk_shader_module;
VkPipelineLayout vk_pipeline_layout;
Vector<shader::SpecializationConstant::Value> specialization_constants;
bool operator==(const VKComputeInfo &other) const
{
return vk_shader_module == other.vk_shader_module &&
vk_pipeline_layout == other.vk_pipeline_layout &&
specialization_constants == other.specialization_constants;
};
uint64_t hash() const
{
uint64_t hash = uint64_t(vk_shader_module);
hash = hash * 33 ^ uint64_t(vk_pipeline_layout);
hash = hash * 33 ^ specialization_constants.hash();
return hash;
}
VkPipelineLayout vk_pipeline_layout_get() const
{
return vk_pipeline_layout;
}
};
/**
* Struct containing key information to identify graphics pipelines.
*
* The struct has already been separated into 4 stages to be compatible with
* `VK_EXT_graphics_pipeline_library` being vertex_in, pre_rasterization, fragment_shader and
* fragment_out.
*/
struct VKGraphicsInfo {
struct VertexIn {
VkPrimitiveTopology vk_topology;
VKVertexInputDescriptionPool::Key vertex_input_key;
bool operator==(const VertexIn &other) const
{
return vk_topology == other.vk_topology && vertex_input_key == other.vertex_input_key;
}
uint64_t hash() const
{
uint64_t hash = uint64_t(vk_topology);
hash = hash * 33 ^ vertex_input_key;
return hash;
}
};
struct Shaders {
VkPipelineLayout vk_pipeline_layout;
VkShaderModule vk_vertex_module;
VkShaderModule vk_geometry_module;
VkShaderModule vk_fragment_module;
VkPrimitiveTopology vk_topology;
uint32_t viewport_count;
GPUState state;
Vector<shader::SpecializationConstant::Value> specialization_constants;
bool has_depth;
bool has_stencil;
uint32_t max_input_attachment_index = 0;
bool operator==(const Shaders &other) const
{
return vk_vertex_module == other.vk_vertex_module &&
vk_geometry_module == other.vk_geometry_module &&
vk_fragment_module == other.vk_fragment_module &&
vk_pipeline_layout == other.vk_pipeline_layout && vk_topology == other.vk_topology &&
viewport_count == other.viewport_count && state == other.state &&
specialization_constants == other.specialization_constants &&
has_depth == other.has_depth && has_stencil == other.has_stencil &&
max_input_attachment_index == other.max_input_attachment_index;
}
uint64_t hash() const
{
uint64_t hash = viewport_count;
hash = hash * 33 ^ uint64_t(vk_vertex_module);
hash = hash * 33 ^ uint64_t(vk_geometry_module);
hash = hash * 33 ^ uint64_t(vk_fragment_module);
hash = hash * 33 ^ uint64_t(vk_pipeline_layout);
hash = hash * 33 ^ uint64_t(vk_topology);
hash = hash * 33 ^ state.data;
hash = hash * 33 ^ specialization_constants.hash();
hash = hash * 33 ^ (uint64_t(has_depth) << 1 | uint64_t(has_stencil));
hash = hash * 33 ^ uint64_t(max_input_attachment_index);
return hash;
}
VkPipelineLayout vk_pipeline_layout_get() const
{
return vk_pipeline_layout;
}
};
struct FragmentOut {
GPUState state;
/* Dynamic rendering */
VkFormat depth_attachment_format;
VkFormat stencil_attachment_format;
Vector<VkFormat> color_attachment_formats;
bool operator==(const FragmentOut &other) const
{
#if 0
return hash() == other.hash();
#else
if (state != other.state || depth_attachment_format != other.depth_attachment_format ||
stencil_attachment_format != other.stencil_attachment_format ||
color_attachment_formats != other.color_attachment_formats)
{
return false;
}
return true;
#endif
}
uint64_t hash() const
{
uint64_t hash = uint64_t(depth_attachment_format);
hash = hash * 33 ^ uint64_t(stencil_attachment_format);
hash = hash * 33 ^ XXH3_64bits(color_attachment_formats.data(),
color_attachment_formats.size() * sizeof(VkFormat));
hash = hash * 33 ^ state.data;
return hash;
}
};
VertexIn vertex_in;
Shaders shaders;
FragmentOut fragment_out;
bool operator==(const VKGraphicsInfo &other) const
{
return vertex_in == other.vertex_in && shaders == other.shaders &&
fragment_out == other.fragment_out;
};
uint64_t hash() const
{
uint64_t hash = 0;
hash = hash * 33 ^ vertex_in.hash();
hash = hash * 33 ^ shaders.hash();
hash = hash * 33 ^ fragment_out.hash();
return hash;
}
VkPipelineLayout vk_pipeline_layout_get() const
{
return shaders.vk_pipeline_layout;
}
/** Generate a code snippet for configuring ShaderCreateInfo pipeline. */
std::string pipeline_info_source() const;
};
/**
* Thread safe map to pipelines of a certain type (graphics or compute).
*/
template<typename PipelineInfo> class VKPipelineMap {
Map<PipelineInfo, VkPipeline> pipelines_;
Mutex mutex_;
std::condition_variable_any new_pipeline_added_;
public:
/**
* Get an existing or create a new pipeline based on the provided PipelineInfo.
*
* When vk_pipeline_base is a valid pipeline handle, the pipeline base will be used to speed up
* pipeline creation process.
*
* \param pipeline_info: Description of the pipeline to compile.
* \param vk_pipeline_cache: Pipeline cache to use.
* \param vk_pipeline_base: An already existing pipeline that can be used as a base when
* compiling the pipeline.
* \param name: Name to give as a debug label when creating a pipeline.
* \returns The handle of the compiled pipeline.
*/
VkPipeline get_or_create(const PipelineInfo &pipeline_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name,
bool &r_created)
{
bool do_wait_for_pipeline = false;
bool do_compile_pipeline = false;
r_created = false;
{
std::scoped_lock lock(mutex_);
const VkPipeline *found_pipeline = pipelines_.lookup_ptr(pipeline_info);
if (found_pipeline) {
if (*found_pipeline == VK_NULL_HANDLE) {
do_wait_for_pipeline = true;
}
else {
/* Early exit: pipeline_info found and has a valid pipeline. */
return *found_pipeline;
}
}
else {
pipelines_.add_new(pipeline_info, VK_NULL_HANDLE);
do_compile_pipeline = true;
}
}
if (do_wait_for_pipeline) {
return wait_for_completion(pipeline_info);
}
if (do_compile_pipeline) {
VkPipeline pipeline = create(pipeline_info, vk_pipeline_cache, vk_pipeline_base, name);
r_created = true;
/* Store result in the compute pipelines map. */
{
std::scoped_lock lock(mutex_);
VkPipeline &pipeline_item = pipelines_.lookup(pipeline_info);
pipeline_item = pipeline;
}
/* Notify other threads that a new pipeline is available. */
{
new_pipeline_added_.notify_all();
}
return pipeline;
}
BLI_assert_unreachable();
return VK_NULL_HANDLE;
}
/**
* Discard all pipelines associated with the given layout
*/
void discard(VKDiscardPool &discard_pool, VkPipelineLayout vk_pipeline_layout)
{
std::scoped_lock lock(mutex_);
pipelines_.remove_if([&](auto item) {
if (item.key.vk_pipeline_layout_get() == vk_pipeline_layout) {
discard_pool.discard_pipeline(item.value);
return true;
}
return false;
});
}
/**
* Free all data.
*
* \note Handle is passed to fix recursive inclusion of vk_device.hh
*/
void free_data(VkDevice vk_device)
{
std::scoped_lock lock(mutex_);
for (VkPipeline &vk_pipeline : pipelines_.values()) {
vkDestroyPipeline(vk_device, vk_pipeline, nullptr);
}
pipelines_.clear();
}
/**
* Number of pipelines stored inside the instance.
*/
uint64_t size() const
{
return pipelines_.size();
}
private:
/**
* Create a new pipeline based on the provided PipelineInfo.
*
* When vk_pipeline_base is a valid pipeline handle, the pipeline base will be used to speed up
* pipeline creation process.
*
* \param pipeline_info: Description of the pipeline to compile.
* \param vk_pipeline_cache: Pipeline cache to use.
* \param vk_pipeline_base: An already existing pipeline that can be used as a base when
* compiling the pipeline.
* \param name: Name to give as a debug label when creating a pipeline.
* \returns The handle of the compiled pipeline.
*/
VkPipeline create(const PipelineInfo &pipeline_info,
VkPipelineCache vk_pipeline_cache,
VkPipeline vk_pipeline_base,
StringRefNull name);
/**
* \brief wait for another thread to complete the same pipeline info.
*
* \param pipeline_info: Description of the pipeline to wait for.
*/
VkPipeline wait_for_completion(const PipelineInfo &pipeline_info)
{
std::unique_lock<Mutex> lock(mutex_);
const VkPipeline *pipeline = nullptr;
new_pipeline_added_.wait(lock, [&]() {
pipeline = pipelines_.lookup_ptr(pipeline_info);
BLI_assert(pipeline != nullptr);
return *pipeline != VK_NULL_HANDLE;
});
return *pipeline;
}
};
/**
* Pipelines are lazy initialized and same pipelines should share their handle.
*
* A requirement of our render graph implementation is that changes of the pipeline can be detected
* based on the VkPipeline handle. We only want to rebind the pipeline handle when the handle
* actually changes. This improves performance (especially on NVIDIA) devices where pipeline binds
* are known to be costly.
*
* Especially for graphics pipelines many parameters are needed to compile a graphics pipeline.
* Some of the information would be boiler plating; or at least from Blender point of view. To
* improve lookup performance we use a slimmed down version of the pipeline create info structs.
* The idea is that we can limit the required data because we control which data we actually use,
* removing the boiler plating and improve hashing performance better than the VkPipelineCache can
* give us.
*
* TODO: Extensions like `VK_EXT_graphics_pipeline_library` should fit in this class and ease the
* development for graphics pipelines. Geometry in and frame-buffer out could be cached separately
* to reduce pipeline creation times. Most likely we will add support when we work on graphic
* pipelines. Recent drivers all support this extension, but the full coverage is still <20%. A
* fallback should made available for older drivers is required.
*
* TODO: Creation of shader modules needs to be revisited.
* VK_EXT_graphics_pipeline_library deprecates the use of shader modules and use the `spriv` bin
* directly. In this extension the pipeline and shader module are the same. The current approach
* should also be revisited as the latest drivers all implement pipeline libraries, but there are
* some platforms where the driver isn't been updated and doesn't implement this extension. In
* that case shader modules should still be used.
*/
class VKPipelinePool : public NonCopyable {
friend class VKDevice;
private:
VkPipelineCache vk_pipeline_cache_static_;
VkPipelineCache vk_pipeline_cache_non_static_;
VKPipelineMap<VKComputeInfo> compute_;
VKPipelineMap<VKGraphicsInfo> graphics_;
/* Cached graphics libraries (VK_EXT_graphics_pipeline_library)
* When using graphics pipeline libraries the shaders is compiled in 3 chunks, vertex input,
* shaders and fragment output. */
VKPipelineMap<VKGraphicsInfo::VertexIn> vertex_input_libs_;
VKPipelineMap<VKGraphicsInfo::Shaders> shaders_libs_;
VKPipelineMap<VKGraphicsInfo::FragmentOut> fragment_output_libs_;
public:
void init();
/**
* Get an existing or create a new compute pipeline based on the provided ComputeInfo.
*
* When vk_pipeline_base is a valid pipeline handle, the pipeline base will be used to speed up
* pipeline creation process.
*
* \param compute_info: Description of the pipeline to compile.
* \param is_static_shader: Pipelines from static shaders are cached between Blender sessions.
* Pipelines from dynamic shaders are only cached for the duration of a
* single Blender session.
* \param vk_pipeline_base: An already existing pipeline that can be used as a base when
* compiling the pipeline.
* \param name: Name to give as a debug label when creating a pipeline.
* \returns The handle of the compiled pipeline.
*/
VkPipeline get_or_create_compute_pipeline(const VKComputeInfo &compute_info,
bool is_static_shader,
VkPipeline vk_pipeline_base,
StringRefNull name);
/**
* Get an existing or create a new graphics pipeline based on the provided GraphicsInfo.
*
* When vk_pipeline_base is a valid pipeline handle, the pipeline base will be used to speed up
* pipeline creation process.
*
* \param graphics_info: Description of the pipeline to compile.
* \param is_static_shader: Pipelines from static shaders are cached between Blender sessions.
* Pipelines from dynamic shaders are only cached for the duration of
* a single Blender session.
* \param vk_pipeline_base: An already existing pipeline that can be used as a base when
* compiling the pipeline.
* \param name: Name to give as a debug label when creating a pipeline.
* \param r_created: Is set to true when this call has compiled a new pipeline. Otherwise
* it is set to false.
* \returns The handle of the compiled pipeline.
*/
VkPipeline get_or_create_graphics_pipeline(const VKGraphicsInfo &graphics_info,
bool is_static_shader,
VkPipeline vk_pipeline_base,
StringRefNull name,
bool &r_created);
/**
* Get an existing or create a new vertex input library pipeline based on the provided info.
*
* \param vertex_input_info: Description of the pipeline to compile.
* \returns The handle of the compiled pipeline.
*/
VkPipeline get_or_create_vertex_input_lib(const VKGraphicsInfo::VertexIn &vertex_input_info);
/**
* Get an existing or create a new shaders library pipeline based on the provided info.
*
* \param shaders_info: Description of the pipeline to compile.
* \returns The handle of the compiled pipeline.
*/
VkPipeline get_or_create_shaders_lib(const VKGraphicsInfo::Shaders &shaders_info);
/**
* Get an existing or create a new fragment output library pipeline based on the provided info.
*
* \param fragment_output_info: Description of the pipeline to compile.
* \returns The handle of the compiled pipeline.
*/
VkPipeline get_or_create_fragment_output_lib(
const VKGraphicsInfo::FragmentOut &fragment_output_info);
/**
* Discard all pipelines that uses the given pipeline_layout.
*/
void discard(VKDiscardPool &discard_pool, VkPipelineLayout vk_pipeline_layout);
/**
* Destroy all created pipelines.
*
* Function is called just before the device is removed. This cannot be done in the destructor as
* that would be called after the device is removed.
*/
void free_data();
/**
* Read the static pipeline cache from cache file.
*
* Pipeline caches requires blender to be build with `WITH_BUILDINFO` enabled . Between commits
* shader modules can change and shader module identifiers cannot be used. We use the build info
* to check if the identifiers can be reused.
*
* Previous stored pipeline cache will not be read when G_DEBUG_GPU is enabled. In this case the
* shader modules will be compiled with other settings and any cached pipeline will not be used
* during this session.
*
* NOTE: When developing shaders we assume that `WITH_BUILDINFO` is turned off or `G_DEBUG_GPU`
* flag is set.
*/
void read_from_disk();
/**
* Store the static pipeline cache to disk.
*
* Pipeline caches requires blender to be build with `WITH_BUILDINFO` enabled . Between commits
* shader modules can change and shader module identifiers cannot be used. We use the build info
* to check if the identifiers can be reused.
*
* The cache will not be written when G_DEBUG_GPU is active. In this case the shader modules will
* been generated with debug information and other compiler settings are used. This will clutter
* the pipeline cache.
*
* NOTE: When developing shaders we assume that `WITH_BUILDINFO` is turned off or `G_DEBUG_GPU`
* flag is set.
*/
void write_to_disk();
};
} // namespace blender::gpu

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_pixel_buffer.hh"
#include "vk_backend.hh"
#include "CLG_log.h"
namespace blender::gpu {
static CLG_LogRef LOG = {"gpu.vulkan"};
VKPixelBuffer::VKPixelBuffer(size_t size) : PixelBuffer(size) {}
void VKPixelBuffer::create(bool memory_export)
{
/* Create on demand with or without memory export. When memory export is
* enabled there is no host mapping. */
if (buffer_initialized_ && buffer_memory_export_ == memory_export) {
return;
}
if (buffer_.is_allocated()) {
buffer_.free();
}
buffer_.create(size_,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
0.8f,
memory_export,
"PixelBuffer");
buffer_initialized_ = true;
buffer_memory_export_ = memory_export;
}
void *VKPixelBuffer::map()
{
/* Vulkan buffers are always mapped between allocation and freeing. */
create(false);
return buffer_.mapped_memory_get();
}
void VKPixelBuffer::unmap()
{
/* Vulkan buffers are always mapped between allocation and freeing. */
}
GPUPixelBufferNativeHandle VKPixelBuffer::get_native_handle()
{
VKDevice &device = VKBackend::get().device;
GPUPixelBufferNativeHandle native_handle;
/* Functionality supported? */
if (!device.extensions_get().external_memory) {
return native_handle;
}
/* Create buffer. */
create(true);
/* Get device memory. */
size_t memory_size = 0;
VkDeviceMemory memory = buffer_.export_memory_get(memory_size);
if (memory == VK_NULL_HANDLE) {
CLOG_ERROR(&LOG, "Failed to get device memory for Vulkan pixel buffer");
return native_handle;
}
#ifdef _WIN32
/* Opaque Windows handle. */
VkMemoryGetWin32HandleInfoKHR info = {};
info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR;
info.pNext = nullptr;
info.memory = memory;
info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
HANDLE handle = 0;
if (device.functions.vkGetMemoryWin32Handle(device.vk_handle(), &info, &handle) != VK_SUCCESS) {
CLOG_ERROR(&LOG, "Failed to get Windows handle for Vulkan pixel buffer");
return native_handle;
}
native_handle.handle = int64_t(handle);
native_handle.size = memory_size;
#else
/* Opaque file descriptor. */
VkMemoryGetFdInfoKHR info = {};
info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
info.pNext = nullptr;
info.memory = memory;
info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
int fd = -1;
if (device.functions.vkGetMemoryFd(device.vk_handle(), &info, &fd) != VK_SUCCESS) {
CLOG_ERROR(&LOG, "Failed to get file descriptor for Vulkan pixel buffer");
return native_handle;
}
native_handle.handle = int64_t(fd);
native_handle.size = memory_size;
#endif
return native_handle;
}
size_t VKPixelBuffer::get_size()
{
return size_;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_texture_private.hh"
#include "vk_buffer.hh"
namespace blender::gpu {
class VKPixelBuffer : public PixelBuffer {
VKBuffer buffer_;
bool buffer_initialized_ = false;
bool buffer_memory_export_ = false;
public:
VKPixelBuffer(size_t size);
void *map() override;
void unmap() override;
GPUPixelBufferNativeHandle get_native_handle() override;
size_t get_size() override;
VKBuffer &buffer_get()
{
return buffer_;
}
protected:
void create(bool memory_export);
};
static inline VKPixelBuffer *unwrap(PixelBuffer *pixel_buffer)
{
return static_cast<VKPixelBuffer *>(pixel_buffer);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,164 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_push_constants.hh"
#include "vk_backend.hh"
#include "vk_buffer.hh"
#include "vk_context.hh"
#include "vk_memory_layout.hh"
#include "vk_shader.hh"
#include "vk_shader_interface.hh"
#include "vk_storage_buffer.hh"
#include "vk_uniform_buffer.hh"
namespace blender::gpu {
template<typename LayoutT>
static VKPushConstants::Layout::PushConstant init_constant(
const shader::ShaderCreateInfo::PushConst &push_constant,
const ShaderInput &shader_input,
uint32_t *r_offset)
{
align<LayoutT>(push_constant.type, push_constant.array_size, r_offset);
VKPushConstants::Layout::PushConstant layout;
layout.location = shader_input.location;
layout.type = push_constant.type;
layout.array_size = push_constant.array_size;
layout.offset = *r_offset;
layout.inner_row_padding = LayoutT::inner_row_padding(push_constant.type);
reserve<LayoutT>(push_constant.type, push_constant.array_size, r_offset);
return layout;
}
template<typename LayoutT>
uint32_t struct_size(Span<shader::ShaderCreateInfo::PushConst> push_constants)
{
uint32_t offset = 0;
for (const shader::ShaderCreateInfo::PushConst &push_constant : push_constants) {
align<LayoutT>(push_constant.type, push_constant.array_size, &offset);
reserve<LayoutT>(push_constant.type, push_constant.array_size, &offset);
}
align_end_of_struct<LayoutT>(&offset);
return offset;
}
VKPushConstants::StorageType VKPushConstants::Layout::determine_storage_type(
const shader::ShaderCreateInfo &info, const VKDevice &device)
{
if (info.push_constants_.is_empty()) {
return StorageType::NONE;
}
uint32_t max_push_constants_size =
device.physical_device_properties_get().limits.maxPushConstantsSize;
uint32_t size = struct_size<Std430>(info.push_constants_);
return size <= max_push_constants_size ? STORAGE_TYPE_DEFAULT : STORAGE_TYPE_FALLBACK;
}
template<typename LayoutT>
void init_struct(const shader::ShaderCreateInfo &info,
const VKShaderInterface &interface,
Vector<VKPushConstants::Layout::PushConstant> &r_struct,
uint32_t *r_offset)
{
for (const shader::ShaderCreateInfo::PushConst &push_constant : info.push_constants_) {
const ShaderInput *shader_input = interface.uniform_get(push_constant.name.c_str());
r_struct.append(init_constant<LayoutT>(push_constant, *shader_input, r_offset));
}
align_end_of_struct<Std140>(r_offset);
}
void VKPushConstants::Layout::init(const shader::ShaderCreateInfo &info,
const VKShaderInterface &interface,
const StorageType storage_type,
const VKDescriptorSet::Location location)
{
BLI_assert(push_constants.is_empty());
storage_type_ = storage_type;
size_in_bytes_ = 0;
if (storage_type == StorageType::BUFFER) {
descriptor_set_location_ = location;
init_struct<Std140>(info, interface, push_constants, &size_in_bytes_);
}
else {
init_struct<Std430>(info, interface, push_constants, &size_in_bytes_);
}
}
const VKPushConstants::Layout::PushConstant *VKPushConstants::Layout::find(int32_t location) const
{
for (const PushConstant &push_constant : push_constants) {
if (push_constant.location == location) {
return &push_constant;
}
}
return nullptr;
}
void VKPushConstants::Layout::debug_print() const
{
std::ostream &stream = std::cout;
stream << "VKPushConstants::Layout::debug_print()\n";
for (const PushConstant &push_constant : push_constants) {
stream << " - location:" << push_constant.location;
stream << ", offset:" << push_constant.offset;
stream << ", array_size:" << push_constant.array_size;
stream << "\n";
}
}
VKPushConstants::VKPushConstants() = default;
VKPushConstants::VKPushConstants(const Layout *layout) : layout_(layout)
{
data_ = MEM_new_uninitialized(layout->size_in_bytes(), __func__);
if (G.debug & G_DEBUG_GPU) {
memset(data_, 0xFD, layout->size_in_bytes());
}
}
VKPushConstants::VKPushConstants(VKPushConstants &&other) : layout_(other.layout_)
{
data_ = other.data_;
other.data_ = nullptr;
}
VKPushConstants::~VKPushConstants()
{
if (data_ != nullptr) {
MEM_delete_void(data_);
data_ = nullptr;
}
}
VKPushConstants &VKPushConstants::operator=(VKPushConstants &&other)
{
layout_ = other.layout_;
data_ = other.data_;
other.data_ = nullptr;
return *this;
}
VKBufferWithOffset VKPushConstants::update_uniform_buffer(VKContext &context)
{
BLI_assert(layout_->storage_type_get() == StorageType::BUFFER);
BLI_assert(data_ != nullptr);
VKBufferWithOffset result =
context.push_constants_pool.append(
Span<uint8_t>(static_cast<const uint8_t *>(data_), layout_->size_in_bytes()));
return result;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,263 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Push constants is a way to quickly provide a small amount of uniform data to shaders. It should
* be much quicker than UBOs but a huge limitation is the size of data - spec requires 128 bytes to
* be available for a push constant range. Hardware vendors may support more, but compared to other
* means it is still very little (for example 256 bytes).
*
* Due to this size requirements we try to use push constants when it fits on the device. If it
* doesn't fit we fall back to use an uniform buffer.
*
* Shader developers are responsible to fine-tune the performance of the shader. One way to do this
* is to tailor what will be sent as a push constant to keep the push constants within the limits.
*/
#pragma once
#include "BLI_vector.hh"
#include "gpu_shader_create_info.hh"
#include "vk_buffer.hh"
#include "vk_descriptor_set.hh"
namespace blender::gpu {
class VKShaderInterface;
class VKUniformBuffer;
class VKContext;
class VKDevice;
/**
* Container to store push constants in a buffer.
*
* Can handle buffers with different memory layouts (std140/std430)
* Which memory layout is used is based on the storage type.
*
* VKPushConstantsLayout only describes the buffer, an instance of this
* class can handle setting/modifying/duplicating push constants.
*
* It should also keep track of the submissions in order to reuse the allocated
* data.
*/
class VKPushConstants {
friend class VKContext;
public:
/** Different methods to store push constants. */
enum class StorageType {
/** Push constants aren't in use. */
NONE,
/** Store push constants as regular vulkan push constants. */
PUSH_CONSTANTS,
/**
* Fallback when push constants doesn't meet the device requirements.
*/
BUFFER,
};
/**
* Describe the layout of the push constants and the storage type that should be used.
*/
struct Layout {
static constexpr StorageType STORAGE_TYPE_DEFAULT = StorageType::PUSH_CONSTANTS;
static constexpr StorageType STORAGE_TYPE_FALLBACK = StorageType::BUFFER;
struct PushConstant {
/* Used as lookup based on ShaderInput. */
int32_t location;
/** Offset in the push constant data (in bytes). */
uint32_t offset;
shader::Type type;
int array_size;
uint inner_row_padding;
};
private:
Vector<PushConstant> push_constants;
uint32_t size_in_bytes_ = 0;
StorageType storage_type_ = StorageType::NONE;
/**
* Binding index in the descriptor set when the push constants use an uniform buffer.
*/
VKDescriptorSet::Location descriptor_set_location_;
public:
/**
* Return the desired storage type that can fit the push constants of the given shader create
* info, matching the limits of the given device.
*
* Returns:
* - StorageType::NONE: No push constants are needed.
* - StorageType::PUSH_CONSTANTS: Regular vulkan push constants can be used.
* - StorageType::BUFFER: The push constants don't fit in the limits of the given
* device. A buffer should be used as a fallback method.
*/
static StorageType determine_storage_type(const shader::ShaderCreateInfo &info,
const VKDevice &device);
/**
* Initialize the push constants of the given shader create info with the
* binding location.
*
* interface: Uniform locations of the interface are used as lookup key.
* storage_type: The type of storage for push constants to use.
* location: When storage_type=StorageType::BUFFER this contains
* the location in the descriptor set where the buffer can be
* bound.
*/
void init(const shader::ShaderCreateInfo &info,
const VKShaderInterface &interface,
StorageType storage_type,
VKDescriptorSet::Location location);
/**
* Return the storage type that is used.
*/
StorageType storage_type_get() const
{
return storage_type_;
}
/**
* Get the binding location for the buffer.
*
* Only valid when storage_type=StorageType::BUFFER.
*/
VKDescriptorSet::Location descriptor_set_location_get() const
{
return descriptor_set_location_;
}
/**
* Get the size needed to store the push constants.
*/
uint32_t size_in_bytes() const
{
return size_in_bytes_;
}
/**
* Find the push constant layout for the given location.
* Location = ShaderInput.location.
*/
const PushConstant *find(int32_t location) const;
void debug_print() const;
};
private:
const Layout *layout_ = nullptr;
void *data_ = nullptr;
public:
VKPushConstants();
VKPushConstants(const Layout *layout);
VKPushConstants(VKPushConstants &&other);
virtual ~VKPushConstants();
VKPushConstants &operator=(VKPushConstants &&other);
size_t offset() const
{
return 0;
}
const Layout &layout_get() const
{
return *layout_;
}
/**
* Get the reference to the active data.
*
* Data can get inactive when push constants are modified, after being added to the command
* queue. We still keep track of the old data for reuse and make sure we don't overwrite data
* that is still not on the GPU.
*/
const void *data() const
{
return data_;
}
/**
* Modify a push constant.
*
* location: ShaderInput.location of the push constant to update.
* comp_len: number of components has the data type that is being updated.
* array_size: number of elements when an array to update. (0=no array)
* input_data: packed source data to use.
*/
template<typename T>
void push_constant_set(int32_t location,
int32_t comp_len,
int32_t array_size,
const T *input_data)
{
const Layout::PushConstant *push_constant_layout = layout_->find(location);
if (push_constant_layout == nullptr) {
/* Legacy code can still try to update push constants when they don't exist. For example
* `immDrawPixelsTexSetup` will bind an image slot manually. This works in OpenGL, but in
* vulkan images aren't stored as push constants. */
return;
}
uint8_t *bytes = static_cast<uint8_t *>(data_);
T *dst = static_cast<T *>(static_cast<void *>(&bytes[push_constant_layout->offset]));
const int inner_row_padding = push_constant_layout->inner_row_padding;
const bool is_tightly_std140_packed = (comp_len % 4) == 0;
/* Vec3[] are not tightly packed in std430. */
const bool is_tightly_std430_packed = comp_len != 3 || array_size == 0;
if (inner_row_padding == 0 &&
((layout_->storage_type_get() == StorageType::PUSH_CONSTANTS &&
is_tightly_std430_packed) ||
array_size == 0 || push_constant_layout->array_size == 0 || is_tightly_std140_packed))
{
const size_t copy_size_in_bytes = comp_len * max_ii(array_size, 1) * sizeof(T);
BLI_assert_msg(push_constant_layout->offset + copy_size_in_bytes <= layout_->size_in_bytes(),
"Tried to write outside the push constant allocated memory.");
memcpy(dst, input_data, copy_size_in_bytes);
return;
}
/* Store elements in buffer as array. In Std140 arrays have an element stride of 16
* bytes. */
BLI_assert(sizeof(T) == 4);
const T *src = input_data;
if (inner_row_padding == 0) {
for (const int i : IndexRange(array_size)) {
UNUSED_VARS(i);
memcpy(dst, src, comp_len * sizeof(T));
src += comp_len;
dst += 4;
}
}
else {
BLI_assert_msg(array_size == 1, "No support for MAT3 arrays, but can be added when needed");
for (const int component_index : IndexRange(comp_len)) {
*dst = *src;
dst += 1;
src += 1;
if ((component_index % inner_row_padding) == (inner_row_padding - 1)) {
dst += 1;
}
}
}
}
/**
* When storage type = StorageType::BUFFER use this method to update the buffer.
*
* It must be called just before adding a draw/compute command to the command queue.
*/
VKBufferWithOffset update_uniform_buffer(VKContext &context);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "vk_query.hh"
#include "vk_backend.hh"
#include "vk_context.hh"
#include "GPU_debug.hh"
namespace blender::gpu {
VKQueryPool::~VKQueryPool()
{
VKBackend &backend = VKBackend::get();
const VKDevice &device = backend.device;
while (!vk_query_pools_.is_empty()) {
VkQueryPool vk_query_pool = vk_query_pools_.pop_last();
vkDestroyQueryPool(device.vk_handle(), vk_query_pool, nullptr);
}
}
uint32_t VKQueryPool::query_index_in_pool() const
{
return queries_issued_ - (vk_query_pools_.size() - 1) * query_chunk_len_;
}
void VKQueryPool::init(GPUQueryType type)
{
BLI_assert(vk_query_pools_.is_empty());
queries_issued_ = 0;
vk_query_type_ = to_vk_query_type(type);
}
void VKQueryPool::begin_query()
{
VKBackend &backend = VKBackend::get();
const VKDevice &device = backend.device;
uint32_t pool_index = queries_issued_ / query_chunk_len_;
bool is_new_pool = (queries_issued_ % query_chunk_len_) == 0;
if (pool_index == vk_query_pools_.size()) {
BLI_assert(is_new_pool);
VkQueryPoolCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
create_info.queryType = vk_query_type_;
create_info.queryCount = query_chunk_len_;
VkQueryPool vk_query_pool = VK_NULL_HANDLE;
vkCreateQueryPool(device.vk_handle(), &create_info, nullptr, &vk_query_pool);
vk_query_pools_.append(vk_query_pool);
}
BLI_assert(pool_index < vk_query_pools_.size());
/* When using a new query pool make sure to reset it before first usage. */
VKContext &context = *VKContext::get();
VkQueryPool vk_query_pool = vk_query_pools_[pool_index];
if (is_new_pool) {
render_graph::VKResetQueryPoolNode::Data reset_query_pool = {};
reset_query_pool.vk_query_pool = vk_query_pool;
reset_query_pool.first_query = 0;
reset_query_pool.query_count = query_chunk_len_;
context.render_graph().add_node(reset_query_pool);
}
render_graph::VKBeginQueryNode::Data begin_query = {};
begin_query.vk_query_pool = vk_query_pool;
begin_query.query_index = query_index_in_pool();
context.render_graph().add_node(begin_query);
}
void VKQueryPool::end_query()
{
VKContext &context = *VKContext::get();
render_graph::VKEndQueryNode::Data end_query = {};
end_query.vk_query_pool = vk_query_pools_.last();
end_query.query_index = query_index_in_pool();
context.render_graph().add_node(end_query);
queries_issued_ += 1;
}
void VKQueryPool::get_occlusion_result(MutableSpan<uint32_t> r_values)
{
VKContext &context = *VKContext::get();
/* During selection the frame buffer is still rendering. It needs to finish the render scope to
* ensure the END_RENDERING node */
context.rendering_end();
context.flush_render_graph(RenderGraphFlushFlags::SUBMIT |
RenderGraphFlushFlags::WAIT_FOR_COMPLETION |
RenderGraphFlushFlags::RENEW_RENDER_GRAPH);
int queries_left = queries_issued_;
int pool_index = 0;
VKBackend &backend = VKBackend::get();
const VKDevice &device = backend.device;
while (queries_left) {
VkQueryPool vk_query_pool = vk_query_pools_[pool_index];
uint32_t *r_values_chunk = &r_values[pool_index * query_chunk_len_];
uint32_t values_chunk_size = min_ii(queries_left, query_chunk_len_);
vkGetQueryPoolResults(device.vk_handle(),
vk_query_pool,
0,
values_chunk_size,
values_chunk_size * sizeof(uint32_t),
r_values_chunk,
sizeof(uint32_t),
VK_QUERY_RESULT_WAIT_BIT);
queries_left = max_ii(queries_left - query_chunk_len_, 0);
pool_index += 1;
}
}
} // namespace blender::gpu

Some files were not shown because too many files have changed in this diff Show More