downsample & alpha shaderchain

This commit is contained in:
PancakeTAS 2025-06-30 03:27:22 +02:00
parent 0345ec9e69
commit c3e1d0ded9
No known key found for this signature in database
8 changed files with 231 additions and 16 deletions

View file

@ -0,0 +1,67 @@
#ifndef ALPHA_HPP
#define ALPHA_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 {
///
/// Shader chain alpha.
///
/// Takes an 8-bit R texture creates four quarter-sized 8-bit RGBA textures.
///
class Alpha {
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 texture to process
///
/// @throws ls::vulkan_error if resource creation fails.
///
Alpha(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
Alpha(const Alpha&) noexcept = default;
Alpha& operator=(const Alpha&) noexcept = default;
Alpha(Alpha&&) noexcept = default;
Alpha& operator=(Alpha&&) noexcept = default;
~Alpha() = default;
private:
std::vector<Core::ShaderModule> shaderModules{4};
std::vector<Core::Pipeline> pipelines{4};
std::vector<Core::DescriptorSet> descriptorSets{4};
Core::Image inImage;
std::vector<Core::Image> tempTex1{2}; // half-size
std::vector<Core::Image> tempTex2{2}; // half-size
std::vector<Core::Image> tempTex3{4}; // quarter-size
std::vector<Core::Image> outImages{4}; // quarter-size
};
}
#endif // ALPHA_HPP

View file

@ -57,7 +57,7 @@ namespace Vulkan::Shaderchains {
Core::Image inImage;
std::vector<Core::Image> outImages;
std::vector<Core::Image> outImages{7};
};
}

View file

@ -22,8 +22,8 @@ namespace Vulkan::Utils {
///
void insertBarrier(
const Vulkan::Core::CommandBuffer& buffer,
std::vector<Vulkan::Core::Image> r2wImages,
std::vector<Vulkan::Core::Image> w2rImages);
std::vector<Vulkan::Core::Image> w2rImages,
std::vector<Vulkan::Core::Image> r2wImages);
///
/// Upload a DDS file to a Vulkan image.

View file

@ -13,7 +13,7 @@ Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) {
.addressModeU = mode,
.addressModeV = mode,
.addressModeW = mode,
.maxLod = VK_LOD_CLAMP_NONE
.maxLod = 15.99609F
};
VkSampler samplerHandle{};
auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle);

View file

@ -5,6 +5,7 @@
#include "core/image.hpp"
#include "device.hpp"
#include "instance.hpp"
#include "shaderchains/alpha.hpp"
#include "shaderchains/downsample.hpp"
#include "utils.hpp"
@ -13,7 +14,7 @@
#include <renderdoc_app.h>
#include <dlfcn.h>
#include <unistd.h>
#include <vulkan/vulkan_core.h>
#include <vector>
using namespace Vulkan;
@ -52,6 +53,11 @@ int main() {
// create the shaderchains
Shaderchains::Downsample downsample(device, descriptorPool, inputImage);
std::vector<Shaderchains::Alpha> alphas;
alphas.reserve(7);
for (size_t i = 0; i < 7; ++i)
alphas.emplace_back(device, descriptorPool, downsample.getOutImages().at(i));
// start the rendering pipeline
if (rdoc)
rdoc->StartFrameCapture(nullptr, nullptr);
@ -60,6 +66,8 @@ int main() {
commandBuffer.begin();
downsample.Dispatch(commandBuffer);
for (size_t i = 0; i < 7; i++)
alphas.at(6 - i).Dispatch(commandBuffer);
// finish the rendering pipeline
commandBuffer.end();

View file

@ -0,0 +1,141 @@
#include "shaderchains/alpha.hpp"
#include "utils.hpp"
using namespace Vulkan::Shaderchains;
Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool,
const Core::Image& inImage) : inImage(inImage) {
this->shaderModules = {{
Core::ShaderModule(device, "rsc/shaders/alpha/0.spv",
{ { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
Core::ShaderModule(device, "rsc/shaders/alpha/1.spv",
{ { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
Core::ShaderModule(device, "rsc/shaders/alpha/2.spv",
{ { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }),
Core::ShaderModule(device, "rsc/shaders/alpha/3.spv",
{ { 1, VK_DESCRIPTOR_TYPE_SAMPLER },
{ 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE },
{ 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } })
}};
for (size_t i = 0; i < 4; i++) {
this->pipelines.at(i) = Core::Pipeline(device,
this->shaderModules.at(i));
this->descriptorSets.at(i) = Core::DescriptorSet(device, pool,
this->shaderModules.at(i));
}
auto extent = inImage.getExtent();
auto halfWidth = (extent.width + 1) >> 1;
auto halfHeight = (extent.height + 1) >> 1;
auto quarterWidth = (extent.width + 3) >> 2;
auto quarterHeight = (extent.height + 3) >> 2;
for (size_t i = 0; i < 2; i++) {
this->tempTex1.at(i) = Core::Image(device,
{ halfWidth, halfHeight },
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
this->tempTex2.at(i) = Core::Image(device,
{ halfWidth, halfHeight },
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
}
for (size_t i = 0; i < 4; i++) {
this->tempTex3.at(i) = Core::Image(device,
{ quarterWidth, quarterHeight },
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
this->outImages.at(i) = Core::Image(device,
{ quarterWidth, quarterHeight },
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
}
this->descriptorSets.at(0).update(device)
.add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder)
.add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImage)
.add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex1)
.build();
this->descriptorSets.at(1).update(device)
.add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder)
.add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex1)
.add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex2)
.build();
this->descriptorSets.at(2).update(device)
.add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder)
.add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex2)
.add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex3)
.build();
this->descriptorSets.at(3).update(device)
.add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder)
.add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex3)
.add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImages)
.build();
}
void Alpha::Dispatch(const Core::CommandBuffer& buf) {
const auto halfExtent = this->tempTex1.at(0).getExtent();
const auto quarterExtent = this->tempTex3.at(0).getExtent();
// first pass
Utils::insertBarrier(
buf,
{ this->inImage },
this->tempTex1
);
this->pipelines.at(0).bind(buf);
this->descriptorSets.at(0).bind(buf, this->pipelines.at(0));
uint32_t threadsX = (halfExtent.width + 7) >> 3;
uint32_t threadsY = (halfExtent.height + 7) >> 3;
buf.dispatch(threadsX, threadsY, 1);
// second pass
Utils::insertBarrier(
buf,
this->tempTex1,
this->tempTex2
);
this->pipelines.at(1).bind(buf);
this->descriptorSets.at(1).bind(buf, this->pipelines.at(1));
buf.dispatch(threadsX, threadsY, 1);
// third pass
Utils::insertBarrier(
buf,
this->tempTex2,
this->tempTex3
);
this->pipelines.at(2).bind(buf);
this->descriptorSets.at(2).bind(buf, this->pipelines.at(2));
threadsX = (quarterExtent.width + 7) >> 3;
threadsY = (quarterExtent.height + 7) >> 3;
buf.dispatch(threadsX, threadsY, 1);
// fourth pass
Utils::insertBarrier(
buf,
this->tempTex3,
this->outImages
);
this->pipelines.at(3).bind(buf);
this->descriptorSets.at(3).bind(buf, this->pipelines.at(3));
buf.dispatch(threadsX, threadsY, 1);
}

View file

@ -20,7 +20,6 @@ Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool,
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 },
@ -44,8 +43,8 @@ void Downsample::Dispatch(const Core::CommandBuffer& buf) {
Utils::insertBarrier(
buf,
this->outImages,
{ this->inImage }
{ this->inImage },
this->outImages
);
this->pipeline.bind(buf);

View file

@ -8,8 +8,8 @@ using namespace Vulkan;
void Utils::insertBarrier(
const Core::CommandBuffer& buffer,
std::vector<Core::Image> r2wImages,
std::vector<Core::Image> w2rImages) {
std::vector<Core::Image> w2rImages,
std::vector<Core::Image> r2wImages) {
std::vector<VkImageMemoryBarrier2> barriers(r2wImages.size() + w2rImages.size());
size_t index = 0;
@ -60,16 +60,16 @@ void Utils::insertBarrier(
void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPool,
Core::Image& image, const std::string& path) {
// read image bytecode
std::ifstream file(path, std::ios::ate | std::ios::binary);
if (!file)
std::ifstream file(path.data(), std::ios::binary | std::ios::ate);
if (!file.is_open())
throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path);
std::streamsize size = file.tellg();
size -= 124 - 4; // dds header and magic bytes
std::vector<uint8_t> code(static_cast<size_t>(size));
size -= 124 + 4; // dds header and magic bytes
std::vector<char> code(static_cast<size_t>(size));
file.seekg(0, std::ios::beg);
if (!file.read(reinterpret_cast<char*>(code.data()), size))
file.seekg(124 + 4, std::ios::beg);
if (!file.read(code.data(), size))
throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path);
file.close();