mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
renderer/vulkan: Add FSR upscaling
This commit is contained in:
Vendored
+2656
File diff suppressed because it is too large
Load Diff
Vendored
+1199
File diff suppressed because it is too large
Load Diff
@@ -417,7 +417,7 @@ static bool ImGui_ImplSdlVulkan_CreatePipeline(ImGui_VulkanState &state) {
|
||||
.pColorBlendState = &gui_pipeline_blend_info,
|
||||
.pDynamicState = &gui_pipeline_dynamic_info,
|
||||
.layout = state.PipelineLayout,
|
||||
.renderPass = vk_state.screen_renderer.render_pass,
|
||||
.renderPass = vk_state.screen_renderer.default_render_pass,
|
||||
};
|
||||
gui_pipeline_info.setStages(shader_stage_infos);
|
||||
|
||||
|
||||
@@ -540,10 +540,11 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
|
||||
// Screen Filter
|
||||
ImGui::Spacing();
|
||||
int curr_filter = 0;
|
||||
const std::array<const char *, 3> possible_filters = {
|
||||
const std::array<const char *, 4> possible_filters = {
|
||||
"Nearest",
|
||||
"Bilinear",
|
||||
"FXAA"
|
||||
"FXAA",
|
||||
"FSR"
|
||||
};
|
||||
const int filters_available = emuenv.renderer->get_supported_filters();
|
||||
std::vector<const char *> filters;
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace renderer {
|
||||
enum struct Filter : int {
|
||||
NEAREST = 1 << 0,
|
||||
BILINEAR = 1 << 1,
|
||||
FXAA = 1 << 2
|
||||
FXAA = 1 << 2,
|
||||
FSR = 1 << 3
|
||||
};
|
||||
|
||||
struct State {
|
||||
|
||||
@@ -32,8 +32,13 @@ protected:
|
||||
public:
|
||||
ScreenFilter(ScreenRenderer &screen_renderer);
|
||||
virtual void init() = 0;
|
||||
virtual void on_resize(){};
|
||||
virtual void render(bool is_pre_renderpass, vk::ImageView src_img, vk::ImageLayout src_layout, const Viewport &viewport) = 0;
|
||||
virtual std::string_view get_name() = 0;
|
||||
// do we need the render pass not to clear the swapchain content ?
|
||||
virtual bool need_post_processing_render_pass() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class SinglePassScreenFilter : public ScreenFilter {
|
||||
@@ -109,4 +114,40 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class FSRScreenFilter : public ScreenFilter {
|
||||
private:
|
||||
// dst of the easu shader, src of the rcas shader
|
||||
std::vector<vkutil::Image> intermediate_images;
|
||||
|
||||
vk::ShaderModule easu_shader;
|
||||
vk::ShaderModule rcas_shader;
|
||||
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
vk::DescriptorPool descriptor_pool;
|
||||
std::vector<vk::DescriptorSet> descriptor_sets;
|
||||
vk::PipelineLayout pipeline_layout_easu;
|
||||
vk::PipelineLayout pipeline_layout_rcas;
|
||||
vk::Pipeline pipeline_easu;
|
||||
vk::Pipeline pipeline_rcas;
|
||||
|
||||
vk::Extent2D output_offset;
|
||||
vk::Extent2D output_size;
|
||||
|
||||
vk::Sampler sampler;
|
||||
|
||||
public:
|
||||
FSRScreenFilter(ScreenRenderer &screen_renderer)
|
||||
: ScreenFilter(screen_renderer) {}
|
||||
~FSRScreenFilter();
|
||||
void init() override;
|
||||
void on_resize() override;
|
||||
void render(bool is_pre_renderpass, vk::ImageView src_img, vk::ImageLayout src_layout, const Viewport &viewport) override;
|
||||
std::string_view get_name() override {
|
||||
return "FSR";
|
||||
}
|
||||
bool need_post_processing_render_pass() override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -49,7 +49,10 @@ public:
|
||||
std::vector<vk::CommandBuffer> command_buffers;
|
||||
std::vector<vk::Fence> fences;
|
||||
|
||||
vk::RenderPass render_pass;
|
||||
// renderpass used when no effect is done previously (clear the swapchain content)
|
||||
vk::RenderPass default_render_pass;
|
||||
// renderpass used after a post-processing pass clearing the swapchain, compatible with the default render pass
|
||||
vk::RenderPass post_filter_render_pass;
|
||||
|
||||
std::unique_ptr<ScreenFilter> filter;
|
||||
std::vector<vk::Semaphore> image_acquired_semaphores;
|
||||
|
||||
@@ -78,6 +78,8 @@ struct VKState : public renderer::State {
|
||||
vkutil::Image default_image;
|
||||
vkutil::Buffer default_buffer;
|
||||
|
||||
bool support_fsr = false;
|
||||
|
||||
VKState(int gpu_idx);
|
||||
|
||||
bool init(const char *base_path, const bool hashless_texture_cache) override;
|
||||
|
||||
@@ -341,6 +341,7 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
.fillModeNonSolid = physical_device_features.fillModeNonSolid,
|
||||
.wideLines = physical_device_features.wideLines,
|
||||
.samplerAnisotropy = physical_device_features.samplerAnisotropy,
|
||||
.shaderInt16 = physical_device_features.shaderInt16
|
||||
};
|
||||
|
||||
// look for optional extensions
|
||||
@@ -350,7 +351,7 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
bool support_buffer_device_address = false;
|
||||
bool support_standard_layout = false;
|
||||
bool support_external_memory = false;
|
||||
const std::map<std::string, bool *> optional_extensions = {
|
||||
const std::map<std::string_view, bool *> optional_extensions = {
|
||||
{ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, &temp_bool },
|
||||
// can be used by vma to improve performance
|
||||
{ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, &support_dedicated_allocations },
|
||||
@@ -366,6 +367,8 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
{ VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, &support_buffer_device_address },
|
||||
// needed for uniform uvec2 arrays not to take twice the size
|
||||
{ VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, &support_standard_layout },
|
||||
// needed for FSR
|
||||
{ VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, &support_fsr },
|
||||
#ifdef __APPLE__
|
||||
// Needed to create the MoltenVK device
|
||||
{ VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, &temp_bool },
|
||||
@@ -377,7 +380,7 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
if (it != optional_extensions.end()) {
|
||||
// this extension is available on the GPU
|
||||
*it->second = true;
|
||||
device_extensions.push_back(it->first.c_str());
|
||||
device_extensions.push_back(it->first.data());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,14 +438,21 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
// We use subpass input to get something similar to direct fragcolor access (there is no difference for the shader)
|
||||
features.direct_fragcolor = true;
|
||||
|
||||
vk::StructureChain<vk::DeviceCreateInfo, vk::PhysicalDeviceBufferDeviceAddressFeatures, vk::PhysicalDeviceUniformBufferStandardLayoutFeatures> device_info{
|
||||
vk::DeviceCreateInfo{
|
||||
.pEnabledFeatures = &enabled_features },
|
||||
vk::PhysicalDeviceBufferDeviceAddressFeatures{
|
||||
.bufferDeviceAddress = VK_TRUE },
|
||||
vk::PhysicalDeviceUniformBufferStandardLayoutFeatures{
|
||||
.uniformBufferStandardLayout = VK_TRUE }
|
||||
};
|
||||
vk::StructureChain<vk::DeviceCreateInfo,
|
||||
vk::PhysicalDeviceBufferDeviceAddressFeatures,
|
||||
vk::PhysicalDeviceUniformBufferStandardLayoutFeatures,
|
||||
vk::PhysicalDeviceShaderFloat16Int8Features>
|
||||
device_info{
|
||||
vk::DeviceCreateInfo{
|
||||
.pEnabledFeatures = &enabled_features },
|
||||
vk::PhysicalDeviceBufferDeviceAddressFeatures{
|
||||
.bufferDeviceAddress = VK_TRUE },
|
||||
vk::PhysicalDeviceUniformBufferStandardLayoutFeatures{
|
||||
.uniformBufferStandardLayout = VK_TRUE },
|
||||
vk::PhysicalDeviceShaderFloat16Int8Features{
|
||||
// FSR uses float16
|
||||
.shaderFloat16 = VK_TRUE }
|
||||
};
|
||||
device_info.get().setQueueCreateInfos(queue_infos);
|
||||
device_info.get().setPEnabledExtensionNames(device_extensions);
|
||||
|
||||
@@ -451,6 +461,9 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
device_info.unlink<vk::PhysicalDeviceUniformBufferStandardLayoutFeatures>();
|
||||
}
|
||||
|
||||
if (!support_fsr)
|
||||
device_info.unlink<vk::PhysicalDeviceShaderFloat16Int8Features>();
|
||||
|
||||
try {
|
||||
device = physical_device.createDevice(device_info.get());
|
||||
} catch (vk::NotPermittedKHRError &) {
|
||||
@@ -545,6 +558,8 @@ bool VKState::create(SDL_Window *window, std::unique_ptr<renderer::State> &state
|
||||
if (!screen_renderer.setup(base_path))
|
||||
return false;
|
||||
|
||||
support_fsr &= static_cast<bool>(screen_renderer.surface_capabilities.supportedUsageFlags & vk::ImageUsageFlagBits::eStorage);
|
||||
|
||||
#ifdef __linux__
|
||||
// According to my tests (Macdu), mprotect on buffers (mapped with external memory host) only works with Nvidia drivers
|
||||
surface_cache.can_mprotect_mapped_memory = std::string_view(physical_device_properties.deviceName).find("NVIDIA") != std::string_view::npos;
|
||||
@@ -648,11 +663,18 @@ void VKState::swap_window(SDL_Window *window) {
|
||||
|
||||
int VKState::get_supported_filters() {
|
||||
int filters = static_cast<int>(Filter::NEAREST) | static_cast<int>(Filter::BILINEAR) | static_cast<int>(Filter::FXAA);
|
||||
if (support_fsr)
|
||||
filters |= static_cast<int>(Filter::FSR);
|
||||
return filters;
|
||||
}
|
||||
|
||||
void VKState::set_screen_filter(const std::string_view &filter) {
|
||||
screen_renderer.set_filter(filter);
|
||||
if (filter == "FSR" && !support_fsr) {
|
||||
LOG_WARN("Trying to enable FSR but the GPU does not support it");
|
||||
screen_renderer.set_filter("");
|
||||
} else {
|
||||
screen_renderer.set_filter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
bool VKState::map_memory(MemState &mem, Ptr<void> address, uint32_t size) {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "renderer/vulkan/state.h"
|
||||
|
||||
#include <util/align.h>
|
||||
|
||||
namespace renderer::vulkan {
|
||||
|
||||
ScreenFilter::ScreenFilter(ScreenRenderer &screen_renderer)
|
||||
@@ -48,6 +50,8 @@ SinglePassScreenFilter::~SinglePassScreenFilter() {
|
||||
device.freeDescriptorSets(descriptor_pool, descriptor_sets);
|
||||
device.destroy(descriptor_pool);
|
||||
device.destroy(descriptor_set_layout);
|
||||
device.destroy(fragment_shader);
|
||||
device.destroy(vertex_shader);
|
||||
|
||||
device.destroy(sampler);
|
||||
}
|
||||
@@ -184,7 +188,7 @@ void SinglePassScreenFilter::create_graphics_pipeline() {
|
||||
.pColorBlendState = &color_blending,
|
||||
.pDynamicState = &dynamic_info,
|
||||
.layout = pipeline_layout,
|
||||
.renderPass = screen.render_pass,
|
||||
.renderPass = screen.default_render_pass,
|
||||
.subpass = 0
|
||||
};
|
||||
pipeline_info.setStages(shader_stages);
|
||||
@@ -333,7 +337,6 @@ std::string_view FXAAScreenFilter::get_fragment_name() {
|
||||
|
||||
vk::Sampler FXAAScreenFilter::create_sampler() {
|
||||
vk::SamplerCreateInfo sampler_info{
|
||||
// I'm still unsure whether the FXAA sampler should be nearest or linear...
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
|
||||
@@ -343,4 +346,285 @@ vk::Sampler FXAAScreenFilter::create_sampler() {
|
||||
return screen.state.device.createSampler(sampler_info);
|
||||
}
|
||||
|
||||
struct EasuConstant {
|
||||
Viewport viewport;
|
||||
vk::Extent2D output_size;
|
||||
};
|
||||
|
||||
struct RcasConstant {
|
||||
vk::Extent2D offset;
|
||||
float sharpening;
|
||||
};
|
||||
|
||||
FSRScreenFilter::~FSRScreenFilter() {
|
||||
vk::Device device = screen.state.device;
|
||||
device.waitIdle();
|
||||
|
||||
device.destroy(sampler);
|
||||
|
||||
device.destroy(pipeline_easu);
|
||||
device.destroy(pipeline_rcas);
|
||||
device.destroy(pipeline_layout_easu);
|
||||
device.destroy(pipeline_layout_rcas);
|
||||
device.freeDescriptorSets(descriptor_pool, descriptor_sets);
|
||||
device.destroy(descriptor_pool);
|
||||
device.destroy(descriptor_set_layout);
|
||||
|
||||
device.destroy(rcas_shader);
|
||||
device.destroy(easu_shader);
|
||||
}
|
||||
|
||||
void FSRScreenFilter::init() {
|
||||
vk::Device device = screen.state.device;
|
||||
|
||||
// create sampler
|
||||
vk::SamplerCreateInfo sampler_info{
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
|
||||
};
|
||||
sampler = device.createSampler(sampler_info);
|
||||
|
||||
const auto builtin_shaders_path = std::string(screen.state.base_path) + "shaders-builtin/vulkan/";
|
||||
easu_shader = vkutil::load_shader(screen.state.device, builtin_shaders_path + "fsr_filter_easu.comp.spv");
|
||||
rcas_shader = vkutil::load_shader(screen.state.device, builtin_shaders_path + "fsr_filter_rcas.comp.spv");
|
||||
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> layout_bindings = {
|
||||
// src img
|
||||
vk::DescriptorSetLayoutBinding{
|
||||
.binding = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute },
|
||||
// dst img
|
||||
vk::DescriptorSetLayoutBinding{
|
||||
.binding = 2,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute },
|
||||
// src img sampler
|
||||
vk::DescriptorSetLayoutBinding{
|
||||
.binding = 3,
|
||||
.descriptorType = vk::DescriptorType::eSampler,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute,
|
||||
.pImmutableSamplers = &sampler },
|
||||
};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo layout_create_info{};
|
||||
layout_create_info.setBindings(layout_bindings);
|
||||
descriptor_set_layout = device.createDescriptorSetLayout(layout_create_info);
|
||||
|
||||
std::array<vk::DescriptorPoolSize, 3> pool_sizes{
|
||||
vk::DescriptorPoolSize{
|
||||
.type = vk::DescriptorType::eSampledImage,
|
||||
.descriptorCount = screen.swapchain_size * 2 },
|
||||
vk::DescriptorPoolSize{
|
||||
.type = vk::DescriptorType::eStorageImage,
|
||||
.descriptorCount = screen.swapchain_size * 2 },
|
||||
vk::DescriptorPoolSize{
|
||||
.type = vk::DescriptorType::eSampler,
|
||||
.descriptorCount = screen.swapchain_size * 2 },
|
||||
};
|
||||
vk::DescriptorPoolCreateInfo pool_info{
|
||||
.maxSets = screen.swapchain_size * 2,
|
||||
};
|
||||
pool_info.setPoolSizes(pool_sizes);
|
||||
descriptor_pool = device.createDescriptorPool(pool_info);
|
||||
|
||||
vk::DescriptorSetAllocateInfo descr_set_info{
|
||||
.descriptorPool = descriptor_pool,
|
||||
};
|
||||
std::vector<vk::DescriptorSetLayout> descr_set_layouts(screen.swapchain_size * 2, descriptor_set_layout);
|
||||
descr_set_info.setSetLayouts(descr_set_layouts);
|
||||
descriptor_sets = device.allocateDescriptorSets(descr_set_info);
|
||||
|
||||
vk::PipelineLayoutCreateInfo layout_info{};
|
||||
layout_info.setSetLayouts(descriptor_set_layout);
|
||||
vk::PushConstantRange push_constant{
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute,
|
||||
.offset = 0,
|
||||
.size = sizeof(EasuConstant),
|
||||
};
|
||||
layout_info.setPushConstantRanges(push_constant);
|
||||
pipeline_layout_easu = device.createPipelineLayout(layout_info);
|
||||
|
||||
push_constant.size = sizeof(RcasConstant);
|
||||
pipeline_layout_rcas = device.createPipelineLayout(layout_info);
|
||||
|
||||
// create easu and rcas pipelines
|
||||
vk::ComputePipelineCreateInfo compute_info{
|
||||
.stage = {
|
||||
.stage = vk::ShaderStageFlagBits::eCompute,
|
||||
.module = easu_shader,
|
||||
.pName = "main" },
|
||||
.layout = pipeline_layout_easu
|
||||
};
|
||||
auto result = device.createComputePipeline(nullptr, compute_info);
|
||||
if (result.result != vk::Result::eSuccess)
|
||||
LOG_ERROR("Failed to create compute pipeline");
|
||||
pipeline_easu = result.value;
|
||||
|
||||
compute_info.stage.module = rcas_shader;
|
||||
compute_info.layout = pipeline_layout_rcas;
|
||||
result = device.createComputePipeline(nullptr, compute_info);
|
||||
if (result.result != vk::Result::eSuccess)
|
||||
LOG_ERROR("Failed to create compute pipeline");
|
||||
pipeline_rcas = result.value;
|
||||
|
||||
// create intermediate images
|
||||
intermediate_images.resize(screen.swapchain_size);
|
||||
for (auto &img : intermediate_images) {
|
||||
img.allocator = screen.state.allocator;
|
||||
img.format = vk::Format::eR8G8B8A8Unorm;
|
||||
}
|
||||
on_resize();
|
||||
}
|
||||
|
||||
void FSRScreenFilter::on_resize() {
|
||||
// compute the extent
|
||||
const float window_aspect = static_cast<float>(screen.extent.width) / screen.extent.height;
|
||||
const float vita_aspect = static_cast<float>(DEFAULT_RES_WIDTH) / DEFAULT_RES_HEIGHT;
|
||||
if (window_aspect > vita_aspect) {
|
||||
// Window is wide. Pin top and bottom.
|
||||
output_size.width = static_cast<uint32_t>(std::round(screen.extent.height * vita_aspect));
|
||||
output_size.height = screen.extent.height;
|
||||
output_offset.width = static_cast<uint32_t>(std::round((screen.extent.width - output_size.width) / 2.0f));
|
||||
output_offset.height = 0.0;
|
||||
} else {
|
||||
// Window is tall. Pin left and right.
|
||||
output_size.width = screen.extent.width;
|
||||
output_size.height = static_cast<uint32_t>(std::round(screen.extent.width / vita_aspect));
|
||||
output_offset.width = 0.0f;
|
||||
output_offset.height = static_cast<uint32_t>(std::round((screen.extent.height - output_size.height) / 2.0f));
|
||||
}
|
||||
|
||||
// recreate the intermediate images
|
||||
for (auto &img : intermediate_images) {
|
||||
img.destroy();
|
||||
img.width = output_size.width;
|
||||
img.height = output_size.height;
|
||||
img.init_image(vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled);
|
||||
}
|
||||
|
||||
// update the descriptor sets (except the first sampler image as it is not fixed)
|
||||
std::vector<vk::DescriptorImageInfo> descr_images(screen.swapchain_size * 3);
|
||||
std::vector<vk::WriteDescriptorSet> write_descr(screen.swapchain_size * 3);
|
||||
for (int i = 0; i < write_descr.size(); i++) {
|
||||
descr_images[i].imageView = intermediate_images[i / 3].view;
|
||||
write_descr[i].setImageInfo(descr_images[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < screen.swapchain_size; i++) {
|
||||
// easu dst
|
||||
descr_images[i * 3].imageLayout = vk::ImageLayout::eGeneral;
|
||||
write_descr[i * 3]
|
||||
.setDstSet(descriptor_sets[i * 2])
|
||||
.setDstBinding(2)
|
||||
.setDescriptorType(vk::DescriptorType::eStorageImage);
|
||||
// rcas src
|
||||
descr_images[i * 3 + 1].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
write_descr[i * 3 + 1]
|
||||
.setDstSet(descriptor_sets[i * 2 + 1])
|
||||
.setDstBinding(1)
|
||||
.setDescriptorType(vk::DescriptorType::eSampledImage);
|
||||
// rcas dst
|
||||
descr_images[i * 3 + 2]
|
||||
.setImageView(screen.swapchain_views[i])
|
||||
.setImageLayout(vk::ImageLayout::eGeneral);
|
||||
write_descr[i * 3 + 2]
|
||||
.setDstSet(descriptor_sets[i * 2 + 1])
|
||||
.setDstBinding(2)
|
||||
.setDescriptorType(vk::DescriptorType::eStorageImage);
|
||||
}
|
||||
screen.state.device.updateDescriptorSets(write_descr, {});
|
||||
}
|
||||
|
||||
void FSRScreenFilter::render(bool is_pre_renderpass, vk::ImageView src_img, vk::ImageLayout src_layout, const Viewport &viewport) {
|
||||
if (!is_pre_renderpass)
|
||||
// we are using compute shaders
|
||||
return;
|
||||
|
||||
// update descriptor set, (only the easu src)
|
||||
vk::DescriptorImageInfo descr_image_info{
|
||||
.imageView = src_img,
|
||||
.imageLayout = src_layout,
|
||||
};
|
||||
vk::WriteDescriptorSet write_descr{
|
||||
.dstSet = descriptor_sets[2 * screen.swapchain_image_idx],
|
||||
.dstBinding = 1,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
};
|
||||
write_descr.setImageInfo(descr_image_info);
|
||||
screen.state.device.updateDescriptorSets(write_descr, {});
|
||||
|
||||
vk::CommandBuffer cmd_buffer = screen.current_cmd_buffer;
|
||||
// first, make a barrier to make sure we can write to the intermediate texture
|
||||
// we don't care about the previous content
|
||||
intermediate_images[screen.swapchain_image_idx].transition_to_discard(cmd_buffer, vkutil::ImageLayout::StorageImage);
|
||||
|
||||
// upscaling pass
|
||||
cmd_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline_easu);
|
||||
cmd_buffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipeline_layout_easu, 0, descriptor_sets[2 * screen.swapchain_image_idx], {});
|
||||
// push the viewport to the shader
|
||||
EasuConstant easu_constant{
|
||||
.viewport = viewport,
|
||||
.output_size = output_size
|
||||
};
|
||||
cmd_buffer.pushConstants(pipeline_layout_easu, vk::ShaderStageFlagBits::eCompute, 0, sizeof(EasuConstant), &easu_constant);
|
||||
const int dispatch_x = (output_size.width + 15) / 16;
|
||||
const int dispatch_y = (output_size.height + 15) / 16;
|
||||
cmd_buffer.dispatch(dispatch_x, dispatch_y, 1);
|
||||
|
||||
// meanwhile, we need to clear the swapchain surface
|
||||
vk::ImageMemoryBarrier barrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.oldLayout = vk::ImageLayout::eUndefined,
|
||||
.newLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = screen.swapchain_images[screen.swapchain_image_idx],
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
cmd_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::DependencyFlags(), {}, {}, barrier);
|
||||
|
||||
vk::ClearColorValue clear_color{ std::array<float, 4>({ 0.0f, 0.0f, 0.0f, 0.0f }) };
|
||||
cmd_buffer.clearColorImage(screen.swapchain_images[screen.swapchain_image_idx], vk::ImageLayout::eTransferDstOptimal, clear_color, vkutil::color_subresource_range);
|
||||
|
||||
// then transition the read texture to sampled // wait for the previous compute shader to be done
|
||||
intermediate_images[screen.swapchain_image_idx].transition_to(cmd_buffer, vkutil::ImageLayout::SampledImage);
|
||||
|
||||
// also transition the swapchain image to general
|
||||
barrier = {
|
||||
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderWrite,
|
||||
.oldLayout = vk::ImageLayout::eTransferDstOptimal,
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = screen.swapchain_images[screen.swapchain_image_idx],
|
||||
.subresourceRange = vkutil::color_subresource_range
|
||||
};
|
||||
cmd_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::DependencyFlags(), {}, {}, barrier);
|
||||
|
||||
// sharpening pass
|
||||
cmd_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline_rcas);
|
||||
cmd_buffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipeline_layout_rcas, 0, descriptor_sets[2 * screen.swapchain_image_idx + 1], {});
|
||||
RcasConstant rcas_constant{
|
||||
.offset = output_offset,
|
||||
// some default value for sharpening
|
||||
.sharpening = 0.2f
|
||||
};
|
||||
cmd_buffer.pushConstants(pipeline_layout_rcas, vk::ShaderStageFlagBits::eCompute, 0, sizeof(RcasConstant), &rcas_constant);
|
||||
cmd_buffer.dispatch(dispatch_x, dispatch_y, 1);
|
||||
|
||||
// the barrier for the render pass will be handled by the renderpass external dependencies
|
||||
}
|
||||
|
||||
} // namespace renderer::vulkan
|
||||
|
||||
@@ -118,6 +118,11 @@ void ScreenRenderer::create_swapchain() {
|
||||
|
||||
// Create Swapchain
|
||||
{
|
||||
vk::ImageUsageFlags surface_usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
if (surface_capabilities.supportedUsageFlags & vk::ImageUsageFlagBits::eStorage)
|
||||
// needed for FSR
|
||||
surface_usage |= vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eStorage;
|
||||
|
||||
vk::SwapchainCreateInfoKHR swapchain_info{
|
||||
.surface = surface,
|
||||
.minImageCount = swapchain_size,
|
||||
@@ -125,7 +130,7 @@ void ScreenRenderer::create_swapchain() {
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
.imageExtent = extent,
|
||||
.imageArrayLayers = 1,
|
||||
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment,
|
||||
.imageUsage = surface_usage,
|
||||
.imageSharingMode = vk::SharingMode::eExclusive,
|
||||
.preTransform = surface_capabilities.currentTransform,
|
||||
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque,
|
||||
@@ -157,7 +162,7 @@ void ScreenRenderer::create_swapchain() {
|
||||
swapchain_framebuffers.resize(swapchain_size);
|
||||
for (uint32_t i = 0; i < swapchain_size; i++) {
|
||||
vk::FramebufferCreateInfo fb_info{
|
||||
.renderPass = render_pass,
|
||||
.renderPass = default_render_pass,
|
||||
.width = extent.width,
|
||||
.height = extent.height,
|
||||
.layers = 1
|
||||
@@ -166,6 +171,9 @@ void ScreenRenderer::create_swapchain() {
|
||||
|
||||
swapchain_framebuffers[i] = state.device.createFramebuffer(fb_info);
|
||||
}
|
||||
|
||||
if (filter)
|
||||
filter->on_resize();
|
||||
}
|
||||
|
||||
void ScreenRenderer::destroy_swapchain() {
|
||||
@@ -188,7 +196,8 @@ void ScreenRenderer::cleanup() {
|
||||
for (vk::Framebuffer fb : swapchain_framebuffers)
|
||||
state.device.destroy(fb);
|
||||
|
||||
state.device.destroy(render_pass);
|
||||
state.device.destroy(default_render_pass);
|
||||
state.device.destroy(post_filter_render_pass);
|
||||
|
||||
for (vk::ImageView view : swapchain_views)
|
||||
state.device.destroy(view);
|
||||
@@ -260,7 +269,7 @@ bool ScreenRenderer::acquire_swapchain_image(bool start_render_pass) {
|
||||
|
||||
if (start_render_pass) {
|
||||
vk::RenderPassBeginInfo pass_info{
|
||||
.renderPass = render_pass,
|
||||
.renderPass = default_render_pass,
|
||||
.framebuffer = swapchain_framebuffers[swapchain_image_idx],
|
||||
.renderArea = {
|
||||
.offset = { 0, 0 },
|
||||
@@ -283,6 +292,7 @@ void ScreenRenderer::render(vk::ImageView image_view, vk::ImageLayout layout, co
|
||||
// we need to apply the screen filter at the right moment (before or after we start the render pass depending on it)
|
||||
filter->render(true, image_view, layout, viewport);
|
||||
|
||||
const auto render_pass = filter->need_post_processing_render_pass() ? post_filter_render_pass : default_render_pass;
|
||||
vk::RenderPassBeginInfo pass_info{
|
||||
.renderPass = render_pass,
|
||||
.framebuffer = swapchain_framebuffers[swapchain_image_idx],
|
||||
@@ -311,7 +321,7 @@ void ScreenRenderer::swap_window() {
|
||||
vk::SubmitInfo submit_info{};
|
||||
std::array<vk::Semaphore, 1> wait_semaphores = { image_acquired_semaphores[current_frame] };
|
||||
std::array<vk::PipelineStageFlags, 1> dst_masks
|
||||
= { vk::PipelineStageFlagBits::eColorAttachmentOutput };
|
||||
= { vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eTransfer };
|
||||
submit_info.setWaitSemaphores(wait_semaphores);
|
||||
submit_info.setWaitDstStageMask(dst_masks);
|
||||
submit_info.setSignalSemaphores(image_ready_semaphores[current_frame]);
|
||||
@@ -356,7 +366,9 @@ void ScreenRenderer::set_filter(const std::string_view &filter) {
|
||||
return;
|
||||
|
||||
this->filter.reset();
|
||||
if (filter == "FXAA")
|
||||
if (filter == "FSR")
|
||||
this->filter = std::make_unique<FSRScreenFilter>(*this);
|
||||
else if (filter == "FXAA")
|
||||
this->filter = std::make_unique<FXAAScreenFilter>(*this);
|
||||
else if (filter == "Nearest")
|
||||
this->filter = std::make_unique<NearestScreenFilter>(*this);
|
||||
@@ -412,10 +424,11 @@ void ScreenRenderer::create_render_pass() {
|
||||
vk::SubpassDependency dependency{
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eComputeShader,
|
||||
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
.srcAccessMask = vk::AccessFlags(),
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
// don't forget blending
|
||||
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite
|
||||
};
|
||||
|
||||
vk::RenderPassCreateInfo pass_info{};
|
||||
@@ -423,7 +436,13 @@ void ScreenRenderer::create_render_pass() {
|
||||
pass_info.setSubpasses(subpass);
|
||||
pass_info.setDependencies(dependency);
|
||||
|
||||
render_pass = state.device.createRenderPass(pass_info);
|
||||
default_render_pass = state.device.createRenderPass(pass_info);
|
||||
|
||||
// renderpass after post processing filter
|
||||
color_attachment
|
||||
.setLoadOp(vk::AttachmentLoadOp::eLoad)
|
||||
.setInitialLayout(vk::ImageLayout::eGeneral);
|
||||
post_filter_render_pass = state.device.createRenderPass(pass_info);
|
||||
}
|
||||
|
||||
void ScreenRenderer::create_surface_image() {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2023 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_ARB_shading_language_420pack : enable
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
layout(push_constant) uniform viewport
|
||||
{
|
||||
uvec2 offset;
|
||||
uvec2 dim;
|
||||
uvec2 texture_dim;
|
||||
uvec2 output_dim;
|
||||
};
|
||||
|
||||
uvec4 con0;
|
||||
uvec4 con1;
|
||||
uvec4 con2;
|
||||
uvec4 con3;
|
||||
|
||||
#define A_GPU 1
|
||||
#define A_GLSL 1
|
||||
|
||||
#define A_HALF
|
||||
#include "../../../external/GPUOpen/ffx_a.h"
|
||||
layout(set=0,binding=1) uniform texture2D InputTexture;
|
||||
layout(set=0,binding=2,rgba16f) uniform image2D OutputTexture;
|
||||
layout(set=0,binding=3) uniform sampler InputSampler;
|
||||
#define FSR_EASU_H 1
|
||||
AH4 FsrEasuRH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture,InputSampler), p, 0)); return res; }
|
||||
AH4 FsrEasuGH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture,InputSampler), p, 1)); return res; }
|
||||
AH4 FsrEasuBH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture,InputSampler), p, 2)); return res; }
|
||||
|
||||
#include "../../../external/GPUOpen/ffx_fsr1.h"
|
||||
|
||||
void CurrFilter(AU2 pos)
|
||||
{
|
||||
AH3 c;
|
||||
FsrEasuH(c, pos, con0, con1, con2, con3);
|
||||
imageStore(OutputTexture, ASU2(pos), AH4(c, 1));
|
||||
}
|
||||
|
||||
layout(local_size_x=64) in;
|
||||
void main()
|
||||
{
|
||||
FsrEasuConOffset(con0, con1, con2, con3, dim.x, dim.y, texture_dim.x, texture_dim.y, output_dim.x, output_dim.y, offset.x, offset.y);
|
||||
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
|
||||
CurrFilter(gxy);
|
||||
gxy.x += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.y += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.x -= 8u;
|
||||
CurrFilter(gxy);
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
// Vita3K emulator project
|
||||
// Copyright (C) 2023 Vita3K team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_ARB_shading_language_420pack : enable
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
layout(push_constant) uniform viewport
|
||||
{
|
||||
uvec2 offset;
|
||||
float sharpening;
|
||||
};
|
||||
|
||||
uvec4 con0;
|
||||
|
||||
#define A_GPU 1
|
||||
#define A_GLSL 1
|
||||
|
||||
#define A_HALF
|
||||
#include "../../../external/GPUOpen/ffx_a.h"
|
||||
layout(set=0,binding=1) uniform texture2D InputTexture;
|
||||
layout(set=0,binding=2,rgba16f) uniform image2D OutputTexture;
|
||||
layout(set=0,binding=3) uniform sampler InputSampler;
|
||||
#define FSR_RCAS_H
|
||||
AH4 FsrRcasLoadH(ASW2 p) { return AH4(texelFetch(sampler2D(InputTexture,InputSampler), ASU2(p), 0)); }
|
||||
void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b){}
|
||||
|
||||
#include "../../../external/GPUOpen/ffx_fsr1.h"
|
||||
|
||||
void CurrFilter(AU2 pos)
|
||||
{
|
||||
AH3 c;
|
||||
FsrRcasH(c.r, c.g, c.b, pos, con0);
|
||||
imageStore(OutputTexture, ASU2(pos) + ASU2(offset), AH4(c, 1));
|
||||
}
|
||||
|
||||
layout(local_size_x=64) in;
|
||||
void main()
|
||||
{
|
||||
FsrRcasCon(con0, sharpening);
|
||||
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
|
||||
CurrFilter(gxy);
|
||||
gxy.x += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.y += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.x -= 8u;
|
||||
CurrFilter(gxy);
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -133,13 +133,13 @@ static constexpr ImageLayoutTransition layout_transitions[] = {
|
||||
// SampledImage
|
||||
{
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::AccessFlagBits::eShaderRead },
|
||||
// StorageImage
|
||||
{
|
||||
vk::ImageLayout::eGeneral,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eShaderRead },
|
||||
vk::PipelineStageFlagBits::eFragmentShader | vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite },
|
||||
// DepthReadOnly
|
||||
{
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
|
||||
Reference in New Issue
Block a user