initial shaderchain

This commit is contained in:
PancakeTAS 2025-06-30 00:33:28 +02:00
parent 15c2319ab6
commit 6b21e1f298
No known key found for this signature in database
11 changed files with 234 additions and 115 deletions

4
.gitignore vendored
View file

@ -7,5 +7,5 @@
/.cache
/.ccls
# private shaders
/shaders
# private resources
/rsc

View file

@ -18,6 +18,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
file(GLOB SOURCES
"src/core/*.cpp"
"src/shaderchains/*.cpp"
"src/utils/*.cpp"
"src/*.cpp"
)

View file

@ -45,6 +45,11 @@ namespace Vulkan::Core {
/// Get the aspect flags of the image.
[[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; }
/// Set the layout of the image.
void setLayout(VkImageLayout layout) { *this->layout = layout; }
/// Get the current layout of the image.
[[nodiscard]] VkImageLayout getLayout() const { return *this->layout; }
/// Trivially copyable, moveable and destructible
Image(const Image&) noexcept = default;
Image& operator=(const Image&) noexcept = default;
@ -56,6 +61,8 @@ namespace Vulkan::Core {
std::shared_ptr<VkDeviceMemory> memory;
std::shared_ptr<VkImageView> view;
std::shared_ptr<VkImageLayout> layout;
VkExtent2D extent{};
VkFormat format{};
VkImageAspectFlags aspectFlags{};

View file

@ -0,0 +1,65 @@
#ifndef DOWNSAMPLE_HPP
#define DOWNSAMPLE_HPP
#include "core/buffer.hpp"
#include "core/commandbuffer.hpp"
#include "core/descriptorpool.hpp"
#include "core/descriptorset.hpp"
#include "core/image.hpp"
#include "core/pipeline.hpp"
#include "core/shadermodule.hpp"
#include "device.hpp"
namespace Vulkan::Shaderchains {
///
/// Downsample shader.
///
/// Takes an 8-bit RGBA texture and downsamples it into 7x 8-bit R textures.
///
class Downsample {
public:
///
/// Initialize the shaderchain.
///
/// @param device The Vulkan device to create the resources on.
/// @param pool The descriptor pool to allocate in.
/// @param inImage The input image to downsample.
///
/// @throws ls::vulkan_error if resource creation fails.
///
Downsample(const Device& device, const Core::DescriptorPool& pool,
const Core::Image& inImage);
///
/// Dispatch the shaderchain.
///
/// @param buf The command buffer to use for dispatching.
///
/// @throws std::logic_error if the command buffer is not recording.
///
void Dispatch(const Core::CommandBuffer& buf);
/// Get the output images.
[[nodiscard]] const auto& getOutImages() const { return this->outImages; }
/// Trivially copyable, moveable and destructible
Downsample(const Downsample&) noexcept = default;
Downsample& operator=(const Downsample&) noexcept = default;
Downsample(Downsample&&) noexcept = default;
Downsample& operator=(Downsample&&) noexcept = default;
~Downsample() = default;
private:
Core::ShaderModule shaderModule;
Core::Pipeline pipeline;
Core::DescriptorSet descriptorSet;
Core::Buffer buffer;
Core::Image inImage;
std::vector<Core::Image> outImages;
};
}
#endif // DOWNSAMPLE_HPP

View file

@ -16,24 +16,13 @@ namespace Barriers {
/// @param buffer Command buffer to insert barriers into
/// @param r2wImages Images that are being read and will be written to
/// @param w2rImages Images that are being written to and will be read from
/// @param srcLayout Optional source layout for the images, defaults to VK_IMAGE_LAYOUT_GENERAL
///
/// @throws std::logic_error if the command buffer is not in Recording state
///
void insertBarrier(
const Vulkan::Core::CommandBuffer& buffer,
const std::vector<Vulkan::Core::Image>& r2wImages,
const std::vector<Vulkan::Core::Image>& w2rImages,
std::optional<VkImageLayout> srcLayout = std::nullopt);
///
/// Insert a global memory barrier in a command buffer.
///
/// @param buffer Command buffer to insert the barrier into
///
/// @throws std::logic_error if the command buffer is not in Recording state
///
void insertGlobalBarrier(const Vulkan::Core::CommandBuffer& buffer);
std::vector<Vulkan::Core::Image> r2wImages,
std::vector<Vulkan::Core::Image> w2rImages);
};

41
include/utils/global.hpp Normal file
View file

@ -0,0 +1,41 @@
#ifndef GLOBAL_HPP
#define GLOBAL_HPP
#include "core/sampler.hpp"
#include <array>
namespace Vulkan::Globals {
/// Global sampler with address mode set to clamp to border.
extern Core::Sampler samplerClampBorder;
/// Global sampler with address mode set to clamp to edge.
extern Core::Sampler samplerClampEdge;
/// Commonly used constant buffer structure for shaders.
struct FgBuffer {
std::array<uint32_t, 2> inputOffset;
uint32_t firstIter;
uint32_t firstIterS;
uint32_t advancedColorKind;
uint32_t hdrSupport;
float resolutionInvScale;
float timestamp;
float uiThreshold;
std::array<uint32_t, 3> pad;
};
/// Default instance of the FgBuffer.
extern FgBuffer fgBuffer;
static_assert(sizeof(FgBuffer) == 48, "FgBuffer must be 48 bytes in size.");
/// Initialize global resources.
void initializeGlobals(const Device& device);
/// Uninitialize global resources.
void uninitializeGlobals() noexcept;
}
#endif // GLOBAL_HPP

View file

@ -90,6 +90,7 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format,
throw ls::vulkan_error(res, "Failed to create image view");
// store objects in shared ptr
this->layout = std::make_shared<VkImageLayout>(VK_IMAGE_LAYOUT_UNDEFINED);
this->image = std::shared_ptr<VkImage>(
new VkImage(imageHandle),
[dev = device.handle()](VkImage* img) {

View file

@ -1,109 +1,55 @@
#include "core/buffer.hpp"
#include "core/commandbuffer.hpp"
#include "core/commandpool.hpp"
#include "core/descriptorpool.hpp"
#include "core/descriptorset.hpp"
#include "core/fence.hpp"
#include "core/image.hpp"
#include "core/pipeline.hpp"
#include "core/sampler.hpp"
#include "core/shadermodule.hpp"
#include "device.hpp"
#include "instance.hpp"
#include "utils/memorybarriers.hpp"
#include "shaderchains/downsample.hpp"
#include "utils/global.hpp"
#include <array>
#include <cassert>
#include <iostream>
#include <vector>
#include <vulkan/vulkan_core.h>
using namespace Vulkan;
struct DataBuffer {
std::array<uint32_t, 2> inputOffset;
uint32_t firstIter;
uint32_t firstIterS;
uint32_t advancedColorKind;
uint32_t hdrSupport;
float resolutionInvScale;
float timestamp;
float uiThreshold;
std::array<uint32_t, 3> pad;
};
const static DataBuffer data{
.inputOffset = { 0, 29 },
.resolutionInvScale = 1.0F,
.timestamp = 0.5,
.uiThreshold = 0.1F
};
int main() {
// initialize application
const Instance instance;
const Device device(instance);
const Core::DescriptorPool descriptorPool(device);
const Core::CommandPool commandPool(device);
const Core::Fence fence(device);
const Core::ShaderModule computeShader(device, "shaders/downsample.spv",
{ { 1, VK_DESCRIPTOR_TYPE_SAMPLER},
{ 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE},
{ 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE},
{ 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER} });
const Core::Pipeline computePipeline(device, computeShader);
Globals::initializeGlobals(device);
const Core::Sampler sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
const std::vector<Core::Image> inputImages(1, Core::Image(
// create initialization resources
const Core::Image inputImage(
device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT
));
);
const Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
std::vector<Core::Image> outputImages;
outputImages.reserve(7);
for (size_t i = 0; i < 7; ++i)
outputImages.emplace_back(device,
VkExtent2D { .width = 2560U >> i, .height = 1411U >> i },
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
// load descriptor set
const Core::DescriptorSet descriptorSet(device, descriptorPool, computeShader);
descriptorSet.update(device)
.add(VK_DESCRIPTOR_TYPE_SAMPLER, sampler)
.add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inputImages)
.add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages)
.add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, buffer)
.build();
// start pass
Core::Fence fence(device);
// create the shaderchains
Shaderchains::Downsample downsample(device, descriptorPool, inputImage);
// start the rendering pipeline
Core::CommandBuffer commandBuffer(device, commandPool);
commandBuffer.begin();
// render
Barriers::insertBarrier(
commandBuffer,
inputImages,
outputImages,
VK_IMAGE_LAYOUT_UNDEFINED
);
downsample.Dispatch(commandBuffer);
computePipeline.bind(commandBuffer);
descriptorSet.bind(commandBuffer, computePipeline);
commandBuffer.dispatch(40, 23, 1);
// end pass
// finish the rendering pipeline
commandBuffer.end();
commandBuffer.submit(device.getComputeQueue(), fence);
assert(fence.wait(device) && "Synchronization fence timed out");
if (!fence.wait(device)) {
Globals::uninitializeGlobals();
std::cerr << "Application failed due to timeout" << '\n';
return 1;
}
Globals::uninitializeGlobals();
std::cerr << "Application finished" << '\n';
return 0;

View file

@ -0,0 +1,55 @@
#include "shaderchains/downsample.hpp"
#include "utils/global.hpp"
#include "utils/barriers.hpp"
using namespace Vulkan::Shaderchains;
Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool,
const Core::Image& inImage) : inImage(inImage) {
// create internal resources
this->shaderModule = Core::ShaderModule(device, "rsc/shaders/downsample.spv",
{ { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE },
{ 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } });
this->pipeline = Core::Pipeline(device, this->shaderModule);
this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule);
const Globals::FgBuffer data = Globals::fgBuffer;
this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
auto extent = inImage.getExtent();
// create output images
this->outImages.resize(7);
for (size_t i = 0; i < 7; i++)
this->outImages.at(i) = Core::Image(device,
{ extent.width >> i, extent.height >> i },
VK_FORMAT_R8_UNORM,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
// update descriptor set
this->descriptorSet.update(device)
.add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder)
.add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImage)
.add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImages)
.add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer)
.build();
}
void Downsample::Dispatch(const Core::CommandBuffer& buf) {
auto extent = inImage.getExtent();
const uint32_t threadsX = (extent.width + 63) >> 6;
const uint32_t threadsY = (extent.height + 63) >> 6;
Barriers::insertBarrier(
buf,
this->outImages,
{ this->inImage }
);
this->pipeline.bind(buf);
this->descriptorSet.bind(buf, this->pipeline);
buf.dispatch(threadsX, threadsY, 1);
}

View file

@ -1,25 +1,22 @@
#include "utils/memorybarriers.hpp"
#include <optional>
#include "utils/barriers.hpp"
using namespace Barriers;
void Barriers::insertBarrier(
const Vulkan::Core::CommandBuffer& buffer,
const std::vector<Vulkan::Core::Image>& r2wImages,
const std::vector<Vulkan::Core::Image>& w2rImages,
std::optional<VkImageLayout> srcLayout) {
std::vector<Vulkan::Core::Image> r2wImages,
std::vector<Vulkan::Core::Image> w2rImages) {
std::vector<VkImageMemoryBarrier2> barriers(r2wImages.size() + w2rImages.size());
size_t index = 0;
for (const auto& image : r2wImages) {
for (auto& image : r2wImages) {
barriers[index++] = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
.oldLayout = srcLayout.value_or(VK_IMAGE_LAYOUT_GENERAL),
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.image = image.handle(),
.subresourceRange = {
@ -28,15 +25,16 @@ void Barriers::insertBarrier(
.layerCount = 1
}
};
image.setLayout(VK_IMAGE_LAYOUT_GENERAL);
}
for (const auto& image : w2rImages) {
for (auto& image : w2rImages) {
barriers[index++] = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
.oldLayout = srcLayout.value_or(VK_IMAGE_LAYOUT_GENERAL),
.oldLayout = image.getLayout(),
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.image = image.handle(),
.subresourceRange = {
@ -45,6 +43,7 @@ void Barriers::insertBarrier(
.layerCount = 1
}
};
image.setLayout(VK_IMAGE_LAYOUT_GENERAL);
}
const VkDependencyInfo dependencyInfo = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
@ -53,19 +52,3 @@ void Barriers::insertBarrier(
};
vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo);
}
void Barriers::insertGlobalBarrier(const Vulkan::Core::CommandBuffer& buffer) {
const VkMemoryBarrier2 barrier = {
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT
};
const VkDependencyInfo dependencyInfo = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.memoryBarrierCount = 1,
.pMemoryBarriers = &barrier
};
vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo);
}

31
src/utils/globals.cpp Normal file
View file

@ -0,0 +1,31 @@
#include "utils/global.hpp"
using namespace Vulkan;
Core::Sampler Globals::samplerClampBorder;
Core::Sampler Globals::samplerClampEdge;
Globals::FgBuffer Globals::fgBuffer;
void Globals::initializeGlobals(const Device& device) {
// initialize global samplers
samplerClampBorder = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
samplerClampEdge = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
// initialize global constant buffer
fgBuffer = {
.inputOffset = { 0, 29 },
.resolutionInvScale = 1.0F,
.timestamp = 0.5F,
.uiThreshold = 0.1F,
};
}
void Globals::uninitializeGlobals() noexcept {
// uninitialize global samplers
samplerClampBorder = Core::Sampler();
samplerClampEdge = Core::Sampler();
// uninitialize global constant buffer
fgBuffer = Globals::FgBuffer();
}