#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 #include 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 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), ¶meters); command.dispatch((parameters.output_size[0] + 7) / 8, (parameters.output_size[1] + 7) / 8, 1); ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader); ResetBindings(); } } // namespace Libs::Graphics