graphics: add experimental Pro mode and linear PSSR

Co-authored-by: Joshua Jayasingh <joshuajayasingh08@gmail.com>
This commit is contained in:
nmzikandJoshua Jayasingh committed 2026-10-29 07:55:50 +01:00
1 parent 5e837a9bea
commit 651ef73a03
30 files changed
+1340 -278

No files matched your search

+9
View File
@@ -150,15 +150,21 @@ private:
friend class CommandScheduler;
};
namespace Pssr {
struct Request;
}
class RenderExecutor {
public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
~RenderExecutor();
KYTY_CLASS_NO_COPY(RenderExecutor);
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode, bool async_compute);
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
uint32_t mode, bool async_compute);
void ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(std::span<PreparedBindings* const> stages);
@@ -219,6 +225,9 @@ private:
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
vk::DescriptorSetLayout m_pssr_descriptor_layout = nullptr;
vk::PipelineLayout m_pssr_pipeline_layout = nullptr;
vk::Pipeline m_pssr_pipeline = nullptr;
friend class CommandProcessor;
friend struct RenderExecutorTestAccess;
@@ -0,0 +1,184 @@
#include "common/assert.h"
#include "common/threads.h"
#include "gpu_blit_shaders/gpu_pssr_linear_spv.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/shaderBindings.h"
#include <algorithm>
#include <array>
namespace Libs::Graphics {
namespace {
struct PssrParameters {
uint32_t input_size[2];
uint32_t output_size[2];
uint32_t output_channels[4];
};
bool IsInputFormat(Prospero::BufferFormat format) {
return format == Prospero::BufferFormat::k11_11_10Float ||
format == Prospero::BufferFormat::k10_11_11Float ||
format == Prospero::BufferFormat::k16_16_16_16Float;
}
} // namespace
RenderExecutor::~RenderExecutor() {
const auto device = m_context.GetGraphics().device;
device.destroyPipeline(m_pssr_pipeline, nullptr);
device.destroyPipelineLayout(m_pssr_pipeline_layout, nullptr);
device.destroyDescriptorSetLayout(m_pssr_descriptor_layout, nullptr);
}
void RenderExecutor::ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request) {
EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
Common::LockGuard lock(m_context.GetMutex());
ShaderTextureResource input_descriptor {}, output_descriptor {};
std::copy_n(request.input, 8, input_descriptor.fields);
std::copy_n(request.output, 8, output_descriptor.fields);
const auto is_2d = [](const ShaderTextureResource& descriptor) {
return !descriptor.IsNull() &&
(descriptor.Type() == Prospero::ImageType::kColor2D ||
descriptor.Type() == Prospero::ImageType::kColor2DArray) &&
descriptor.TileMode() != Prospero::TileMode::kDepth;
};
EXIT_IF(!is_2d(input_descriptor) || !is_2d(output_descriptor) ||
!IsInputFormat(input_descriptor.Format()) ||
output_descriptor.Format() != Prospero::BufferFormat::k11_11_10Float ||
output_descriptor.TileMode() != Prospero::TileMode::kStandard256B ||
output_descriptor.BaseLevel() != 0 || output_descriptor.LastLevel() != 0 ||
output_descriptor.MaxMip() != 0 || output_descriptor.BaseArray5() != 0 ||
output_descriptor.Depth() != 0 || request.width == 0 || request.height == 0);
const auto input_width = std::max(1u, (input_descriptor.Width5() + 1u) >>
input_descriptor.BaseLevel());
const auto input_height = std::max(1u, (input_descriptor.Height5() + 1u) >>
input_descriptor.BaseLevel());
EXIT_IF(request.width > input_width || request.height > input_height);
PssrParameters parameters {{request.width, request.height},
{output_descriptor.Width5() + 1u, output_descriptor.Height5() + 1u},
{4, 4, 4, 4}};
for (uint32_t component = 0; component < 3; ++component) {
for (uint32_t source = 0; source < 4; ++source) {
if (GetDstSel(output_descriptor.DstSelXYZW(), source) == 4u + component) {
parameters.output_channels[component] = source;
break;
}
}
}
const auto resolve = [&](const uint32_t* words, bool output) {
ShaderRecompiler::IR::ImageResource resource {};
resource.resource_class = output ? ShaderRecompiler::IR::ImageResourceClass::Storage
: ShaderRecompiler::IR::ImageResourceClass::Sampled;
resource.numeric_class = Prospero::TextureNumericClass::Float;
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
resource.read = !output;
resource.written = output;
ShaderRecompiler::IR::DescriptorValue value {};
std::copy_n(words, 8, value.dwords.begin());
value.dword_count = 8;
auto binding = ResolveTexture(resource, value);
binding.desc.view_info.level_count = 1;
binding.desc.view_info.min_lod = 0;
return binding;
};
auto& cache = m_context.GetTextureCache();
auto input = resolve(request.input, false);
BindImage(input.image_id, false);
auto output = resolve(request.output, true);
BindImage(output.image_id, true);
EXIT_IF(input.desc.info.data.address < output.desc.info.data.End() &&
output.desc.info.data.address < input.desc.info.data.End());
const auto acquire = [&](TextureBinding& binding, const uint32_t* words, bool storage) {
auto& previous = cache.GetImage(binding.image_id);
if (!previous.registered || previous.binding.needs_rebind) {
previous.binding = {};
binding = resolve(words, storage);
BindImage(binding.image_id, storage);
}
binding.image_view = cache.FindTexture(binding.image_id, binding.desc);
};
acquire(input, request.input, false);
acquire(output, request.output, true);
auto& source = cache.GetImage(input.image_id);
auto& destination = cache.GetImage(output.image_id);
const auto& source_view = input.desc.view_info;
const auto& destination_view = output.desc.view_info;
EXIT_IF(source.info.IsDepth() || destination.info.IsDepth() ||
source.backing.samples != 1 || destination.backing.samples != 1 ||
(input.image_id == output.image_id &&
source_view.base_level == destination_view.base_level &&
source_view.base_layer == destination_view.base_layer));
auto& graphics = m_context.GetGraphics();
if (m_pssr_pipeline == nullptr) {
const std::array bindings {
vk::DescriptorSetLayoutBinding {0, vk::DescriptorType::eSampledImage, 1,
vk::ShaderStageFlagBits::eCompute},
vk::DescriptorSetLayoutBinding {1, vk::DescriptorType::eStorageImage, 1,
vk::ShaderStageFlagBits::eCompute}};
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = bindings.size();
descriptor_info.pBindings = bindings.data();
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(
&descriptor_info, nullptr, &m_pssr_descriptor_layout),
"create upscaling descriptor layout");
const vk::PushConstantRange push {vk::ShaderStageFlagBits::eCompute, 0,
sizeof(PssrParameters)};
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_pssr_descriptor_layout;
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = &push;
RequireVulkanSuccess(graphics.device.createPipelineLayout(
&layout_info, nullptr, &m_pssr_pipeline_layout),
"create upscaling pipeline layout");
const auto shader = CompileSPV(GPU_PSSR_LINEAR_SPV, graphics.device);
vk::ComputePipelineCreateInfo create {};
create.stage.stage = vk::ShaderStageFlagBits::eCompute;
create.stage.module = shader;
create.stage.pName = "main";
create.layout = m_pssr_pipeline_layout;
const auto result = graphics.device.createComputePipelines(
nullptr, 1, &create, nullptr, &m_pssr_pipeline);
graphics.device.destroyShaderModule(shader, nullptr);
RequireVulkanSuccess(result, "create upscaling pipeline");
}
buffer.EndRendering();
const auto command = buffer.Handle();
source.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead,
ImageSubresourceRange {source_view.base_level, 1, source_view.base_layer, 1},
command);
destination.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite,
ImageSubresourceRange {destination_view.base_level, 1,
destination_view.base_layer, 1}, command);
const std::array images {
vk::DescriptorImageInfo {nullptr, input.image_view, vk::ImageLayout::eGeneral},
vk::DescriptorImageInfo {nullptr, output.image_view, vk::ImageLayout::eGeneral}};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t i = 0; i < writes.size(); ++i) {
writes[i].dstBinding = i;
writes[i].descriptorCount = 1;
writes[i].descriptorType = i == 0 ? vk::DescriptorType::eSampledImage
: vk::DescriptorType::eStorageImage;
writes[i].pImageInfo = &images[i];
}
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pssr_pipeline_layout,
0, writes.size(), writes.data());
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_pssr_pipeline);
command.pushConstants(m_pssr_pipeline_layout, vk::ShaderStageFlagBits::eCompute,
0, sizeof(parameters), &parameters);
command.dispatch((parameters.output_size[0] + 7) / 8,
(parameters.output_size[1] + 7) / 8, 1);
ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader);
ResetBindings();
}
} // namespace Libs::Graphics