mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2025-10-30 07:01:10 +00:00
initial shaderchain
This commit is contained in:
parent
15c2319ab6
commit
6b21e1f298
11 changed files with 234 additions and 115 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -7,5 +7,5 @@
|
|||
/.cache
|
||||
/.ccls
|
||||
|
||||
# private shaders
|
||||
/shaders
|
||||
# private resources
|
||||
/rsc
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||
|
||||
file(GLOB SOURCES
|
||||
"src/core/*.cpp"
|
||||
"src/shaderchains/*.cpp"
|
||||
"src/utils/*.cpp"
|
||||
"src/*.cpp"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
65
include/shaderchains/downsample.hpp
Normal file
65
include/shaderchains/downsample.hpp
Normal 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
|
||||
|
|
@ -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
41
include/utils/global.hpp
Normal 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
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
94
src/main.cpp
94
src/main.cpp
|
|
@ -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;
|
||||
|
|
|
|||
55
src/shaderchains/downsample.cpp
Normal file
55
src/shaderchains/downsample.cpp
Normal 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);
|
||||
}
|
||||
|
|
@ -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
31
src/utils/globals.cpp
Normal 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();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue