graphics: add experimental Pro mode and linear PSSR

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

No files matched your search

+4
View File
@@ -113,6 +113,10 @@ bool VrEnabled() {
return g_config->vr_enabled;
}
bool Ps5ProEnabled() {
return g_config->pro_enabled;
}
bool AmdCpuEnabled() {
return g_config->amd_cpu_enabled;
}
+2
View File
@@ -67,6 +67,7 @@ struct ConfigOptions {
bool stretch_enabled = false;
bool splash_enabled = true;
bool vr_enabled = false;
bool pro_enabled = false;
bool amd_cpu_enabled = false;
bool network_connected = false;
uint32_t vblank_frequency = 60;
@@ -128,6 +129,7 @@ bool HdrEnabled();
bool HideCursorEnabled();
bool StretchEnabled();
bool VrEnabled();
bool Ps5ProEnabled();
bool AmdCpuEnabled();
bool IsNetworkConnected();
uint32_t GetVblankFrequency();
@@ -3,6 +3,7 @@
#include "common/assert.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -92,6 +93,7 @@ public:
uint32_t mode);
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
void ProcessPssr(const Pssr::Request& request);
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
void WaitForRewind(bool valid);
@@ -226,6 +226,7 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
g_cp_op_custom_func[Pm4::R_PSSR] = CpOpPssr;
return g_cp_op_custom_func;
}
@@ -151,6 +151,7 @@ uint32_t CpOpPushMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
uint32_t CpOpPopMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitRegMem64(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpFlip(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPssr(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
} // namespace Libs::Graphics
@@ -1706,6 +1706,17 @@ KYTY_CP_OP_PARSER(CpOpFlip) {
return 5;
}
KYTY_CP_OP_PARSER(CpOpPssr) {
KYTY_PROFILER_FUNCTION();
const auto packet_dw = KYTY_PM4_LEN(cmd_id) - 1;
EXIT_NOT_IMPLEMENTED(packet_dw < Pssr::RequestDwords || packet_dw > dw);
Pssr::Request request {};
std::memcpy(&request, buffer, sizeof(request));
cp.ProcessPssr(request);
return packet_dw;
}
KYTY_CP_OP_PARSER(CpOpIndexType) {
KYTY_PROFILER_FUNCTION();
+5
View File
@@ -929,6 +929,11 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
}
}
void CommandProcessor::ProcessPssr(const Pssr::Request& request) {
EXIT_NOT_IMPLEMENTED(IsAsyncComputeQueue());
m_renderer.GetRenderExecutor().ProcessPssr(CurrentBuffer(), request);
}
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
+1
View File
@@ -81,6 +81,7 @@ constexpr uint32_t R_WRITE_DATA = 0x15;
constexpr uint32_t R_FLIP = 0x17;
constexpr uint32_t R_DMA_DATA = 0x19;
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
constexpr uint32_t R_PSSR = 0x1B;
constexpr uint32_t R_NUM = 0x3F + 1;
+20
View File
@@ -0,0 +1,20 @@
#ifndef EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
#define EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
#include <cstdint>
namespace Libs::Graphics::Pssr {
struct Request {
uint32_t output[8];
uint32_t input[8];
uint32_t width;
uint32_t height;
};
constexpr uint32_t RequestDwords = sizeof(Request) / sizeof(uint32_t);
static_assert(sizeof(Request) == 72);
} // namespace Libs::Graphics::Pssr
#endif // EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
+9
View File
@@ -150,15 +150,21 @@ private:
friend class CommandScheduler;
};
namespace Pssr {
struct Request;
}
class RenderExecutor {
public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
~RenderExecutor();
KYTY_CLASS_NO_COPY(RenderExecutor);
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode, bool async_compute);
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
uint32_t mode, bool async_compute);
void ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(std::span<PreparedBindings* const> stages);
@@ -219,6 +225,9 @@ private:
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
vk::DescriptorSetLayout m_pssr_descriptor_layout = nullptr;
vk::PipelineLayout m_pssr_pipeline_layout = nullptr;
vk::Pipeline m_pssr_pipeline = nullptr;
friend class CommandProcessor;
friend struct RenderExecutorTestAccess;
@@ -0,0 +1,184 @@
#include "common/assert.h"
#include "common/threads.h"
#include "gpu_blit_shaders/gpu_pssr_linear_spv.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/shaderBindings.h"
#include <algorithm>
#include <array>
namespace Libs::Graphics {
namespace {
struct PssrParameters {
uint32_t input_size[2];
uint32_t output_size[2];
uint32_t output_channels[4];
};
bool IsInputFormat(Prospero::BufferFormat format) {
return format == Prospero::BufferFormat::k11_11_10Float ||
format == Prospero::BufferFormat::k10_11_11Float ||
format == Prospero::BufferFormat::k16_16_16_16Float;
}
} // namespace
RenderExecutor::~RenderExecutor() {
const auto device = m_context.GetGraphics().device;
device.destroyPipeline(m_pssr_pipeline, nullptr);
device.destroyPipelineLayout(m_pssr_pipeline_layout, nullptr);
device.destroyDescriptorSetLayout(m_pssr_descriptor_layout, nullptr);
}
void RenderExecutor::ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request) {
EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
Common::LockGuard lock(m_context.GetMutex());
ShaderTextureResource input_descriptor {}, output_descriptor {};
std::copy_n(request.input, 8, input_descriptor.fields);
std::copy_n(request.output, 8, output_descriptor.fields);
const auto is_2d = [](const ShaderTextureResource& descriptor) {
return !descriptor.IsNull() &&
(descriptor.Type() == Prospero::ImageType::kColor2D ||
descriptor.Type() == Prospero::ImageType::kColor2DArray) &&
descriptor.TileMode() != Prospero::TileMode::kDepth;
};
EXIT_IF(!is_2d(input_descriptor) || !is_2d(output_descriptor) ||
!IsInputFormat(input_descriptor.Format()) ||
output_descriptor.Format() != Prospero::BufferFormat::k11_11_10Float ||
output_descriptor.TileMode() != Prospero::TileMode::kStandard256B ||
output_descriptor.BaseLevel() != 0 || output_descriptor.LastLevel() != 0 ||
output_descriptor.MaxMip() != 0 || output_descriptor.BaseArray5() != 0 ||
output_descriptor.Depth() != 0 || request.width == 0 || request.height == 0);
const auto input_width = std::max(1u, (input_descriptor.Width5() + 1u) >>
input_descriptor.BaseLevel());
const auto input_height = std::max(1u, (input_descriptor.Height5() + 1u) >>
input_descriptor.BaseLevel());
EXIT_IF(request.width > input_width || request.height > input_height);
PssrParameters parameters {{request.width, request.height},
{output_descriptor.Width5() + 1u, output_descriptor.Height5() + 1u},
{4, 4, 4, 4}};
for (uint32_t component = 0; component < 3; ++component) {
for (uint32_t source = 0; source < 4; ++source) {
if (GetDstSel(output_descriptor.DstSelXYZW(), source) == 4u + component) {
parameters.output_channels[component] = source;
break;
}
}
}
const auto resolve = [&](const uint32_t* words, bool output) {
ShaderRecompiler::IR::ImageResource resource {};
resource.resource_class = output ? ShaderRecompiler::IR::ImageResourceClass::Storage
: ShaderRecompiler::IR::ImageResourceClass::Sampled;
resource.numeric_class = Prospero::TextureNumericClass::Float;
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
resource.read = !output;
resource.written = output;
ShaderRecompiler::IR::DescriptorValue value {};
std::copy_n(words, 8, value.dwords.begin());
value.dword_count = 8;
auto binding = ResolveTexture(resource, value);
binding.desc.view_info.level_count = 1;
binding.desc.view_info.min_lod = 0;
return binding;
};
auto& cache = m_context.GetTextureCache();
auto input = resolve(request.input, false);
BindImage(input.image_id, false);
auto output = resolve(request.output, true);
BindImage(output.image_id, true);
EXIT_IF(input.desc.info.data.address < output.desc.info.data.End() &&
output.desc.info.data.address < input.desc.info.data.End());
const auto acquire = [&](TextureBinding& binding, const uint32_t* words, bool storage) {
auto& previous = cache.GetImage(binding.image_id);
if (!previous.registered || previous.binding.needs_rebind) {
previous.binding = {};
binding = resolve(words, storage);
BindImage(binding.image_id, storage);
}
binding.image_view = cache.FindTexture(binding.image_id, binding.desc);
};
acquire(input, request.input, false);
acquire(output, request.output, true);
auto& source = cache.GetImage(input.image_id);
auto& destination = cache.GetImage(output.image_id);
const auto& source_view = input.desc.view_info;
const auto& destination_view = output.desc.view_info;
EXIT_IF(source.info.IsDepth() || destination.info.IsDepth() ||
source.backing.samples != 1 || destination.backing.samples != 1 ||
(input.image_id == output.image_id &&
source_view.base_level == destination_view.base_level &&
source_view.base_layer == destination_view.base_layer));
auto& graphics = m_context.GetGraphics();
if (m_pssr_pipeline == nullptr) {
const std::array bindings {
vk::DescriptorSetLayoutBinding {0, vk::DescriptorType::eSampledImage, 1,
vk::ShaderStageFlagBits::eCompute},
vk::DescriptorSetLayoutBinding {1, vk::DescriptorType::eStorageImage, 1,
vk::ShaderStageFlagBits::eCompute}};
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = bindings.size();
descriptor_info.pBindings = bindings.data();
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(
&descriptor_info, nullptr, &m_pssr_descriptor_layout),
"create upscaling descriptor layout");
const vk::PushConstantRange push {vk::ShaderStageFlagBits::eCompute, 0,
sizeof(PssrParameters)};
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_pssr_descriptor_layout;
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = &push;
RequireVulkanSuccess(graphics.device.createPipelineLayout(
&layout_info, nullptr, &m_pssr_pipeline_layout),
"create upscaling pipeline layout");
const auto shader = CompileSPV(GPU_PSSR_LINEAR_SPV, graphics.device);
vk::ComputePipelineCreateInfo create {};
create.stage.stage = vk::ShaderStageFlagBits::eCompute;
create.stage.module = shader;
create.stage.pName = "main";
create.layout = m_pssr_pipeline_layout;
const auto result = graphics.device.createComputePipelines(
nullptr, 1, &create, nullptr, &m_pssr_pipeline);
graphics.device.destroyShaderModule(shader, nullptr);
RequireVulkanSuccess(result, "create upscaling pipeline");
}
buffer.EndRendering();
const auto command = buffer.Handle();
source.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead,
ImageSubresourceRange {source_view.base_level, 1, source_view.base_layer, 1},
command);
destination.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite,
ImageSubresourceRange {destination_view.base_level, 1,
destination_view.base_layer, 1}, command);
const std::array images {
vk::DescriptorImageInfo {nullptr, input.image_view, vk::ImageLayout::eGeneral},
vk::DescriptorImageInfo {nullptr, output.image_view, vk::ImageLayout::eGeneral}};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t i = 0; i < writes.size(); ++i) {
writes[i].dstBinding = i;
writes[i].descriptorCount = 1;
writes[i].descriptorType = i == 0 ? vk::DescriptorType::eSampledImage
: vk::DescriptorType::eStorageImage;
writes[i].pImageInfo = &images[i];
}
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pssr_pipeline_layout,
0, writes.size(), writes.data());
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_pssr_pipeline);
command.pushConstants(m_pssr_pipeline_layout, vk::ShaderStageFlagBits::eCompute,
0, sizeof(parameters), &parameters);
command.dispatch((parameters.output_size[0] + 7) / 8,
(parameters.output_size[1] + 7) / 8, 1);
ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader);
ResetBindings();
}
} // namespace Libs::Graphics
@@ -0,0 +1,33 @@
#version 450 core
#extension GL_EXT_samplerless_texture_functions : require
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform texture2D source;
layout(binding = 1) writeonly uniform image2D destination;
layout(push_constant) uniform Parameters {
uvec2 input_size;
uvec2 output_size;
uvec4 output_channels;
} parameters;
vec4 fetch_color(ivec2 coordinate) {
return texelFetch(source, clamp(coordinate, ivec2(0), ivec2(parameters.input_size) - 1), 0);
}
void main() {
uvec2 output_coordinate = gl_GlobalInvocationID.xy;
if (any(greaterThanEqual(output_coordinate, parameters.output_size))) return;
vec2 input_coordinate = (vec2(output_coordinate) + 0.5) *
vec2(parameters.input_size) / vec2(parameters.output_size) - 0.5;
ivec2 lower = ivec2(floor(input_coordinate));
vec2 weight = fract(input_coordinate);
vec4 color = mix(mix(fetch_color(lower), fetch_color(lower + ivec2(1, 0)), weight.x),
mix(fetch_color(lower + ivec2(0, 1)), fetch_color(lower + ivec2(1, 1)), weight.x),
weight.y);
vec4 stored = vec4(0.0);
for (uint channel = 0; channel < 3; ++channel) {
uint selected = parameters.output_channels[channel];
if (selected < 4) stored[channel] = color[selected];
}
imageStore(destination, ivec2(output_coordinate), stored);
}
+4 -2
View File
@@ -1006,8 +1006,10 @@ void Initialize() {
const auto table_overhead = g_cpu_table_memory + g_gpu_table_memory +
std::max(params.amm_table_memory, INCLUDED_TABLE_MEMORY) -
3 * INCLUDED_TABLE_MEMORY;
EXIT_IF(g_flexible_memory_size + table_overhead >= GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE);
g_guest_memory_size = GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE - table_overhead;
const auto memory_budget = GUEST_MEMORY_BUDGET +
(Loader::SystemContentIsTrinityMode() ? 1216ull * 1024ull * 1024ull : 0);
EXIT_IF(g_flexible_memory_size + table_overhead >= memory_budget - RESERVED_CACHE_SIZE);
g_guest_memory_size = memory_budget - RESERVED_CACHE_SIZE - table_overhead;
LOGF("\t flexible memory size = %" PRIu64 " MiB, direct memory size = %" PRIu64 " MiB\n",
g_flexible_memory_size / (1024ull * 1024ull),
(g_guest_memory_size - g_flexible_memory_size) / (1024ull * 1024ull));
@@ -254,6 +254,12 @@
<widget class="QGroupBox" name="general_group">
<property name="title"><string>General</string></property>
<layout class="QVBoxLayout" name="generalLayout">
<item>
<widget class="QCheckBox" name="checkBox_pro">
<property name="toolTip"><string>Enable experimental Pro mode for supported titles. PSSR uses linear scaling. Disabled by default.</string></property>
<property name="text"><string>Pro mode (experimental)</string></property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_fullscreen">
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
+2
View File
@@ -141,6 +141,7 @@ public:
QString printf_output_file = "_kyty.txt";
bool profiler_enabled = false;
bool renderdoc_enabled = false;
bool pro_enabled = false;
bool amd_cpu_enabled = false;
#if defined(_WIN32)
bool red_zone_protection_enabled = false;
@@ -184,6 +185,7 @@ public:
printf_output_file = other.printf_output_file;
profiler_enabled = other.profiler_enabled;
renderdoc_enabled = other.renderdoc_enabled;
pro_enabled = other.pro_enabled;
amd_cpu_enabled = other.amd_cpu_enabled;
#if defined(_WIN32)
red_zone_protection_enabled = other.red_zone_protection_enabled;
+2
View File
@@ -55,6 +55,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_enabled);
KYTY_CFG_SET(renderdoc_enabled);
KYTY_CFG_SET(pro_enabled);
KYTY_CFG_SET(amd_cpu_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
@@ -112,6 +113,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_enabled);
KYTY_CFG_GET(renderdoc_enabled);
pro_enabled = s.value("pro_enabled", false).toBool();
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
#if defined(_WIN32)
red_zone_protection_enabled =
@@ -311,6 +311,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled);
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
m_ui->checkBox_pro->setChecked(info.pro_enabled);
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
#if defined(__APPLE__)
m_ui->checkBox_amd_cpu->setVisible(false);
@@ -541,6 +542,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked();
info.pro_enabled = ui.checkBox_pro->isChecked();
info.amd_cpu_enabled = ui.checkBox_amd_cpu->isChecked();
#if defined(_WIN32)
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
+1
View File
@@ -291,6 +291,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--printf-direction" << EnumToText(info.printf_direction);
args << "--printf-output-file" << info.printf_output_file;
AddFlag(args, info.profiler_enabled, "--profile");
AddFlag(args, info.pro_enabled, "--trinity");
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
#if defined(_WIN32)
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
+3 -2
View File
@@ -24,6 +24,7 @@
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
@@ -839,8 +840,8 @@ static bool is_native_reg_indirect_packet(const uint32_t* cmd, RegIndirectPacket
return ((cmd[0] >> 8u) & 0xffu) == reg_indirect_native_op(type);
}
static constexpr bool is_trinity_mode() {
return false;
static bool is_trinity_mode() {
return Loader::SystemContentIsTrinityMode();
}
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
+3 -13
View File
@@ -20,6 +20,7 @@
#include "libs/network.h"
#include "loader/elf.h"
#include "loader/runtimeLinker.h"
#include "loader/systemContent.h"
#include "loader/symbolDatabase.h"
#include <algorithm>
@@ -1321,23 +1322,12 @@ static void* KYTY_SYSV_ABI KernelGetProcParam() {
}
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
static std::atomic_uint32_t call_count {0};
auto count = call_count.fetch_add(1, std::memory_order_relaxed);
if (count < 8 || (count % 1024) == 0) {
PRINT_NAME();
LOGF("\tcall = %u, return = 0 (base PS5 mode)\n", count + 1);
}
return 0;
return Loader::SystemContentIsTrinityMode() ? 1 : 0;
}
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
PRINT_NAME();
*mode = 2; // PS5 Base
*mode = Loader::SystemContentIsTrinityMode() ? 3 : 2;
*submode = 0; // None
LOGF("\t mode = %d, submode = %d\n", *mode, *submode);
return OK;
}
+283 -256
View File
@@ -1,349 +1,376 @@
#include "graphics/guest_gpu/pm4.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/shader/shaderBindings.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include "loader/systemContent.h"
#include <atomic>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <unordered_map>
namespace Libs {
namespace LibPsml {
namespace Libs::LibPsml {
LIB_VERSION("Psml", 1, "Psml", 1, 1);
namespace Psml {
namespace {
constexpr int PSML_ERROR_NOT_INITIALIZED = static_cast<int>(0x8a810001u);
constexpr int PSML_ERROR_INVALID_OBJECT = static_cast<int>(0x8a810005u);
constexpr int PSML_ERROR_INVALID_POINTER = static_cast<int>(0x8a810009u);
constexpr int PSML_ERROR_INVALID_VALUE = static_cast<int>(0x8a81000du);
constexpr int PSML_ERROR_NULL_OBJECT = static_cast<int>(0x8a810014u);
constexpr int NOT_INITIALIZED = static_cast<int>(0x8a810001u);
constexpr int ALREADY_INITIALIZED = static_cast<int>(0x8a810002u);
constexpr int TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
constexpr int INVALID_CONTEXT = static_cast<int>(0x8a810005u);
constexpr int COMMAND_BUFFER_FULL = static_cast<int>(0x8a810006u);
constexpr int COMMAND_BUFFER_BAD = static_cast<int>(0x8a810007u);
constexpr int INSUFFICIENT_BUFFER = static_cast<int>(0x8a810008u);
constexpr int ARGUMENT_NULL = static_cast<int>(0x8a810009u);
constexpr int TEXTURE_NULL = static_cast<int>(0x8a81000au);
constexpr int INVALID_TEXTURE = static_cast<int>(0x8a81000bu);
constexpr int INVALID_PARAMETER = static_cast<int>(0x8a81000du);
constexpr int UNSUPPORTED_RENDER_SIZE = static_cast<int>(0x8a81000eu);
constexpr int INVALID_OUTPUT_TEXTURE = static_cast<int>(0x8a81000fu);
constexpr int NOT_TRINITY_MODE = static_cast<int>(0x8a810012u);
constexpr int UNSUPPORTED_OUTPUT_SIZE = static_cast<int>(0x8a810013u);
constexpr int INVALID_SHARED_RESOURCES = static_cast<int>(0x8a810014u);
constexpr int TOO_MANY_SHARED_RESOURCES = static_cast<int>(0x8a810015u);
constexpr uint64_t MEMORY_BLOCK_SIZE = 0x200000;
constexpr uint32_t PACKET_DWORDS = 1 + Graphics::Pssr::RequestDwords;
constexpr uint32_t SHARED_RESOURCES_MAGIC = 0xa9c4;
constexpr uint32_t CONTEXT_MAGIC = 0x9231;
constexpr uint64_t MAIN_MEMORY_BLOCK_SIZE = 0x200000;
constexpr uint64_t MAIN_MEMORY_ALIGNMENT = 0x200000;
constexpr uint64_t EXTRA_VA_BYTES = 0x600000;
struct MainMemoryRequirements {
struct MemoryRequirements {
uint64_t block_size;
uint64_t alignment;
uint64_t block_count;
};
struct MainMemoryParameters {
uint32_t type;
uint32_t reserved;
};
struct DirectMemoryBlock {
uint64_t address;
uint64_t offset;
uint64_t size;
};
struct SharedResourcesInitParameters {
uint32_t type;
uint32_t reserved;
struct MemoryParameters {
uint32_t profile;
uint32_t flags;
const DirectMemoryBlock* blocks;
uint64_t block_count;
uint64_t virtual_address_start;
};
static_assert(sizeof(MainMemoryRequirements) == 24);
static_assert(sizeof(DirectMemoryBlock) == 16);
struct SharedParameters {
MemoryParameters memory;
uint64_t virtual_address;
};
static bool g_initialized = false;
static uint32_t g_supported_modes = 0x3;
static std::atomic<int32_t> g_shared_ref_count = 0;
struct ContextParameters {
MemoryParameters memory;
uint16_t width;
uint16_t height;
uint32_t reserved;
const void* depth_copy;
const void* motion_copy;
const char* name;
void* shared;
};
static_assert(offsetof(ContextParameters, width) == 0x18);
static_assert(offsetof(ContextParameters, shared) == 0x38);
static uint64_t BaseVaBytes(uint32_t type) {
// The native implementation indexes this table before adding one 6 MiB range.
switch (type) {
case 0: return 0x06200000;
case 1: return 0x18200000;
case 2: return 0x12200000;
default: return 0;
struct SharedResources {
uint32_t contexts = 0;
};
struct Context {
uint32_t width;
uint32_t height;
SharedResources* shared;
};
struct CommandSpace {
uint32_t* cmd;
uint32_t dwords;
};
std::mutex g_mutex;
bool g_initialized = false;
std::unique_ptr<SharedResources> g_shared;
std::unordered_map<const void*, std::unique_ptr<Context>> g_contexts;
uint64_t SharedBlockCount(uint32_t profile) {
constexpr uint64_t bytes[] = {0x06200000, 0x18200000, 0x12200000};
return profile < 3 ? (bytes[profile] + 0x600000) / MEMORY_BLOCK_SIZE : 0;
}
int CheckContext(const void* context) {
if (!g_initialized) {
return NOT_INITIALIZED;
}
return g_contexts.contains(context) ? OK : INVALID_CONTEXT;
}
static bool IsSupportedType(uint32_t type) {
switch (type) {
case 0: return true;
case 1: return (g_supported_modes & 0x1u) != 0;
case 2: return (g_supported_modes & 0x2u) != 0;
default: return false;
int KYTY_SYSV_ABI Initialize() {
std::lock_guard lock(g_mutex);
if (!Loader::SystemContentIsTrinityMode()) {
return NOT_TRINITY_MODE;
}
}
static uint64_t RequiredBlockCount(uint32_t type) {
const auto base = BaseVaBytes(type);
if (base == 0) {
return 0;
if (g_initialized) {
return ALREADY_INITIALIZED;
}
return (base + EXTRA_VA_BYTES) / MAIN_MEMORY_BLOCK_SIZE;
}
static int CheckInitialized() {
return (g_initialized ? OK : PSML_ERROR_NOT_INITIALIZED);
}
static void MarkSharedResources(void* resources, const SharedResourcesInitParameters& params) {
auto* bytes = static_cast<uint8_t*>(resources);
*reinterpret_cast<uint32_t*>(bytes) = SHARED_RESOURCES_MAGIC;
*reinterpret_cast<const DirectMemoryBlock**>(bytes + 0x08) = params.blocks;
*reinterpret_cast<uint64_t*>(bytes + 0x18) = params.block_count;
*reinterpret_cast<uint32_t*>(bytes + 0x20) = params.type;
*reinterpret_cast<uint64_t*>(bytes + 0x28) = params.virtual_address_start;
}
static bool HasKnownObjectMagic(const void* object) {
if (object == nullptr) {
return false;
}
const auto magic = *static_cast<const uint32_t*>(object);
return magic == SHARED_RESOURCES_MAGIC || magic == CONTEXT_MAGIC;
}
static int KYTY_SYSV_ABI PsmlInitialize() {
PRINT_NAME();
g_initialized = true;
g_supported_modes = 0x3;
g_shared_ref_count = 0;
g_initialized = true;
return OK;
}
static int KYTY_SYSV_ABI PsmlGetMainMemoryRequirements(MainMemoryRequirements* out,
const MainMemoryParameters* params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI GetSharedRequirements(MemoryRequirements* out, const MemoryParameters* params) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (out == nullptr || params == nullptr) {
return PSML_ERROR_INVALID_POINTER;
return ARGUMENT_NULL;
}
if (!IsSupportedType(params->type)) {
return PSML_ERROR_INVALID_VALUE;
const auto blocks = SharedBlockCount(params->profile);
if (blocks == 0) {
return INVALID_PARAMETER;
}
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
out->alignment = MAIN_MEMORY_ALIGNMENT;
out->block_count = RequiredBlockCount(params->type);
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, blocks};
return OK;
}
static int KYTY_SYSV_ABI
PsmlSharedResourcesInitialize(void* resources, const SharedResourcesInitParameters* params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI CreateSharedResources(void** out, const SharedParameters* params) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (resources == nullptr || params == nullptr || params->blocks == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (out == nullptr || params == nullptr || params->memory.blocks == nullptr) {
return ARGUMENT_NULL;
}
if (params->reserved != 0 || !IsSupportedType(params->type) ||
params->block_count < RequiredBlockCount(params->type)) {
return PSML_ERROR_INVALID_VALUE;
const auto required = SharedBlockCount(params->memory.profile);
if (required == 0 || params->memory.flags != 0) {
return INVALID_PARAMETER;
}
MarkSharedResources(resources, *params);
g_shared_ref_count.fetch_add(1, std::memory_order_relaxed);
if (params->memory.block_count < required) {
return INSUFFICIENT_BUFFER;
}
if (g_shared) {
return TOO_MANY_SHARED_RESOURCES;
}
g_shared = std::make_unique<SharedResources>();
*out = g_shared.get();
return OK;
}
static int KYTY_SYSV_ABI PsmlSharedResourcesFinalize(void* resources) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI ReleaseSharedResources(void* resources) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (resources == nullptr) {
return PSML_ERROR_NULL_OBJECT;
if (!g_shared || resources != g_shared.get()) {
return INVALID_SHARED_RESOURCES;
}
g_shared_ref_count.fetch_sub(1, std::memory_order_relaxed);
if (g_shared->contexts != 0) {
return INVALID_CONTEXT;
}
g_shared.reset();
return OK;
}
static int KYTY_SYSV_ABI PsmlGetContextMemoryRequirements(MainMemoryRequirements* out,
uint64_t /*params*/) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI GetContextRequirements(MemoryRequirements* out, const void* params) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (out == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (out == nullptr || params == nullptr) {
return ARGUMENT_NULL;
}
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
out->alignment = MAIN_MEMORY_ALIGNMENT;
out->block_count = 1;
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, 1};
return OK;
}
static int KYTY_SYSV_ABI PsmlContextInitialize(void* context, const void* params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int CreateContext(void** out, const MemoryParameters* memory, uint32_t width, uint32_t height,
void* shared, bool use_shared) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (context == nullptr || params == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (out == nullptr || memory == nullptr || memory->blocks == nullptr) {
return ARGUMENT_NULL;
}
auto* bytes = static_cast<uint8_t*>(context);
auto* params_bytes = static_cast<const uint8_t*>(params);
auto* shared_resources = *reinterpret_cast<void* const*>(params_bytes + 0x08);
if (shared_resources == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (memory->profile > 2) {
return INVALID_PARAMETER;
}
*reinterpret_cast<uint32_t*>(bytes) = CONTEXT_MAGIC;
*reinterpret_cast<void**>(bytes + 0x360) = shared_resources;
*reinterpret_cast<uint8_t*>(bytes + 0x368) = 0;
if (memory->block_count == 0) {
return INSUFFICIENT_BUFFER;
}
if (width == 0 || height == 0 || width > 16384 || height > 16384) {
return UNSUPPORTED_OUTPUT_SIZE;
}
if (use_shared && (!g_shared || shared != g_shared.get())) {
return INVALID_SHARED_RESOURCES;
}
if (g_contexts.size() == 4) {
return TOO_MANY_CONTEXTS;
}
auto record =
std::make_unique<Context>(Context {width, height, use_shared ? g_shared.get() : nullptr});
auto* handle = record.get();
g_contexts.emplace(handle, std::move(record));
if (handle->shared != nullptr) {
++handle->shared->contexts;
}
*out = handle;
return OK;
}
static int KYTY_SYSV_ABI PsmlContextFinalize(void* context) {
PRINT_NAME();
int KYTY_SYSV_ABI CreateFixedContext(void** out, const MemoryParameters* params) {
const bool large = params != nullptr && params->profile == 1;
return CreateContext(out, params, large ? 7680 : 3840, large ? 4320 : 2160, nullptr, false);
}
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI CreatePrivateContext(void** out, const ContextParameters* params) {
return CreateContext(out, params == nullptr ? nullptr : &params->memory,
params == nullptr ? 0 : params->width,
params == nullptr ? 0 : params->height, nullptr, false);
}
int KYTY_SYSV_ABI CreateSharedContext(void** out, const ContextParameters* params) {
return CreateContext(
out, params == nullptr ? nullptr : &params->memory, params == nullptr ? 0 : params->width,
params == nullptr ? 0 : params->height, params == nullptr ? nullptr : params->shared, true);
}
int KYTY_SYSV_ABI ReleaseContext(void* context) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (!HasKnownObjectMagic(context)) {
return PSML_ERROR_INVALID_OBJECT;
auto& record = g_contexts.at(context);
if (record->shared != nullptr) {
--record->shared->contexts;
}
*static_cast<uint32_t*>(context) = 0;
g_contexts.erase(context);
return OK;
}
static int KYTY_SYSV_ABI PsmlGetWorkAreaSize(const void* object, uint32_t* out_size) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
int KYTY_SYSV_ABI GetPacketSize(const void* context, uint32_t* out_size) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (out_size == nullptr) {
return PSML_ERROR_INVALID_POINTER;
return ARGUMENT_NULL;
}
*out_size = 0x600;
*out_size = PACKET_DWORDS;
return OK;
}
static int KYTY_SYSV_ABI PsmlDispatch(void* context, uint64_t command, uint64_t params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI Dispatch(const void* context, const CommandSpace* space, const void* params) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (!HasKnownObjectMagic(context)) {
return PSML_ERROR_INVALID_OBJECT;
if (space == nullptr || params == nullptr || space->cmd == nullptr) {
return ARGUMENT_NULL;
}
if (command == 0 || params == 0) {
return PSML_ERROR_INVALID_POINTER;
if (space->dwords < PACKET_DWORDS) {
return COMMAND_BUFFER_FULL;
}
if (space->dwords > 0x4001) {
return COMMAND_BUFFER_BAD;
}
const uint32_t* descriptors[2];
std::memcpy(descriptors, params, sizeof(descriptors));
if (descriptors[0] == nullptr || descriptors[1] == nullptr) {
return TEXTURE_NULL;
}
Graphics::Pssr::Request request {};
std::memcpy(request.output, descriptors[0], sizeof(request.output));
std::memcpy(request.input, descriptors[1], sizeof(request.input));
std::memcpy(&request.width, static_cast<const uint8_t*>(params) + 0xf0, sizeof(uint32_t) * 2);
Graphics::ShaderTextureResource input, output;
std::memcpy(input.fields, request.input, sizeof(input.fields));
std::memcpy(output.fields, request.output, sizeof(output.fields));
namespace Gpu = Graphics::Prospero;
const auto is_2d = [](const Graphics::ShaderTextureResource& descriptor) {
return !descriptor.IsNull() &&
(descriptor.Type() == Gpu::ImageType::kColor2D ||
descriptor.Type() == Gpu::ImageType::kColor2DArray) &&
descriptor.TileMode() != Gpu::TileMode::kDepth &&
descriptor.BaseLevel() <= descriptor.LastLevel() &&
descriptor.LastLevel() <= descriptor.MaxMip() &&
descriptor.BaseArray5() <= descriptor.Depth();
};
if (!is_2d(input) || (input.Format() != Gpu::BufferFormat::k11_11_10Float &&
input.Format() != Gpu::BufferFormat::k10_11_11Float &&
input.Format() != Gpu::BufferFormat::k16_16_16_16Float)) {
return INVALID_TEXTURE;
}
if (!is_2d(output) || output.Format() != Gpu::BufferFormat::k11_11_10Float ||
output.TileMode() != Gpu::TileMode::kStandard256B || output.BaseLevel() != 0 ||
output.LastLevel() != 0 || output.MaxMip() != 0 || output.BaseArray5() != 0 ||
output.Depth() != 0 || input.Base40() == output.Base40()) {
return INVALID_OUTPUT_TEXTURE;
}
const uint32_t input_width = std::max(1u, (input.Width5() + 1u) >> input.BaseLevel());
const uint32_t input_height = std::max(1u, (input.Height5() + 1u) >> input.BaseLevel());
if (request.width == 0 || request.height == 0 || request.width > input_width ||
request.height > input_height) {
return UNSUPPORTED_RENDER_SIZE;
}
const auto& record = *g_contexts.at(context);
if (uint32_t(output.Width5()) + 1 != record.width ||
uint32_t(output.Height5()) + 1 != record.height) {
return INVALID_OUTPUT_TEXTURE;
}
namespace Pm4 = Graphics::Pm4;
space->cmd[0] = KYTY_PM4(space->dwords, Pm4::IT_NOP, Pm4::R_PSSR);
std::memcpy(space->cmd + 1, &request, sizeof(request));
std::memset(space->cmd + PACKET_DWORDS, 0, (space->dwords - PACKET_DWORDS) * sizeof(uint32_t));
return OK;
}
static int KYTY_SYSV_ABI PsmlGetProgress(const void* object, float* out_progress,
uint32_t /*frame_index*/) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI GetMipmapBias(const void* context, float* bias, uint32_t width, uint32_t height) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
if (bias == nullptr) {
return ARGUMENT_NULL;
}
if (out_progress == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (width == 0 || height == 0) {
return UNSUPPORTED_RENDER_SIZE;
}
*out_progress = 0.0f;
*bias = std::log2(float(width) / float(g_contexts.at(context)->width)) - 1.0f;
return OK;
}
static int KYTY_SYSV_ABI PsmlRequestCapture(const void* object) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
}
return OK;
int KYTY_SYSV_ABI ValidateContext(const void* context) {
std::lock_guard lock(g_mutex);
return CheckContext(context);
}
static int KYTY_SYSV_ABI PsmlValidateObject(const void* object) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
}
return OK;
}
} // namespace Psml
} // namespace
LIB_DEFINE(InitPsml_1) {
LIB_FUNC("3WVD91e12ZQ", Psml::PsmlInitialize);
LIB_FUNC("+2KpvixvL6E", Psml::PsmlGetMainMemoryRequirements);
LIB_FUNC("eWoKNeB6V-k", Psml::PsmlSharedResourcesInitialize);
LIB_FUNC("jEevBXmagOQ", Psml::PsmlSharedResourcesFinalize);
LIB_FUNC("EO9YQXmJEN8", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("IMlj247LdTo", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("sjTyYheKTrU", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("ArakEpzsZo0", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("VGjrQa-WqdU", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("2ecEbQaf9VU", Psml::PsmlContextInitialize);
LIB_FUNC("vUk2pWMx3KQ", Psml::PsmlContextInitialize);
LIB_FUNC("gxv3i+MTEzU", Psml::PsmlContextInitialize);
LIB_FUNC("JaLBe0P3jSU", Psml::PsmlContextFinalize);
LIB_FUNC("AHalTX9wFZY", Psml::PsmlGetWorkAreaSize);
LIB_FUNC("RUNLFro+qok", Psml::PsmlDispatch);
LIB_FUNC("GHna9-DvnUk", Psml::PsmlGetProgress);
LIB_FUNC("GJY0MvuTcs8", Psml::PsmlRequestCapture);
LIB_FUNC("LXq+6mIxpCw", Psml::PsmlValidateObject);
LIB_FUNC("FSGaTQze0UY", Psml::PsmlValidateObject);
LIB_FUNC("3WVD91e12ZQ", Initialize);
LIB_FUNC("+2KpvixvL6E", GetSharedRequirements);
LIB_FUNC("eWoKNeB6V-k", CreateSharedResources);
LIB_FUNC("jEevBXmagOQ", ReleaseSharedResources);
LIB_FUNC("EO9YQXmJEN8", GetContextRequirements);
LIB_FUNC("IMlj247LdTo", GetContextRequirements);
LIB_FUNC("sjTyYheKTrU", GetContextRequirements);
LIB_FUNC("ArakEpzsZo0", GetContextRequirements);
LIB_FUNC("VGjrQa-WqdU", GetContextRequirements);
LIB_FUNC("2ecEbQaf9VU", CreateFixedContext);
LIB_FUNC("vUk2pWMx3KQ", CreatePrivateContext);
LIB_FUNC("gxv3i+MTEzU", CreateSharedContext);
LIB_FUNC("JaLBe0P3jSU", ReleaseContext);
LIB_FUNC("AHalTX9wFZY", GetPacketSize);
LIB_FUNC("RUNLFro+qok", Dispatch);
LIB_FUNC("GHna9-DvnUk", GetMipmapBias);
LIB_FUNC("GJY0MvuTcs8", ValidateContext);
LIB_FUNC("LXq+6mIxpCw", ValidateContext);
LIB_FUNC("FSGaTQze0UY", ValidateContext);
}
} // namespace LibPsml
} // namespace Libs
} // namespace Libs::LibPsml
+14
View File
@@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/singleton.h"
@@ -90,6 +91,7 @@ private:
};
struct SystemContent {
bool trinity_mode = false;
std::filesystem::path psf_path;
Psf psf;
std::filesystem::path playgo_path;
@@ -603,6 +605,14 @@ void SystemContentLoadParamSfo(const std::filesystem::path& file_name) {
sc->psf_path = file_name;
sc->psf.DbgPrint();
int32_t attribute3 = 0;
const bool title_supports_trinity = sc->psf.GetParamInt("ATTRIBUTE3", &attribute3) &&
(static_cast<uint32_t>(attribute3) & (1u << 22u)) != 0;
sc->trinity_mode = Config::Ps5ProEnabled() && title_supports_trinity;
if (Config::Ps5ProEnabled()) {
Log::WriteToConsoleAndLog(fmt::format("[PS5] title Trinity support={}\n",
title_supports_trinity ? "yes" : "no"));
}
sc->icon_path = file_name.parent_path() / "icon0.png";
@@ -626,6 +636,10 @@ void SystemContentLoadParamSfo(const std::filesystem::path& file_name) {
}
}
bool SystemContentIsTrinityMode() {
return Common::Singleton<SystemContent>::Instance()->trinity_mode;
}
bool SystemContentParamSfoGetInt(const char* name, int32_t* value) {
if (name == nullptr || value == nullptr) {
return false;
+1
View File
@@ -11,6 +11,7 @@
namespace Loader {
void SystemContentLoadParamSfo(const std::filesystem::path& file_name);
bool SystemContentIsTrinityMode();
bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
bool SystemContentParamSfoGetString(const char* name, std::string* value);
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
+3
View File
@@ -74,6 +74,8 @@ static void PrintUsage() {
" --stretch Stretch the image to the window. Default: off.\n");
::printf(" --no-splash Disable the startup splash screen.\n");
::printf(" --vr Enable the virtual VR headset.\n");
::printf(" --trinity Enable experimental PS5 Pro mode for supported titles\n"
" (PSSR uses linear scaling). Default: off.\n");
::printf(" --amd-cpu Apply AMD CPU instruction patches.\n");
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
@@ -204,6 +206,7 @@ static constexpr Flag FLAGS[] = {
{"--stretch", &ConfigOptions::stretch_enabled},
{"--no-splash", &ConfigOptions::splash_enabled, false},
{"--vr", &ConfigOptions::vr_enabled},
{"--trinity", &ConfigOptions::pro_enabled},
{"--amd-cpu", &ConfigOptions::amd_cpu_enabled},
{"--network-connected", &ConfigOptions::network_connected},
{"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled},