renderer/vulkan: Add FSR upscaling

This commit is contained in:
Macdu
2023-05-22 00:04:40 +02:00
parent 18f9b84276
commit edca8593ae
16 changed files with 4396 additions and 30 deletions
+2656
View File
File diff suppressed because it is too large Load Diff
+1199
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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);
+3 -2
View File
@@ -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;
+2 -1
View File
@@ -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;
+33 -11
View File
@@ -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) {
+286 -2
View File
@@ -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
+28 -9
View File
@@ -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);
}
@@ -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);
}
+3 -3
View File
@@ -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,