Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions 3rdparty/FidelityFX-FSR/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# FidelityFX-FSR

The shader headers of AMD FidelityFX Super Resolution 1.0. The AGC driver compiles them into the presentation's EASU and RCAS shaders, which run under `ANYPS5_UPSCALE=fsr`.

- Source: https://github.com/GPUOpen-Effects/FidelityFX-FSR
- Tag: v1.0.2, commit a21ffb8f6c13233ba336352bdff293894c706575
- License: MIT, in [license.txt](license.txt)

The files are copied unmodified from that commit, at the same paths:

| File | SHA-256 |
|------|---------|
| `ffx-fsr/ffx_a.h` | `f60e2722fcd13989523b9164d776ab382b3692791767f3bf8bb19967f763f3fb` |
| `ffx-fsr/ffx_fsr1.h` | `93c3922362ea7fc99cbcc698ca30c98de4f8c246d1fbb0b09e015ddef38ce3a5` |
| `license.txt` | `db089274ce766da70f5b7d791029c3486f9f9e27c8c79c652689603d3192e802` |

They are copied instead of added as a submodule because every release tag of that repository carries two submodules for AMD's sample, Cauldron and Cauldron-Media, which a recursive checkout would fetch: about 337 MB at depth 1 and 1.47 GB with full history, for 243 KB of headers.
2,656 changes: 2,656 additions & 0 deletions 3rdparty/FidelityFX-FSR/ffx-fsr/ffx_a.h

Large diffs are not rendered by default.

1,199 changes: 1,199 additions & 0 deletions 3rdparty/FidelityFX-FSR/ffx-fsr/ffx_fsr1.h

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions 3rdparty/FidelityFX-FSR/license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
24 changes: 24 additions & 0 deletions core/libs/prx/libSceAgcDriver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,23 @@ foreach(agcOverlayStage IN ITEMS Vert Frag)
)
list(APPEND agcPresentationShaderHeaders ${agcOverlayShaderHeader})
endforeach()
set(agcFsrIncludeDir ${CMAKE_SOURCE_DIR}/3rdparty/FidelityFX-FSR/ffx-fsr)
foreach(agcFsrFilter IN ITEMS Easu Rcas)
string(TOUPPER "${agcFsrFilter}" agcFsrSymbol)
set(agcFsrShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/Fsr${agcFsrFilter}.frag)
set(agcFsrShaderSpv ${agcTextureDetileGeneratedDir}/Fsr${agcFsrFilter}Frag.spv)
set(agcFsrShaderHeader ${agcTextureDetileGeneratedDir}/Fsr${agcFsrFilter}Frag_spv.h)
add_custom_command(
OUTPUT "${agcFsrShaderHeader}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}"
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 "-I${agcFsrIncludeDir}" -o "${agcFsrShaderSpv}" "${agcFsrShaderSource}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcFsrShaderSpv} -DOUTPUT=${agcFsrShaderHeader}
-DSYMBOL=FSR_${agcFsrSymbol}_FRAG_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${agcFsrShaderSource}" "${agcFsrIncludeDir}/ffx_a.h" "${agcFsrIncludeDir}/ffx_fsr1.h" glslang-standalone
VERBATIM
)
list(APPEND agcPresentationShaderHeaders ${agcFsrShaderHeader})
endforeach()

set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp
Expand Down Expand Up @@ -319,6 +336,7 @@ set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/AspectFit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PresentationScaler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PresentationPass.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PresentationUpscaler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PerformanceOverlay.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/KeyboardOverlay.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PadStatusOverlay.cpp
Expand Down Expand Up @@ -2154,6 +2172,12 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_presentation_pass PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_presentation_fsr COMMAND agc_driver_presentation_pass_tests --fsr)
set_tests_properties(agc_driver_presentation_fsr PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_presentation_fsr PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

add_test_executable(agc_performance_overlay_statistics_tests EXCLUDE_FROM_ALL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>
#include "prx/libSceAgcDriver/Execution/include/AspectFit.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include <cstddef>
#include <cstdint>
Expand All @@ -29,6 +30,7 @@ class PresentationPass {
void Bind(std::size_t slot, VkImage image, std::uint32_t width, std::uint32_t height, std::uint64_t pixelFormat, float whiteNits, std::uint32_t layer = 0);
void BindTexels(std::size_t slot, VkCommandBuffer commands, std::span<const std::byte> texels, std::uint32_t width, std::uint32_t height, std::uint64_t pixelFormat, float whiteNits);
void RecordPresent(VkCommandBuffer commands, std::size_t slot, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout finalLayout);
void RecordStage(VkCommandBuffer commands, std::size_t slot, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout finalLayout);
void RecordOverlay(VkCommandBuffer commands, std::size_t slot, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout initialLayout, VkImageLayout finalLayout);
void RecordDump(VkCommandBuffer commands, std::size_t slot, std::uint32_t stride, VkBuffer destination);
void RecordDumpImage(VkCommandBuffer commands, std::size_t slot, std::uint32_t stride);
Expand All @@ -38,6 +40,7 @@ class PresentationPass {
void Release(std::size_t slot) noexcept;
static bool TenBitFormat(VkFormat format);
static VkExtent2D DumpExtent(std::uint32_t width, std::uint32_t height, std::uint32_t stride);
static VkSubpassDependency ExitDependency(VkImageLayout finalLayout);

private:
struct Target {
Expand Down Expand Up @@ -72,6 +75,7 @@ class PresentationPass {
void ensureUploadImage(Slot& slot, VkExtent2D extent);
void releaseUploadImage(Slot& slot) noexcept;
void draw(VkCommandBuffer commands, const Slot& slot, const Target& target, VkFramebuffer framebuffer, VkExtent2D extent, VkRect2D scissor, const std::int32_t (&parameters)[9]) const;
void recordInto(VkCommandBuffer commands, std::size_t slot, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout finalLayout, const AspectFitRect& rect);
void release() noexcept;

Graphics::Context context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class PresentationScaler {
// The aspect-fitting blit of RecordBlit from any image: presenting a resident render target needs
// no copy into the source image first.
static void RecordBlitFrom(const Graphics::Context& context, VkCommandBuffer commands, VkImage image, VkImageLayout layout, std::uint32_t width, std::uint32_t height, VkFilter filter, VkImage destinationImage, std::uint32_t destinationWidth, std::uint32_t destinationHeight, std::uint32_t layer = 0);
void RecordStretch(VkCommandBuffer commands, VkImage destinationImage, const AspectFitRect& destination);
static void RecordStretchFrom(const Graphics::Context& context, VkCommandBuffer commands, VkImage image, VkImageLayout layout, std::uint32_t width, std::uint32_t height, VkFilter filter, VkImage destinationImage, const AspectFitRect& destination, std::uint32_t layer = 0);
void RecordLetterbox(VkCommandBuffer commands, std::uint32_t width, std::uint32_t height, VkImage destinationImage, std::uint32_t destinationWidth, std::uint32_t destinationHeight);
static std::vector<AspectFitRect> LetterboxBands(std::uint32_t width, std::uint32_t height, std::uint32_t destinationWidth, std::uint32_t destinationHeight);
std::uint32_t SourceWidth() const { return sourceWidth; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PRESENTATIONUPSCALER_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PRESENTATIONUPSCALER_HPP

#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include <cstdint>
#include <map>
#include <tuple>
#include <utility>
#include <vector>

namespace AgcDriver {

class PresentationUpscaler {
public:
PresentationUpscaler(const Graphics::Context& context, std::size_t slots, float sharpness);
~PresentationUpscaler();
PresentationUpscaler(const PresentationUpscaler&) = delete;
PresentationUpscaler& operator=(const PresentationUpscaler&) = delete;

static bool Upscales(std::uint32_t width, std::uint32_t height, VkExtent2D extent);
static VkExtent2D StageExtent(std::uint32_t width, std::uint32_t height, VkExtent2D extent);
VkImage Stage(std::size_t slot, VkFormat format, VkExtent2D extent);
void RecordStageTransferDestination(VkCommandBuffer commands, std::size_t slot);
void RecordStageWritten(VkCommandBuffer commands, std::size_t slot, VkImageLayout layout, VkPipelineStageFlags stage, VkAccessFlags access);
void RecordUpscale(VkCommandBuffer commands, std::size_t slot, VkImage destination, VkExtent2D extent, VkImageLayout finalLayout);
void RecordPresent(VkCommandBuffer commands, std::size_t slot, std::uint32_t width, std::uint32_t height, VkImage destination, VkExtent2D extent, VkImageLayout finalLayout);
void Release(std::size_t slot) noexcept;
float Sharpness() const { return sharpness; }

private:
enum class Filter { Easu, Rcas };
struct Target {
VkRenderPass renderPass = VK_NULL_HANDLE;
VkPipeline pipeline = VK_NULL_HANDLE;
};
struct Surface {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView sampled = VK_NULL_HANDLE;
VkFormat format = VK_FORMAT_UNDEFINED;
VkExtent2D extent{};
};
struct Slot {
Surface stage;
Surface upscaled;
VkDescriptorSet easuSet = VK_NULL_HANDLE;
VkDescriptorSet rcasSet = VK_NULL_HANDLE;
std::vector<std::pair<VkImageView, VkFramebuffer>> transient;
};

const Target& target(VkFormat format, VkImageLayout finalLayout, Filter filter);
void ensureSurface(Surface& surface, VkFormat format, VkExtent2D extent, VkImageUsageFlags usage);
void releaseSurface(Surface& surface) noexcept;
VkImageView createView(VkImage image, VkFormat format, VkImageUsageFlags usage) const;
VkFramebuffer transientFramebuffer(Slot& slot, VkRenderPass renderPass, VkImage image, VkFormat format, VkExtent2D extent, VkImageLayout finalLayout);
void bindSource(VkDescriptorSet set, VkImageView view) const;
void draw(VkCommandBuffer commands, const Target& target, VkFramebuffer framebuffer, VkExtent2D extent, VkDescriptorSet set, const void* parameters, std::uint32_t size) const;
void release() noexcept;

Graphics::Context context;
float sharpness;
VkSampler sampler = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
VkShaderModule vertex = VK_NULL_HANDLE;
VkShaderModule easu = VK_NULL_HANDLE;
VkShaderModule rcas = VK_NULL_HANDLE;
std::map<std::tuple<VkFormat, VkImageLayout, Filter>, Target> targets;
std::vector<Slot> slots;
};

}

#endif
34 changes: 34 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/UpscaleMode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_UPSCALEMODE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_UPSCALEMODE_HPP

#include <charconv>
#include <cmath>
#include <stdexcept>
#include <string_view>

namespace AgcDriver {

struct UpscaleMode {
bool fsr = false;
float sharpness = 0.2f;
};

inline UpscaleMode SelectUpscaleMode(const char* upscale, const char* sharpness) {
UpscaleMode mode;
if (upscale != nullptr && *upscale != '\0') {
if (std::string_view(upscale) != "fsr") throw std::runtime_error("Vulkan presentation: ANYPS5_UPSCALE must be fsr or unset");
mode.fsr = true;
}
if (sharpness != nullptr && *sharpness != '\0') {
const std::string_view text(sharpness);
float value = 0.0f;
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
if (error != std::errc{} || end != text.data() + text.size() || !std::isfinite(value) || value < 0.0f || value > 2.0f) throw std::runtime_error("Vulkan presentation: ANYPS5_FSR_SHARPNESS must be a number from 0.0 to 2.0");
mode.sharpness = value;
}
return mode;
}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ class VulkanDevice {
std::uint64_t checked, presyncs, keyChecks, keyWaits;
};
static PresyncStatistics PresyncCounts();
static std::uint64_t UpscaledPresents();
// Stage A of a dispatch's resource build, run WITHOUT GuestMemory::GpuMutex before Dispatch or
// DispatchIndirect: the binding plan, data buffers, imports already serving the guest buffers,
// read-only copies and the descriptor set (see ShaderResources). The dispatch completes it
Expand Down
27 changes: 24 additions & 3 deletions core/libs/prx/libSceAgcDriver/Execution/src/PresentationPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ VkExtent2D PresentationPass::DumpExtent(std::uint32_t width, std::uint32_t heigh
return {(width + stride - 1) / stride, (height + stride - 1) / stride};
}

VkSubpassDependency PresentationPass::ExitDependency(VkImageLayout finalLayout) {
VkSubpassDependency dependency{0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0, 0};
if (finalLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
dependency.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
dependency.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
} else if (finalLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
dependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
dependency.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
}
return dependency;
}

void PresentationPass::Release(std::size_t index) noexcept {
if (index >= slots.size()) return;
auto& slot = slots[index];
Expand Down Expand Up @@ -180,10 +192,9 @@ const PresentationPass::Target& PresentationPass::target(VkFormat format, VkImag
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &reference;
const bool transfer = finalLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
const VkSubpassDependency dependencies[2] = {
{VK_SUBPASS_EXTERNAL, 0, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, blended ? VkAccessFlags{VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT} : VkAccessFlags{0}, blended ? VkAccessFlags{VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT} : VkAccessFlags{VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}, 0},
{0, VK_SUBPASS_EXTERNAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, transfer ? VK_PIPELINE_STAGE_TRANSFER_BIT : VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, transfer ? VK_ACCESS_TRANSFER_READ_BIT : VkAccessFlags{0}, 0},
ExitDependency(finalLayout),
};
VkRenderPassCreateInfo renderPassInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
renderPassInfo.attachmentCount = 1;
Expand Down Expand Up @@ -363,6 +374,7 @@ VkFramebuffer PresentationPass::destinationFramebuffer(Slot& slot, const Target&
for (const auto layout : {initialLayout, finalLayout}) {
if (layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if (layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if (layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
const auto view = createView(destination, format, usage);
VkFramebuffer framebuffer = VK_NULL_HANDLE;
Expand All @@ -379,10 +391,19 @@ VkFramebuffer PresentationPass::destinationFramebuffer(Slot& slot, const Target&
void PresentationPass::RecordPresent(VkCommandBuffer commands, std::size_t index, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout finalLayout) {
Graphics::Require(index < slots.size() && slots[index].source != VK_NULL_HANDLE, "presentation pass slot has no source");
Graphics::Require(destination != VK_NULL_HANDLE && extent.width != 0 && extent.height != 0, "presentation pass destination is unavailable");
recordInto(commands, index, destination, format, colorSpace, extent, finalLayout, ComputeContainRect_nid_postfix(slots[index].width, slots[index].height, extent.width, extent.height));
}

void PresentationPass::RecordStage(VkCommandBuffer commands, std::size_t index, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout finalLayout) {
Graphics::Require(index < slots.size() && slots[index].source != VK_NULL_HANDLE, "presentation pass slot has no source");
Graphics::Require(destination != VK_NULL_HANDLE && extent.width != 0 && extent.height != 0, "presentation pass destination is unavailable");
recordInto(commands, index, destination, format, colorSpace, extent, finalLayout, AspectFitRect{0, 0, extent.width, extent.height});
}

void PresentationPass::recordInto(VkCommandBuffer commands, std::size_t index, VkImage destination, VkFormat format, VkColorSpaceKHR colorSpace, VkExtent2D extent, VkImageLayout finalLayout, const AspectFitRect& rect) {
auto& slot = slots[index];
const auto& entry = target(format, finalLayout);
const auto framebuffer = destinationFramebuffer(slot, entry, destination, format, extent, VK_IMAGE_LAYOUT_UNDEFINED, finalLayout);
const auto rect = ComputeContainRect_nid_postfix(slot.width, slot.height, extent.width, extent.height);
const bool hdr10 = colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT;
Graphics::Require(colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR || (hdr10 && (slot.mode & ModePq) != 0 && TenBitFormat(format)), "presentation pass presents only PQ display buffers to a 10-bit HDR10 destination");
const std::uint32_t mode = (hdr10 ? slot.mode & ~ModePq : slot.mode) | (TenBitFormat(format) ? ModeKeepTenBit : 0u);
Expand Down
Loading
Loading