#include <volk.h>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include "backend/vulkan/utils.h"
#include "backend/vulkan/vk_api.h"
#include "backend/vulkan/vk_conversion.h"
namespace rhi {
using namespace vk;
static VkAttachmentLoadOp to_vk_load_op(LoadOp value) {
switch (value) {
case LoadOp::LOAD:
return VK_ATTACHMENT_LOAD_OP_LOAD;
case LoadOp::CLEAR:
return VK_ATTACHMENT_LOAD_OP_CLEAR;
}
return VK_ATTACHMENT_LOAD_OP_LOAD;
}
static VkAttachmentStoreOp to_vk_store_op(StoreOp value) {
switch (value) {
case StoreOp::STORE:
return VK_ATTACHMENT_STORE_OP_STORE;
case StoreOp::DISCARD:
return VK_ATTACHMENT_STORE_OP_DONT_CARE;
}
return VK_ATTACHMENT_STORE_OP_STORE;
}
static VkResolveModeFlagBits to_vk_resolve_mode(ResolveOp value) {
switch (value) {
case ResolveOp::NONE:
return VK_RESOLVE_MODE_NONE;
case ResolveOp::AVERAGE:
return VK_RESOLVE_MODE_AVERAGE_BIT;
case ResolveOp::MIN:
return VK_RESOLVE_MODE_MIN_BIT;
case ResolveOp::MAX:
return VK_RESOLVE_MODE_MAX_BIT;
case ResolveOp::SAMPLE_ZERO:
return VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
}
return VK_RESOLVE_MODE_NONE;
}
static VkClearValue to_vk_clear_value(const ClearValue &value) {
VkClearValue result{};
memcpy(&result, &value, sizeof(VkClearValue));
return result;
}
static VkRenderingAttachmentInfo to_vk_rendering_attachment(const AttachmentDesc &attachment) {
VkRenderingAttachmentInfo info{};
if (attachment.img == nullptr || attachment.img->_handle == nullptr) {
VEL_CRITICAL("ToVkRenderingAttachment: null or zero-handle attachment image");
return info;
}
info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
info.imageView = attachment.img->_handle;
auto aspects = attachment.img->desc.aspect;
const bool depth_read_only =
attachment.img->desc.image != nullptr && attachment.img->desc.image->state == ResourceState::DepthFetch;
if (Any(aspects, ImageAspect::Depth) && Any(aspects, ImageAspect::Stencil)) {
info.imageLayout = depth_read_only ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
} else if (Any(aspects, ImageAspect::Depth)) {
info.imageLayout = depth_read_only ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
: VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
} else if (Any(aspects, ImageAspect::Stencil)) {
info.imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
} else {
info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
info.loadOp = to_vk_load_op(attachment.loadOp);
info.storeOp = to_vk_store_op(attachment.storeOp);
if (attachment.resolveImg) {
info.resolveMode = to_vk_resolve_mode(attachment.resolveOp);
info.resolveImageView = attachment.resolveImg->_handle;
auto resolve_aspects = attachment.resolveImg->desc.aspect;
if (Any(resolve_aspects, ImageAspect::Depth) && Any(resolve_aspects, ImageAspect::Stencil)) {
info.resolveImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
} else if (Any(resolve_aspects, ImageAspect::Depth)) {
info.resolveImageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
} else if (Any(resolve_aspects, ImageAspect::Stencil)) {
info.resolveImageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL;
} else {
info.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
}
if (Any(attachment.img->desc.aspect, ImageAspect::Depth) ||
Any(attachment.img->desc.aspect, ImageAspect::Stencil)) {
info.clearValue.depthStencil = {
attachment.clearValue.depthStencil.depth, attachment.clearValue.depthStencil.stencil
};
} else {
info.clearValue.color.float32[0] = attachment.clearValue.color.float32[0];
info.clearValue.color.float32[1] = attachment.clearValue.color.float32[1];
info.clearValue.color.float32[2] = attachment.clearValue.color.float32[2];
info.clearValue.color.float32[3] = attachment.clearValue.color.float32[3];
}
return info;
}
bool create_cmd_buffer(Device &device, const CmdBufferDesc &desc, CmdBuffer &cmd) {
VkCommandBufferAllocateInfo alloc_ci{};
alloc_ci.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_ci.commandPool = desc.pool->handle;
alloc_ci.level = vk::to_vk_cmd_buffer_level(desc.level);
alloc_ci.commandBufferCount = 1;
VkCommandBuffer vk_cmd;
VK_CHECK(vkAllocateCommandBuffers(device.logical, &alloc_ci, &vk_cmd));
cmd.handle = vk_cmd;
return true;
}
void destroy_cmd_buffer(Device &device, CmdPool &pool, CmdBuffer &cmd) {
if (cmd.handle != VK_NULL_HANDLE) {
VkCommandBuffer vk_cmd = cmd.handle;
vkFreeCommandBuffers(device.logical, pool.handle, 1, &vk_cmd);
}
cmd = {};
}
bool begin_cmd_buffer(CmdBuffer &cmd, const CmdBufferBeginDesc &desc) {
VkCommandBufferBeginInfo begin_ci{};
begin_ci.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_ci.flags = vk::to_vk_cmd_buffer_usage_flags(desc.usage);
VK_CHECK(vkBeginCommandBuffer(cmd.handle, &begin_ci));
return true;
}
bool end_cmd_buffer(CmdBuffer &cmd) {
VK_CHECK(vkEndCommandBuffer(cmd.handle));
return true;
}
bool reset_cmd_buffer(CmdBuffer &cmd) {
VK_CHECK(vkResetCommandBuffer(cmd.handle, 0));
return true;
}
void begin_rendering(CmdBuffer &cmd, RenderingInfo &ri) {
std::vector<VkRenderingAttachmentInfo> colors;
colors.reserve(ri.colorAttachmentCount);
VkRenderingInfo rendering_info{
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = {{0, 0}, {ri.width, ri.height}},
.layerCount = 1,
};
rendering_info.colorAttachmentCount = ri.colorAttachmentCount;
for (u32 i = 0; i < ri.colorAttachmentCount; i++) {
colors.push_back(to_vk_rendering_attachment(ri.colorAttachments[i]));
}
rendering_info.pColorAttachments = colors.data();
VkRenderingAttachmentInfo depth_attach = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO};
VkRenderingAttachmentInfo stencil_attach = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO};
if (ri.depthAttachment.img != nullptr && ri.depthAttachment.img->_handle != nullptr) {
auto aspects = ri.depthAttachment.img->desc.aspect;
if (Any(aspects, ImageAspect::Depth)) {
depth_attach = to_vk_rendering_attachment(ri.depthAttachment);
rendering_info.pDepthAttachment = &depth_attach;
}
if (Any(aspects, ImageAspect::Stencil)) {
stencil_attach = to_vk_rendering_attachment(ri.depthAttachment);
rendering_info.pStencilAttachment = &stencil_attach;
}
}
vkCmdBeginRendering(cmd.handle, &rendering_info);
cmd.is_rendering = true;
}
void end_rendering(CmdBuffer &cmd) {
vkCmdEndRendering(cmd.handle);
cmd.is_rendering = false;
}
void set_viewports(CmdBuffer &cmd, Viewport *viewports, u32 viewportCount) {
std::vector<VkViewport> vk_viewports = {};
for (u32 i = 0; i < viewportCount; i++) {
const Viewport &in = viewports[i];
VkViewport out;
out.x = in.x;
out.y = in.y;
out.width = in.width;
out.height = in.height;
out.minDepth = in.depth_min;
out.maxDepth = in.depth_max;
// flip for vulkan ndc
if (in.flip_y) {
out.y += in.height;
out.height = -in.height;
}
vk_viewports.push_back(out);
}
vkCmdSetViewportWithCount(cmd.handle, viewportCount, vk_viewports.data());
};
void set_scissors(CmdBuffer &cmd, const Rect *rects, u32 rectCount) {
std::vector<VkRect2D> vk_rects = {};
for (u32 i = 0; i < rectCount; i++) {
const Rect &in = rects[i];
VkRect2D out;
out.offset.x = in.x;
out.offset.y = in.y;
out.extent.width = in.width;
out.extent.height = in.height;
vk_rects.push_back(out);
}
vkCmdSetScissorWithCount(cmd.handle, rectCount, vk_rects.data());
}
void set_vertex_buffer(CmdBuffer &cmd, Buffer &buffer, u32 binding, u64 offset) {
VkBuffer buffers[] = {buffer._handle};
VkDeviceSize offsets[] = {offset};
vkCmdBindVertexBuffers(cmd.handle, binding, 1, buffers, offsets);
}
void set_index_buffer(CmdBuffer &cmd, Buffer &buffer, u32 offset, IndexType type) {
u64 size = buffer.desc.size - offset;
vkCmdBindIndexBuffer(
cmd.handle, buffer._handle, offset, type == IndexType::UINT32 ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_UINT16
);
}
void set_pipeline(CmdBuffer &cmd, RenderPipeline &pipeline) {
VEL_ASSERT_HANDLE(cmd.handle, "SetPipeline: null command buffer");
VEL_ASSERT_HANDLE(pipeline._handle, "SetPipeline: null pipeline handle");
assert(pipeline.device != nullptr && "SetPipeline: pipeline has no device");
vkCmdBindPipeline(cmd.handle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline._handle);
vkCmdBindDescriptorSets(
cmd.handle,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeline.device->pipeline_layout,
0,
1,
&pipeline.device->descriptor_set,
0,
nullptr
);
}
void set_pipeline(CmdBuffer &cmd, ComputePipeline &pipeline) {
VEL_ASSERT_HANDLE(cmd.handle, "SetPipeline: null command buffer");
VEL_ASSERT_HANDLE(pipeline._handle, "SetPipeline: null pipeline handle");
assert(pipeline.device != nullptr && "SetPipeline: pipeline has no device");
vkCmdBindPipeline(cmd.handle, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline._handle);
vkCmdBindDescriptorSets(
cmd.handle,
VK_PIPELINE_BIND_POINT_COMPUTE,
pipeline.device->pipeline_layout,
0,
1,
&pipeline.device->descriptor_set,
0,
nullptr
);
}
void set_constants(CmdBuffer &cmd, RenderPipeline &pipeline, rhi::ShaderStage stage, u32 size, const void *data) {
(void)stage;
assert(pipeline.device != nullptr && "SetConstants: pipeline has no device");
vkCmdPushConstants(
cmd.handle, pipeline.device->pipeline_layout, VK_SHADER_STAGE_ALL | VK_SHADER_STAGE_COMPUTE_BIT, 0, size, data
);
}
void set_constants(CmdBuffer &cmd, ComputePipeline &pipeline, rhi::ShaderStage stage, u32 size, const void *data) {
(void)stage;
assert(pipeline.device != nullptr && "SetConstants: pipeline has no device");
vkCmdPushConstants(
cmd.handle, pipeline.device->pipeline_layout, VK_SHADER_STAGE_ALL | VK_SHADER_STAGE_COMPUTE_BIT, 0, size, data
);
}
void draw(CmdBuffer &cmd, u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) {
vkCmdDraw(cmd.handle, vertexCount, instanceCount, firstVertex, firstInstance);
}
void draw_indexed(CmdBuffer &cmd, u32 indexCount, u32 instanceCount, u32 firstIndex, u32 vtxOff, u32 firstInstance) {
vkCmdDrawIndexed(cmd.handle, indexCount, instanceCount, firstIndex, vtxOff, firstInstance);
}
void draw_indirect(CmdBuffer &cmd, Buffer &buffer, u32 drawCount, u32 stride, u64 offset) {
vkCmdDrawIndirect(cmd.handle, buffer._handle, offset, drawCount, stride);
}
void draw_indirect_count(
CmdBuffer &cmd, Buffer &buffer, Buffer &countBuffer, u32 drawCount, u32 stride, u64 offset, u32 countOffset
) {
vkCmdDrawIndirectCount(cmd.handle, buffer._handle, offset, countBuffer._handle, countOffset, drawCount, stride);
}
void draw_indexed_indirect(
CmdBuffer &cmd, Buffer &buffer, Buffer *countBuffer, u32 drawCount, u32 stride, u64 offset, u32 countOffset
) {
if (countBuffer) {
vkCmdDrawIndexedIndirectCount(
cmd.handle, buffer._handle, offset, countBuffer->_handle, countOffset, drawCount, stride
);
} else {
vkCmdDrawIndexedIndirect(cmd.handle, buffer._handle, offset, drawCount, stride);
}
}
void fill_buffer(CmdBuffer &cmd, Buffer &buffer, u64 offset, u64 size, u32 value) {
vkCmdFillBuffer(cmd.handle, buffer._handle, offset, size, value);
}
void dispatch(CmdBuffer &cmd, u32 x, u32 y, u32 z) {
VEL_ASSERT_HANDLE(cmd.handle, "Dispatch: null command buffer");
vkCmdDispatch(cmd.handle, x, y, z);
};
static bool state_is_read_only(ResourceState s) {
switch (s) {
case ResourceState::Idle:
case ResourceState::ColorFetch:
case ResourceState::DepthFetch:
case ResourceState::TextureSample:
case ResourceState::TextureSampleNonFragment:
case ResourceState::StorageRead:
case ResourceState::VertexFetch:
case ResourceState::IndexFetch:
case ResourceState::IndirectFetch:
case ResourceState::UniformRead:
case ResourceState::TransferFrom:
case ResourceState::HostRead:
case ResourceState::AccelTrace:
case ResourceState::Display:
return true;
default:
return false;
}
}
static VkImageMemoryBarrier2
image_barrier_for(Image &image, const ImageRange &range, ResourceState before, ResourceState after) {
vk::StateSync src = vk::image_state_to_vk(before, range.aspect);
vk::StateSync dst = vk::image_state_to_vk(after, range.aspect);
VkImageMemoryBarrier2 out{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
out.srcStageMask = src.stages;
out.srcAccessMask = src.access;
out.dstStageMask = dst.stages;
out.dstAccessMask = dst.access;
out.oldLayout = src.layout == VK_IMAGE_LAYOUT_UNDEFINED && before == ResourceState::Idle ? VK_IMAGE_LAYOUT_UNDEFINED
: src.layout;
out.newLayout = dst.layout;
out.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
out.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
out.image = image._handle;
out.subresourceRange = {
vk::image_aspect_bit_to_vk(range.aspect),
range.mip,
(range.mip_count == REMAINING_MIPS) ? VK_REMAINING_MIP_LEVELS : range.mip_count,
range.layer,
(range.layer_count == REMAINING_LAYERS) ? VK_REMAINING_ARRAY_LAYERS : range.layer_count,
};
return out;
}
// no barriers inside rendering
static void require_outside_rendering(const CmdBuffer &cmd) {
if (cmd.is_rendering) {
VEL_CRITICAL("barrier() called inside dynamic rendering — hoist it before begin_rendering");
std::abort();
}
}
void barrier(CmdBuffer &cmd, ResourceState before, ResourceState after) {
require_outside_rendering(cmd);
vk::StateSync src = vk::buffer_state_to_vk(before);
vk::StateSync dst = vk::buffer_state_to_vk(after);
VkMemoryBarrier2 mem{VK_STRUCTURE_TYPE_MEMORY_BARRIER_2};
mem.srcStageMask = src.stages;
mem.srcAccessMask = src.access;
mem.dstStageMask = dst.stages;
mem.dstAccessMask = dst.access;
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
dep.memoryBarrierCount = 1;
dep.pMemoryBarriers = &mem;
vkCmdPipelineBarrier2(cmd.handle, &dep);
}
void barrier(CmdBuffer &cmd, Buffer &buffer, ResourceState before, ResourceState after) {
require_outside_rendering(cmd);
if (before == after && before != ResourceState::Idle && state_is_read_only(before)) {
return;
}
vk::StateSync src = vk::buffer_state_to_vk(before);
vk::StateSync dst = vk::buffer_state_to_vk(after);
VkBufferMemoryBarrier2 out{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2};
out.srcStageMask = src.stages;
out.srcAccessMask = src.access;
out.dstStageMask = dst.stages;
out.dstAccessMask = dst.access;
out.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
out.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
out.buffer = buffer._handle;
out.offset = 0;
out.size = VK_WHOLE_SIZE;
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
dep.bufferMemoryBarrierCount = 1;
dep.pBufferMemoryBarriers = &out;
vkCmdPipelineBarrier2(cmd.handle, &dep);
buffer.state = after;
}
void barrier(CmdBuffer &cmd, Image &image, const ImageRange &range, ResourceState before, ResourceState after) {
require_outside_rendering(cmd);
if (before == after && before != ResourceState::Idle && state_is_read_only(before)) {
return;
}
VkImageMemoryBarrier2 out = image_barrier_for(image, range, before, after);
const bool region_local = cmd.is_rendering && after == ResourceState::ColorFetch;
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
if (region_local) {
dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
}
dep.imageMemoryBarrierCount = 1;
dep.pImageMemoryBarriers = &out;
vkCmdPipelineBarrier2(cmd.handle, &dep);
if (range.mip_count == REMAINING_MIPS && range.layer_count == REMAINING_LAYERS) {
image.state = after;
}
}
static void copy_buffer_to_image(
CmdBuffer &cmd, Buffer &srcBuffer, Image &dstImage, ImageAspect aspect, const BufferTextureCopyRegion ®ion
) {
VkBuffer vk_src_buffer = srcBuffer._handle;
VkImage vk_dst_image = dstImage._handle;
VkBufferImageCopy2 copy_region{};
copy_region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
copy_region.bufferOffset = region.buffer_offset;
copy_region.bufferRowLength = region.buffer_row_length;
copy_region.bufferImageHeight = region.buffer_image_height;
copy_region.imageSubresource.aspectMask = vk::image_aspect_to_vk(aspect);
copy_region.imageSubresource.mipLevel = region.mip_level;
copy_region.imageSubresource.baseArrayLayer = region.base_array_layer;
copy_region.imageSubresource.layerCount = region.layer_count;
copy_region.imageOffset = {
static_cast<i32>(region.texture_offset_x),
static_cast<i32>(region.texture_offset_y),
static_cast<i32>(region.texture_offset_z)
};
copy_region.imageExtent = {
static_cast<u32>(region.texture_extent_width),
static_cast<u32>(region.texture_extent_height),
static_cast<u32>(region.texture_extent_depth)
};
VkCopyBufferToImageInfo2 copy_info{};
copy_info.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
copy_info.srcBuffer = vk_src_buffer;
copy_info.dstImage = vk_dst_image;
copy_info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy_info.regionCount = 1;
copy_info.pRegions = ©_region;
vkCmdCopyBufferToImage2(cmd.handle, ©_info);
}
void copy_buffer_to_texture(
CmdBuffer &cmd, Buffer &srcBuffer, ImageView &dstView, const BufferTextureCopyRegion ®ion
) {
if (dstView.desc.image == nullptr || dstView.desc.image->_handle == 0) {
VEL_CRITICAL("CopyBufferToTexture: invalid destination image view");
return;
}
copy_buffer_to_image(cmd, srcBuffer, *dstView.desc.image, dstView.desc.aspect, region);
}
static void copy_image_to_buf(
CmdBuffer &cmd, Image &srcImage, ImageAspect aspect, Buffer &dstBuffer, const rhi::BufferTextureCopyRegion ®ion
) {
VkImage vk_src_image = srcImage._handle;
VkBuffer vk_dst_buffer = dstBuffer._handle;
VkBufferImageCopy2 copy_region{};
copy_region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
copy_region.bufferOffset = region.buffer_offset;
copy_region.bufferRowLength = region.buffer_row_length;
copy_region.bufferImageHeight = region.buffer_image_height;
copy_region.imageSubresource.aspectMask = vk::image_aspect_to_vk(aspect);
copy_region.imageSubresource.mipLevel = region.mip_level;
copy_region.imageSubresource.baseArrayLayer = region.base_array_layer;
copy_region.imageSubresource.layerCount = region.layer_count;
copy_region.imageOffset = {
static_cast<i32>(region.texture_offset_x),
static_cast<i32>(region.texture_offset_y),
static_cast<i32>(region.texture_offset_z)
};
copy_region.imageExtent = {
static_cast<u32>(region.texture_extent_width),
static_cast<u32>(region.texture_extent_height),
static_cast<u32>(region.texture_extent_depth)
};
VkCopyImageToBufferInfo2 copy_info{};
copy_info.sType = VK_STRUCTURE_TYPE_COPY_IMAGE_TO_BUFFER_INFO_2;
copy_info.srcImage = vk_src_image;
copy_info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
copy_info.dstBuffer = vk_dst_buffer;
copy_info.regionCount = 1;
copy_info.pRegions = ©_region;
vkCmdCopyImageToBuffer2(cmd.handle, ©_info);
}
void copy_image_to_buffer(
CmdBuffer &cmd, ImageView &srcView, Buffer &dstBuffer, const rhi::BufferTextureCopyRegion ®ion
) {
if (srcView.desc.image == nullptr || srcView.desc.image->_handle == 0) {
VEL_CRITICAL("CopyImageToBuffer: invalid source image view");
return;
}
copy_image_to_buf(cmd, *srcView.desc.image, srcView.desc.aspect, dstBuffer, region);
}
void copy_buffer_to_device(
Device &device, Buffer &src, u64 src_offset, Buffer &dst, u64 dst_offset, u64 size, CmdBuffer &cmd
) {
VkBufferCopy region{.srcOffset = src_offset, .dstOffset = dst_offset, .size = size};
vkCmdCopyBuffer(cmd.handle, src._handle, dst._handle, 1, ®ion);
}
void blit_image(CmdBuffer &cmd, Image &image, const rhi::ImageBlit *regions, u32 region_count) {
std::vector<VkImageBlit> vk_blits(region_count);
for (u32 i = 0; i < region_count; ++i) {
const rhi::ImageBlit &r = regions[i];
vk_blits[i] = VkImageBlit{
.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, r.src_mip, r.src_layer, 1},
.srcOffsets = {{r.src_x0, r.src_y0, 0}, {r.src_x1, r.src_y1, 1}},
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, r.dst_mip, r.dst_layer, 1},
.dstOffsets = {{r.dst_x0, r.dst_y0, 0}, {r.dst_x1, r.dst_y1, 1}},
};
}
vkCmdBlitImage(
cmd.handle,
image._handle,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image._handle,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
region_count,
vk_blits.data(),
VK_FILTER_LINEAR
);
}
void set_polygon_mode(CmdBuffer &cmd, rhi::PipelineFillMode mode) {
vkCmdSetPolygonModeEXT(cmd.handle, vk::pipeline_fill_mode_to_vk(mode));
}
void set_depth_write_enable(CmdBuffer &cmd, bool enable) {
vkCmdSetDepthWriteEnableEXT(cmd.handle, enable ? VK_TRUE : VK_FALSE);
}
} // namespace rhi