Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-12-03 00:02:17 +02:00
parent 94eb26d9bc
commit d7c5a05d02
13 changed files with 390 additions and 38 deletions

View File

@@ -21,6 +21,8 @@
meson meson
ninja ninja
pkg-config pkg-config
glslang
shaderc
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
vulkan-loader vulkan-loader

View File

@@ -1,7 +1,7 @@
project('vr-compositor', 'cpp', project('vr-compositor', 'cpp',
version: '0.1', version: '0.1',
default_options: [ default_options: [
'cpp_std=c++23', 'cpp_std=c++26',
'warning_level=everything', 'warning_level=everything',
'werror=true', 'werror=true',
] ]
@@ -28,17 +28,30 @@ vkbootstrap_dep = cc.find_library(
required: true, required: true,
) )
add_project_arguments('-Wpedantic', language : ['c', 'cpp'])
add_project_arguments( add_project_arguments(
'-Wpedantic', [
language : ['c', 'cpp'] '-Wno-c++98-compat',
'-Wno-c++98-compat-pedantic',
'-Wno-covered-switch-default',
'-Wno-undef',
'-Wno-padded',
'-Wno-unsafe-buffer-usage',
'-Wno-c23-extensions',
],
language : 'cpp'
) )
subdir('shaders')
exe = executable('vr-compositor', exe = executable('vr-compositor',
[ [
'src/main.cpp', 'src/main.cpp',
'src/Impls.cpp', 'src/Impls.cpp',
'src/Util.cpp', 'src/Util.cpp',
'src/Logger.cpp', 'src/Logger.cpp',
'src/DescriptorLayoutBuilder.cpp',
'src/DescriptorAllocator.cpp',
'src/Application.cpp', 'src/Application.cpp',
], ],
include_directories: vkbootstrap_inc, include_directories: vkbootstrap_inc,
@@ -49,5 +62,8 @@ exe = executable('vr-compositor',
vkbootstrap_dep, vkbootstrap_dep,
zlib_dep, zlib_dep,
sdl3_dep, sdl3_dep,
] ],
cpp_args: [
'--embed-dir=' + join_paths(meson.project_build_root(), 'shaders')
],
) )

23
shaders/gradient.comp Normal file
View File

@@ -0,0 +1,23 @@
#version 460
layout (local_size_x = 16, local_size_y = 16) in;
layout(rgba16f, set = 0, binding = 0) uniform image2D image;
void main() {
ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(image);
if (texelCoord.x >= size.x || texelCoord.y >= size.y)
return;
vec2 uv = (vec2(texelCoord) + 0.5) / vec2(size);
float v = sin(uv.x * 10.0) + cos(uv.y * 10.0);
float r = 0.5 + 0.5 * cos(6.2831 * (uv.x + v));
float g = 0.5 + 0.5 * cos(6.2831 * (uv.y + v + 0.33));
float b = 0.5 + 0.5 * cos(6.2831 * (uv.x - uv.y + 0.66));
vec4 color = vec4(r, g, b, 1.0);
imageStore(image, texelCoord, color);
}

30
shaders/meson.build Normal file
View File

@@ -0,0 +1,30 @@
fs = import('fs')
glslc = find_program('glslc', required : false)
glslang = find_program('glslangValidator', required : false)
if glslc.found()
shader_compiler = glslc
shader_compile_cmd = [shader_compiler, '-o', '@OUTPUT@', '@INPUT@']
elif glslang.found()
shader_compiler = glslang
shader_compile_cmd = [shader_compiler, '-V', '@INPUT@', '-o', '@OUTPUT@']
else
error('Either glslc or glslangValidator is required to build shaders')
endif
shader_sources = files(
'gradient.comp',
)
spirv_shaders = []
foreach shader : shader_sources
shader_name = fs.stem(shader)
spirv_shaders += custom_target(
shader_name + '_spv',
input : shader,
output : shader_name + '.spv',
command : shader_compile_cmd,
build_by_default : true,
)
endforeach

View File

@@ -13,6 +13,7 @@
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
#include "Util.h" #include "Util.h"
#include "src/DescriptorLayoutBuilder.h"
namespace Lunar { namespace Lunar {
@@ -34,6 +35,8 @@ Application::Application()
swapchain_init(); swapchain_init();
commands_init(); commands_init();
sync_init(); sync_init();
descriptors_init();
pipelines_init();
m_logger.info("App init done!"); m_logger.info("App init done!");
} }
@@ -156,7 +159,7 @@ auto Application::swapchain_init() -> void
{ {
int w, h; int w, h;
SDL_GetWindowSize(m_window, &w, &h); SDL_GetWindowSize(m_window, &w, &h);
create_swapchain(w, h); create_swapchain(static_cast<uint32_t>(w), static_cast<uint32_t>(h));
create_draw_image(static_cast<uint32_t>(w), static_cast<uint32_t>(h)); create_draw_image(static_cast<uint32_t>(w), static_cast<uint32_t>(h));
} }
@@ -210,12 +213,85 @@ auto Application::sync_init() -> void
} }
} }
auto Application::descriptors_init() -> void
{
std::vector<DescriptorAllocator::PoolSizeRatio> sizes {
{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 },
};
m_vk.descriptor_allocator.init_pool(m_vkb.dev, 10, sizes);
m_vk.draw_image_descriptor_layout
= DescriptorLayoutBuilder()
.add_binding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)
.build(m_logger, m_vkb.dev, VK_SHADER_STAGE_COMPUTE_BIT);
m_vk.draw_image_descriptors = m_vk.descriptor_allocator.allocate(
m_logger, m_vkb.dev, m_vk.draw_image_descriptor_layout);
update_draw_image_descriptor();
m_vk.deletion_queue.emplace([&]() {
m_vk.descriptor_allocator.destroy_pool(m_vkb.dev);
vkDestroyDescriptorSetLayout(
m_vkb.dev, m_vk.draw_image_descriptor_layout, nullptr);
});
}
auto Application::pipelines_init() -> void { background_pipelines_init(); }
auto Application::background_pipelines_init() -> void
{
VkPipelineLayoutCreateInfo layout_ci {};
layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
layout_ci.pNext = nullptr;
layout_ci.pSetLayouts = &m_vk.draw_image_descriptor_layout;
layout_ci.setLayoutCount = 1;
VK_CHECK(m_logger,
vkCreatePipelineLayout(
m_vkb.dev, &layout_ci, nullptr, &m_vk.gradient_pipeline_layout));
uint8_t compute_draw_shader_data[] {
#embed "gradient.spv"
};
VkShaderModule compute_draw_shader {};
if (!vkutil::load_shader_module(std::span<uint8_t>(compute_draw_shader_data,
sizeof(compute_draw_shader_data)),
m_vkb.dev, &compute_draw_shader)) {
m_logger.err("Failed to load gradient compute shader");
}
VkPipelineShaderStageCreateInfo stage_ci {};
stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stage_ci.pNext = nullptr;
stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stage_ci.module = compute_draw_shader;
stage_ci.pName = "main";
VkComputePipelineCreateInfo compute_pip_ci {};
compute_pip_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
compute_pip_ci.pNext = nullptr;
compute_pip_ci.layout = m_vk.gradient_pipeline_layout;
compute_pip_ci.stage = stage_ci;
VK_CHECK(m_logger,
vkCreateComputePipelines(m_vkb.dev, VK_NULL_HANDLE, 1, &compute_pip_ci,
nullptr, &m_vk.gradient_pipeline));
vkDestroyShaderModule(m_vkb.dev, compute_draw_shader, nullptr);
m_vk.deletion_queue.emplace([&]() {
vkDestroyPipelineLayout(
m_vkb.dev, m_vk.gradient_pipeline_layout, nullptr);
vkDestroyPipeline(m_vkb.dev, m_vk.gradient_pipeline, nullptr);
});
}
auto Application::render() -> void auto Application::render() -> void
{ {
defer(m_vk.frame_number++); defer(m_vk.frame_number++);
if (m_vk.swapchain == VK_NULL_HANDLE if (m_vk.swapchain == VK_NULL_HANDLE || m_vk.swapchain_extent.width == 0
|| m_vk.swapchain_extent.width == 0 || m_vk.swapchain_extent.height == 0) { || m_vk.swapchain_extent.height == 0) {
return; return;
} }
@@ -319,20 +395,14 @@ auto Application::render() -> void
auto Application::draw_background(VkCommandBuffer cmd) -> void auto Application::draw_background(VkCommandBuffer cmd) -> void
{ {
VkClearColorValue clear_value; vkCmdBindPipeline(
float flash { std::abs(std::sin(m_vk.frame_number / 60.f)) }; cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_vk.gradient_pipeline);
clear_value = { { 0x64 / 255.0f * flash, 0x95 / 255.0f * flash, vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,
0xED / 255.0f * flash, 1.0f } }; m_vk.gradient_pipeline_layout, 0, 1, &m_vk.draw_image_descriptors, 0,
nullptr);
VkImageSubresourceRange clear_range { vkCmdDispatch(cmd,
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, static_cast<uint32_t>(std::ceil(m_vk.draw_extent.width / 16.0)),
.baseMipLevel = 0, static_cast<uint32_t>(std::ceil(m_vk.draw_extent.height / 16.0)), 1);
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
};
vkCmdClearColorImage(cmd, m_vk.draw_image.image, VK_IMAGE_LAYOUT_GENERAL,
&clear_value, 1, &clear_range);
} }
auto Application::create_swapchain(uint32_t width, uint32_t height) -> void auto Application::create_swapchain(uint32_t width, uint32_t height) -> void
@@ -404,6 +474,25 @@ auto Application::create_draw_image(uint32_t width, uint32_t height) -> void
m_vkb.dev, &rview_ci, nullptr, &m_vk.draw_image.image_view)); m_vkb.dev, &rview_ci, nullptr, &m_vk.draw_image.image_view));
} }
auto Application::update_draw_image_descriptor() -> void
{
// Point the storage image descriptor at the current draw image view
VkDescriptorImageInfo img_info {};
img_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
img_info.imageView = m_vk.draw_image.image_view;
VkWriteDescriptorSet draw_img_write {};
draw_img_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
draw_img_write.pNext = nullptr;
draw_img_write.dstBinding = 0;
draw_img_write.dstSet = m_vk.draw_image_descriptors;
draw_img_write.descriptorCount = 1;
draw_img_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
draw_img_write.pImageInfo = &img_info;
vkUpdateDescriptorSets(m_vkb.dev, 1, &draw_img_write, 0, nullptr);
}
auto Application::destroy_draw_image() -> void auto Application::destroy_draw_image() -> void
{ {
if (m_vk.draw_image.image_view != VK_NULL_HANDLE) { if (m_vk.draw_image.image_view != VK_NULL_HANDLE) {
@@ -435,6 +524,7 @@ auto Application::recreate_swapchain(uint32_t width, uint32_t height) -> void
create_swapchain(width, height); create_swapchain(width, height);
create_draw_image(width, height); create_draw_image(width, height);
update_draw_image_descriptor();
} }
auto Application::destroy_swapchain() -> void auto Application::destroy_swapchain() -> void
@@ -496,8 +586,8 @@ auto Application::run() -> void
} else if (e.type == SDL_EVENT_WINDOW_RESIZED) { } else if (e.type == SDL_EVENT_WINDOW_RESIZED) {
int width {}, height {}; int width {}, height {};
SDL_GetWindowSize(m_window, &width, &height); SDL_GetWindowSize(m_window, &width, &height);
recreate_swapchain( recreate_swapchain(static_cast<uint32_t>(width),
static_cast<uint32_t>(width), static_cast<uint32_t>(height)); static_cast<uint32_t>(height));
} }
} }

View File

@@ -10,6 +10,7 @@
#include "AllocatedImage.h" #include "AllocatedImage.h"
#include "DeletionQueue.h" #include "DeletionQueue.h"
#include "Logger.h" #include "Logger.h"
#include "src/DescriptorAllocator.h"
namespace Lunar { namespace Lunar {
@@ -35,12 +36,16 @@ private:
auto swapchain_init() -> void; auto swapchain_init() -> void;
auto commands_init() -> void; auto commands_init() -> void;
auto sync_init() -> void; auto sync_init() -> void;
auto descriptors_init() -> void;
auto pipelines_init() -> void;
auto background_pipelines_init() -> void;
auto draw_background(VkCommandBuffer cmd) -> void; auto draw_background(VkCommandBuffer cmd) -> void;
auto render() -> void; auto render() -> void;
auto create_swapchain(uint32_t width, uint32_t height) -> void; auto create_swapchain(uint32_t width, uint32_t height) -> void;
auto create_draw_image(uint32_t width, uint32_t height) -> void; auto create_draw_image(uint32_t width, uint32_t height) -> void;
auto update_draw_image_descriptor() -> void;
auto destroy_draw_image() -> void; auto destroy_draw_image() -> void;
auto recreate_swapchain(uint32_t width, uint32_t height) -> void; auto recreate_swapchain(uint32_t width, uint32_t height) -> void;
auto destroy_swapchain() -> void; auto destroy_swapchain() -> void;
@@ -54,11 +59,11 @@ private:
struct { struct {
VkSwapchainKHR swapchain { VK_NULL_HANDLE }; VkSwapchainKHR swapchain { VK_NULL_HANDLE };
VkFormat swapchain_image_format;
VkSurfaceKHR surface { nullptr }; VkSurfaceKHR surface { nullptr };
VkFormat swapchain_image_format;
VkQueue graphics_queue { nullptr };
uint32_t graphics_queue_family { 0 }; uint32_t graphics_queue_family { 0 };
VkQueue graphics_queue { nullptr };
std::vector<VkImage> swapchain_images; std::vector<VkImage> swapchain_images;
std::vector<VkImageView> swapchain_image_views; std::vector<VkImageView> swapchain_image_views;
@@ -75,6 +80,13 @@ private:
VkExtent2D draw_extent {}; VkExtent2D draw_extent {};
VmaAllocator allocator; VmaAllocator allocator;
DescriptorAllocator descriptor_allocator;
VkDescriptorSet draw_image_descriptors;
VkDescriptorSetLayout draw_image_descriptor_layout;
VkPipeline gradient_pipeline {};
VkPipelineLayout gradient_pipeline_layout {};
DeletionQueue deletion_queue; DeletionQueue deletion_queue;

View File

@@ -0,0 +1,57 @@
#include "DescriptorAllocator.h"
#include <vector>
#include "Util.h"
namespace Lunar {
auto DescriptorAllocator::init_pool(VkDevice dev, uint32_t max_sets,
std::span<PoolSizeRatio> pool_ratios) -> void
{
std::vector<VkDescriptorPoolSize> pool_sizes;
for (auto const &ratio : pool_ratios) {
pool_sizes.emplace_back(VkDescriptorPoolSize {
.type = ratio.type,
.descriptorCount = static_cast<uint32_t>(ratio.ratio * max_sets),
});
}
VkDescriptorPoolCreateInfo ci {};
ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
ci.pNext = nullptr;
ci.flags = 0;
ci.maxSets = max_sets;
ci.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
ci.pPoolSizes = pool_sizes.data();
vkCreateDescriptorPool(dev, &ci, nullptr, &pool);
}
auto DescriptorAllocator::clear_descriptors(VkDevice dev) -> void
{
vkResetDescriptorPool(dev, pool, 0);
}
auto DescriptorAllocator::destroy_pool(VkDevice dev) -> void
{
vkDestroyDescriptorPool(dev, pool, nullptr);
}
auto DescriptorAllocator::allocate(Logger &logger, VkDevice dev,
VkDescriptorSetLayout layout) -> VkDescriptorSet
{
VkDescriptorSetAllocateInfo ai {};
ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
ai.pNext = nullptr;
ai.descriptorPool = pool;
ai.descriptorSetCount = 1;
ai.pSetLayouts = &layout;
VkDescriptorSet ds;
VK_CHECK(logger, vkAllocateDescriptorSets(dev, &ai, &ds));
return ds;
}
} // namespace Lunar

28
src/DescriptorAllocator.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <span>
#include <vulkan/vulkan_core.h>
#include "Logger.h"
namespace Lunar {
struct DescriptorAllocator {
struct PoolSizeRatio {
VkDescriptorType type;
float ratio;
};
VkDescriptorPool pool;
auto init_pool(VkDevice dev, uint32_t max_sets,
std::span<PoolSizeRatio> pool_ratios) -> void;
auto clear_descriptors(VkDevice dev) -> void;
auto destroy_pool(VkDevice dev) -> void;
auto allocate(Logger &logger, VkDevice dev, VkDescriptorSetLayout layout)
-> VkDescriptorSet;
};
} // namespace Lunar

View File

@@ -0,0 +1,42 @@
#include "DescriptorLayoutBuilder.h"
#include "Util.h"
namespace Lunar {
auto DescriptorLayoutBuilder::add_binding(
uint32_t binding, VkDescriptorType type) -> DescriptorLayoutBuilder &
{
VkDescriptorSetLayoutBinding b {};
b.binding = binding;
b.descriptorCount = 1;
b.descriptorType = type;
bindings.emplace_back(b);
return *this;
}
auto DescriptorLayoutBuilder::build(Logger &logger, VkDevice dev,
VkShaderStageFlags shader_stages, void *pNext,
VkDescriptorSetLayoutCreateFlags flags) -> VkDescriptorSetLayout
{
for (auto &&b : bindings) {
b.stageFlags |= shader_stages;
}
VkDescriptorSetLayoutCreateInfo ci {};
ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
ci.pNext = pNext;
ci.pBindings = bindings.data();
ci.bindingCount = static_cast<uint32_t>(bindings.size());
ci.flags = flags;
VkDescriptorSetLayout set;
VK_CHECK(logger, vkCreateDescriptorSetLayout(dev, &ci, nullptr, &set));
return set;
}
} // namespace Lunar

View File

@@ -0,0 +1,22 @@
#pragma once
#include <vector>
#include <vulkan/vulkan_core.h>
#include "Logger.h"
namespace Lunar {
struct DescriptorLayoutBuilder {
std::vector<VkDescriptorSetLayoutBinding> bindings;
auto add_binding(uint32_t binding, VkDescriptorType type)
-> DescriptorLayoutBuilder &;
auto clear() -> void { bindings.clear(); }
auto build(Logger &logger, VkDevice dev, VkShaderStageFlags shader_stages,
void *pNext = nullptr, VkDescriptorSetLayoutCreateFlags flags = 0)
-> VkDescriptorSetLayout;
};
} // namespace Lunar

View File

@@ -31,7 +31,7 @@
#define FG_GRAY "\033[90m" #define FG_GRAY "\033[90m"
#define ANSI_RESET "\033[0m" #define ANSI_RESET "\033[0m"
std::filesystem::path get_log_path(std::string_view app_name) static std::filesystem::path get_log_path(std::string_view app_name)
{ {
#ifdef _WIN32 #ifdef _WIN32
PWSTR path = nullptr; PWSTR path = nullptr;
@@ -53,7 +53,7 @@ std::filesystem::path get_log_path(std::string_view app_name)
} }
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
int compress_file(std::filesystem::path const &input_path, static int compress_file(std::filesystem::path const &input_path,
std::filesystem::path const &output_path) std::filesystem::path const &output_path)
{ {
size_t const chunk_size = 4096; size_t const chunk_size = 4096;
@@ -69,7 +69,7 @@ int compress_file(std::filesystem::path const &input_path,
std::vector<char> buffer(chunk_size); std::vector<char> buffer(chunk_size);
while (in) { while (in) {
in.read(buffer.data(), buffer.size()); in.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
std::streamsize bytes = in.gcount(); std::streamsize bytes = in.gcount();
if (bytes > 0) if (bytes > 0)
gzwrite(out, buffer.data(), static_cast<unsigned int>(bytes)); gzwrite(out, buffer.data(), static_cast<unsigned int>(bytes));
@@ -99,12 +99,14 @@ Logger::Logger(std::string_view app_name)
if (!file.is_regular_file()) if (!file.is_regular_file())
continue; continue;
int v; auto name = file.path().filename().stem().string();
if (std::sscanf( constexpr std::string_view prefix = "log_";
file.path().filename().stem().string().c_str(), "log_%d", &v)
!= 1) { if (name.rfind(prefix, 0) != 0) {
continue; continue;
} }
int v = std::stoi(name.substr(prefix.size()));
if (v > max) if (v > max)
max = v; max = v;
@@ -139,7 +141,7 @@ auto Logger::err(std::string_view msg) -> void
log(Logger::Level::Error, msg); log(Logger::Level::Error, msg);
} }
std::string get_current_time_string() static std::string get_current_time_string()
{ {
auto now { std::chrono::system_clock::now() }; auto now { std::chrono::system_clock::now() };
auto now_c { std::chrono::system_clock::to_time_t(now) }; auto now_c { std::chrono::system_clock::to_time_t(now) };

View File

@@ -1,5 +1,7 @@
#include "Util.h" #include "Util.h"
#include <span>
namespace vkutil { namespace vkutil {
auto transition_image(VkCommandBuffer cmd, VkImage image, auto transition_image(VkCommandBuffer cmd, VkImage image,
@@ -75,6 +77,32 @@ auto copy_image_to_image(VkCommandBuffer cmd, VkImage source,
vkCmdBlitImage2(cmd, &blit_info); vkCmdBlitImage2(cmd, &blit_info);
} }
auto load_shader_module(std::span<uint8_t> spirv_data, VkDevice device,
VkShaderModule *out_shader_module) -> bool
{
if (!device || !out_shader_module)
return false;
if (spirv_data.empty() || (spirv_data.size() % 4) != 0)
return false;
VkShaderModuleCreateInfo create_info {};
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.codeSize = spirv_data.size();
create_info.pCode = reinterpret_cast<uint32_t const *>(spirv_data.data());
VkResult const res = vkCreateShaderModule(
device, &create_info, nullptr, out_shader_module);
if (res != VK_SUCCESS) {
*out_shader_module = VK_NULL_HANDLE;
return false;
}
return true;
}
} // namespace vkutil } // namespace vkutil
namespace vkinit { namespace vkinit {

View File

@@ -1,16 +1,14 @@
#pragma once #pragma once
#include <string_view> #include <span>
#include <vulkan/vk_enum_string_helper.h> #include <vulkan/vk_enum_string_helper.h>
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
using namespace std::string_view_literals;
template<typename F> struct privDefer { template<typename F> struct privDefer {
F f; F f;
privDefer(F f) privDefer(F f_)
: f(f) : f(f_)
{ {
} }
~privDefer() { f(); } ~privDefer() { f(); }
@@ -44,6 +42,8 @@ auto transition_image(VkCommandBuffer cmd, VkImage image,
VkImageLayout current_layout, VkImageLayout new_layout) -> void; VkImageLayout current_layout, VkImageLayout new_layout) -> void;
auto copy_image_to_image(VkCommandBuffer cmd, VkImage source, auto copy_image_to_image(VkCommandBuffer cmd, VkImage source,
VkImage destination, VkExtent2D src_size, VkExtent2D dst_size) -> void; VkImage destination, VkExtent2D src_size, VkExtent2D dst_size) -> void;
auto load_shader_module(std::span<uint8_t> spirv_data, VkDevice device,
VkShaderModule *out_shader_module) -> bool;
} // namespace vkutil } // namespace vkutil