From 651ef73a0354fe391e8951ff104da1b162e636d6 Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 29 Oct 2026 06:37:55 +0100 Subject: [PATCH] graphics: add experimental Pro mode and linear PSSR Co-authored-by: Joshua Jayasingh --- CMakeLists.txt | 18 + src/common/emulatorConfig.cpp | 4 + src/common/emulatorConfig.h | 2 + .../command_processor/commandProcessor.h | 2 + .../command_processor/pm4Dispatch.cpp | 1 + .../guest_gpu/command_processor/pm4Dispatch.h | 1 + .../command_processor/pm4Handlers.cpp | 11 + src/graphics/guest_gpu/graphicsRun.cpp | 5 + src/graphics/guest_gpu/pm4.h | 1 + src/graphics/guest_gpu/pssr.h | 20 + src/graphics/host_gpu/renderer/render.h | 9 + src/graphics/host_gpu/renderer/renderPssr.cpp | 184 ++++++ .../host_gpu/shaders/gpu_pssr_linear.comp | 33 ++ src/kernel/memory.cpp | 6 +- .../forms/configuration_edit_dialog.ui | 6 + src/launcher/include/configuration.h | 2 + src/launcher/src/configuration.cpp | 2 + src/launcher/src/configurationEditDialog.cpp | 2 + src/launcher/src/mainDialog.cpp | 1 + src/libs/agc.cpp | 5 +- src/libs/libKernel.cpp | 16 +- src/libs/libPsml.cpp | 539 +++++++++--------- src/loader/systemContent.cpp | 14 + src/loader/systemContent.h | 1 + src/main.cpp | 3 + tests/PsmlTests.cpp | 346 +++++++++++ tests/PssrGpuTests.inc | 228 ++++++++ tests/ShaderRecompilerComputeTests.cpp | 22 +- tests/TrinityTests.cpp | 129 +++++ tests/VirtualMemoryAllocationTests.cpp | 5 + 30 files changed, 1340 insertions(+), 278 deletions(-) create mode 100644 src/graphics/guest_gpu/pssr.h create mode 100644 src/graphics/host_gpu/renderer/renderPssr.cpp create mode 100644 src/graphics/host_gpu/shaders/gpu_pssr_linear.comp create mode 100644 tests/PsmlTests.cpp create mode 100644 tests/PssrGpuTests.inc create mode 100644 tests/TrinityTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b1cfa43e..c2aa8dbf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -325,6 +325,7 @@ add_gpu_blit_shader(gpu_blit_fs_triangle gpu_blit_fs_triangle.vert) add_gpu_blit_shader(gpu_blit_color_to_ms_depth gpu_blit_color_to_ms_depth.frag) add_gpu_blit_shader(gpu_video_out_overlay gpu_video_out_overlay.frag) add_gpu_blit_shader(gpu_imgui_hdr gpu_video_out_overlay.frag -DIMGUI_HDR=1) +add_gpu_blit_shader(gpu_pssr_linear gpu_pssr_linear.comp) list(APPEND kyty_emulator_src ${gpu_blit_shader_headers}) list(APPEND kyty_emulator_src @@ -631,6 +632,19 @@ target_sources(shader_recompiler_compute_tests PRIVATE "${gpu_test_ms_depth_header}") add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp") +add_library(kyty_test_gtest STATIC EXCLUDE_FROM_ALL + "${KYTY_THIRD_PARTY_DIR}/fmt/test/gtest/gmock-gtest-all.cc") +target_include_directories(kyty_test_gtest PUBLIC "${KYTY_THIRD_PARTY_DIR}/fmt/test/gtest") +target_sources(virtual_memory_allocation_tests PRIVATE "${KYTY_TESTS_DIR}/TrinityTests.cpp") +target_link_libraries(virtual_memory_allocation_tests kyty_test_gtest) + +add_executable(psml_tests EXCLUDE_FROM_ALL + "${KYTY_TESTS_DIR}/PsmlTests.cpp" + "${KYTY_SOURCE_DIR}/libs/libPsml.cpp" + "${KYTY_SOURCE_DIR}/loader/symbolDatabase.cpp" + "${KYTY_SOURCE_DIR}/loader/timer.cpp") +target_link_libraries(psml_tests common kyty_test_gtest) +target_include_directories(psml_tests PRIVATE ${inc_headers}) target_compile_definitions(virtual_memory_allocation_tests PRIVATE KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1) @@ -710,6 +724,10 @@ if(BUILD_TESTING) COMMAND $ --draw-offset-only) add_test(NAME virtual_memory_allocation COMMAND $) + add_test(NAME trinity_mode + COMMAND $ --trinity-only) + add_test(NAME psml COMMAND $) + add_test(NAME pssr COMMAND $ --pssr-only) add_test(NAME guest_red_zone_patcher COMMAND $ --red-zone-patcher-only) add_test(NAME command_scheduler_timeline diff --git a/src/common/emulatorConfig.cpp b/src/common/emulatorConfig.cpp index 09c8711dd..78b8a88d8 100644 --- a/src/common/emulatorConfig.cpp +++ b/src/common/emulatorConfig.cpp @@ -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; } diff --git a/src/common/emulatorConfig.h b/src/common/emulatorConfig.h index 47a6b9ab6..9eea69728 100644 --- a/src/common/emulatorConfig.h +++ b/src/common/emulatorConfig.h @@ -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(); diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index dacc3eeb9..e4b073c50 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -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); diff --git a/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp b/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp index fec9b6de9..93657d3fb 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Dispatch.cpp @@ -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; } diff --git a/src/graphics/guest_gpu/command_processor/pm4Dispatch.h b/src/graphics/guest_gpu/command_processor/pm4Dispatch.h index 8efd99be2..b50d2e1e6 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Dispatch.h +++ b/src/graphics/guest_gpu/command_processor/pm4Dispatch.h @@ -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 diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index 42aadd569..f6f206c78 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -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(); diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 6099ec383..9bebff3e8 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -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) { diff --git a/src/graphics/guest_gpu/pm4.h b/src/graphics/guest_gpu/pm4.h index 7e7a506a9..e97c7061f 100644 --- a/src/graphics/guest_gpu/pm4.h +++ b/src/graphics/guest_gpu/pm4.h @@ -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; diff --git a/src/graphics/guest_gpu/pssr.h b/src/graphics/guest_gpu/pssr.h new file mode 100644 index 000000000..7f6a6c896 --- /dev/null +++ b/src/graphics/guest_gpu/pssr.h @@ -0,0 +1,20 @@ +#ifndef EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_ +#define EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_ + +#include + +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_ diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index 49462a9c4..52bf8d9fb 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -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 stages); @@ -219,6 +225,9 @@ private: std::vector m_bound_images; std::vector m_descriptor_images; std::vector 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; diff --git a/src/graphics/host_gpu/renderer/renderPssr.cpp b/src/graphics/host_gpu/renderer/renderPssr.cpp new file mode 100644 index 000000000..60cd3609f --- /dev/null +++ b/src/graphics/host_gpu/renderer/renderPssr.cpp @@ -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 +#include + +namespace Libs::Graphics { +namespace { + +struct PssrParameters { + uint32_t input_size[2]; + uint32_t output_size[2]; + uint32_t output_channels[4]; +}; + +bool IsInputFormat(Prospero::BufferFormat format) { + return format == Prospero::BufferFormat::k11_11_10Float || + format == Prospero::BufferFormat::k10_11_11Float || + format == Prospero::BufferFormat::k16_16_16_16Float; +} + +} // namespace + +RenderExecutor::~RenderExecutor() { + const auto device = m_context.GetGraphics().device; + device.destroyPipeline(m_pssr_pipeline, nullptr); + device.destroyPipelineLayout(m_pssr_pipeline_layout, nullptr); + device.destroyDescriptorSetLayout(m_pssr_descriptor_layout, nullptr); +} + +void RenderExecutor::ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request) { + EXIT_IF(buffer.IsInvalid()); + m_context.GetCommandScheduler().PopPendingOperations(); + Common::LockGuard lock(m_context.GetMutex()); + ShaderTextureResource input_descriptor {}, output_descriptor {}; + std::copy_n(request.input, 8, input_descriptor.fields); + std::copy_n(request.output, 8, output_descriptor.fields); + const auto is_2d = [](const ShaderTextureResource& descriptor) { + return !descriptor.IsNull() && + (descriptor.Type() == Prospero::ImageType::kColor2D || + descriptor.Type() == Prospero::ImageType::kColor2DArray) && + descriptor.TileMode() != Prospero::TileMode::kDepth; + }; + EXIT_IF(!is_2d(input_descriptor) || !is_2d(output_descriptor) || + !IsInputFormat(input_descriptor.Format()) || + output_descriptor.Format() != Prospero::BufferFormat::k11_11_10Float || + output_descriptor.TileMode() != Prospero::TileMode::kStandard256B || + output_descriptor.BaseLevel() != 0 || output_descriptor.LastLevel() != 0 || + output_descriptor.MaxMip() != 0 || output_descriptor.BaseArray5() != 0 || + output_descriptor.Depth() != 0 || request.width == 0 || request.height == 0); + const auto input_width = std::max(1u, (input_descriptor.Width5() + 1u) >> + input_descriptor.BaseLevel()); + const auto input_height = std::max(1u, (input_descriptor.Height5() + 1u) >> + input_descriptor.BaseLevel()); + EXIT_IF(request.width > input_width || request.height > input_height); + PssrParameters parameters {{request.width, request.height}, + {output_descriptor.Width5() + 1u, output_descriptor.Height5() + 1u}, + {4, 4, 4, 4}}; + for (uint32_t component = 0; component < 3; ++component) { + for (uint32_t source = 0; source < 4; ++source) { + if (GetDstSel(output_descriptor.DstSelXYZW(), source) == 4u + component) { + parameters.output_channels[component] = source; + break; + } + } + } + const auto resolve = [&](const uint32_t* words, bool output) { + ShaderRecompiler::IR::ImageResource resource {}; + resource.resource_class = output ? ShaderRecompiler::IR::ImageResourceClass::Storage + : ShaderRecompiler::IR::ImageResourceClass::Sampled; + resource.numeric_class = Prospero::TextureNumericClass::Float; + resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + resource.read = !output; + resource.written = output; + ShaderRecompiler::IR::DescriptorValue value {}; + std::copy_n(words, 8, value.dwords.begin()); + value.dword_count = 8; + auto binding = ResolveTexture(resource, value); + binding.desc.view_info.level_count = 1; + binding.desc.view_info.min_lod = 0; + return binding; + }; + auto& cache = m_context.GetTextureCache(); + auto input = resolve(request.input, false); + BindImage(input.image_id, false); + auto output = resolve(request.output, true); + BindImage(output.image_id, true); + EXIT_IF(input.desc.info.data.address < output.desc.info.data.End() && + output.desc.info.data.address < input.desc.info.data.End()); + const auto acquire = [&](TextureBinding& binding, const uint32_t* words, bool storage) { + auto& previous = cache.GetImage(binding.image_id); + if (!previous.registered || previous.binding.needs_rebind) { + previous.binding = {}; + binding = resolve(words, storage); + BindImage(binding.image_id, storage); + } + binding.image_view = cache.FindTexture(binding.image_id, binding.desc); + }; + acquire(input, request.input, false); + acquire(output, request.output, true); + auto& source = cache.GetImage(input.image_id); + auto& destination = cache.GetImage(output.image_id); + const auto& source_view = input.desc.view_info; + const auto& destination_view = output.desc.view_info; + EXIT_IF(source.info.IsDepth() || destination.info.IsDepth() || + source.backing.samples != 1 || destination.backing.samples != 1 || + (input.image_id == output.image_id && + source_view.base_level == destination_view.base_level && + source_view.base_layer == destination_view.base_layer)); + + auto& graphics = m_context.GetGraphics(); + if (m_pssr_pipeline == nullptr) { + const std::array bindings { + vk::DescriptorSetLayoutBinding {0, vk::DescriptorType::eSampledImage, 1, + vk::ShaderStageFlagBits::eCompute}, + vk::DescriptorSetLayoutBinding {1, vk::DescriptorType::eStorageImage, 1, + vk::ShaderStageFlagBits::eCompute}}; + vk::DescriptorSetLayoutCreateInfo descriptor_info {}; + descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR; + descriptor_info.bindingCount = bindings.size(); + descriptor_info.pBindings = bindings.data(); + RequireVulkanSuccess(graphics.device.createDescriptorSetLayout( + &descriptor_info, nullptr, &m_pssr_descriptor_layout), + "create upscaling descriptor layout"); + const vk::PushConstantRange push {vk::ShaderStageFlagBits::eCompute, 0, + sizeof(PssrParameters)}; + vk::PipelineLayoutCreateInfo layout_info {}; + layout_info.setLayoutCount = 1; + layout_info.pSetLayouts = &m_pssr_descriptor_layout; + layout_info.pushConstantRangeCount = 1; + layout_info.pPushConstantRanges = &push; + RequireVulkanSuccess(graphics.device.createPipelineLayout( + &layout_info, nullptr, &m_pssr_pipeline_layout), + "create upscaling pipeline layout"); + const auto shader = CompileSPV(GPU_PSSR_LINEAR_SPV, graphics.device); + vk::ComputePipelineCreateInfo create {}; + create.stage.stage = vk::ShaderStageFlagBits::eCompute; + create.stage.module = shader; + create.stage.pName = "main"; + create.layout = m_pssr_pipeline_layout; + const auto result = graphics.device.createComputePipelines( + nullptr, 1, &create, nullptr, &m_pssr_pipeline); + graphics.device.destroyShaderModule(shader, nullptr); + RequireVulkanSuccess(result, "create upscaling pipeline"); + } + buffer.EndRendering(); + const auto command = buffer.Handle(); + source.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead, + ImageSubresourceRange {source_view.base_level, 1, source_view.base_layer, 1}, + command); + destination.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite, + ImageSubresourceRange {destination_view.base_level, 1, + destination_view.base_layer, 1}, command); + const std::array images { + vk::DescriptorImageInfo {nullptr, input.image_view, vk::ImageLayout::eGeneral}, + vk::DescriptorImageInfo {nullptr, output.image_view, vk::ImageLayout::eGeneral}}; + std::array writes {}; + for (uint32_t i = 0; i < writes.size(); ++i) { + writes[i].dstBinding = i; + writes[i].descriptorCount = 1; + writes[i].descriptorType = i == 0 ? vk::DescriptorType::eSampledImage + : vk::DescriptorType::eStorageImage; + writes[i].pImageInfo = &images[i]; + } + command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pssr_pipeline_layout, + 0, writes.size(), writes.data()); + command.bindPipeline(vk::PipelineBindPoint::eCompute, m_pssr_pipeline); + command.pushConstants(m_pssr_pipeline_layout, vk::ShaderStageFlagBits::eCompute, + 0, sizeof(parameters), ¶meters); + command.dispatch((parameters.output_size[0] + 7) / 8, + (parameters.output_size[1] + 7) / 8, 1); + ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader); + ResetBindings(); +} + +} // namespace Libs::Graphics diff --git a/src/graphics/host_gpu/shaders/gpu_pssr_linear.comp b/src/graphics/host_gpu/shaders/gpu_pssr_linear.comp new file mode 100644 index 000000000..dfd7d6d96 --- /dev/null +++ b/src/graphics/host_gpu/shaders/gpu_pssr_linear.comp @@ -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); +} diff --git a/src/kernel/memory.cpp b/src/kernel/memory.cpp index c1249bf6d..598697e18 100644 --- a/src/kernel/memory.cpp +++ b/src/kernel/memory.cpp @@ -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)); diff --git a/src/launcher/forms/configuration_edit_dialog.ui b/src/launcher/forms/configuration_edit_dialog.ui index d10bc44dc..7db4d6200 100644 --- a/src/launcher/forms/configuration_edit_dialog.ui +++ b/src/launcher/forms/configuration_edit_dialog.ui @@ -254,6 +254,12 @@ General + + + Enable experimental Pro mode for supported titles. PSSR uses linear scaling. Disabled by default. + Pro mode (experimental) + + Run in borderless desktop fullscreen diff --git a/src/launcher/include/configuration.h b/src/launcher/include/configuration.h index 3ab86bc10..dc9d84042 100644 --- a/src/launcher/include/configuration.h +++ b/src/launcher/include/configuration.h @@ -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; diff --git a/src/launcher/src/configuration.cpp b/src/launcher/src/configuration.cpp index ad07c12d6..520b5c56f 100644 --- a/src/launcher/src/configuration.cpp +++ b/src/launcher/src/configuration.cpp @@ -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 = diff --git a/src/launcher/src/configurationEditDialog.cpp b/src/launcher/src/configurationEditDialog.cpp index c9de0ee79..1faf54681 100644 --- a/src/launcher/src/configurationEditDialog.cpp +++ b/src/launcher/src/configurationEditDialog.cpp @@ -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(); diff --git a/src/launcher/src/mainDialog.cpp b/src/launcher/src/mainDialog.cpp index 21d0af992..f0df182de 100644 --- a/src/launcher/src/mainDialog.cpp +++ b/src/launcher/src/mainDialog.cpp @@ -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"); diff --git a/src/libs/agc.cpp b/src/libs/agc.cpp index c3aa0e58d..ea2a363ab 100644 --- a/src/libs/agc.cpp +++ b/src/libs/agc.cpp @@ -24,6 +24,7 @@ #include "kernel/pthread.h" #include "libs/errno.h" #include "libs/libs.h" +#include "loader/systemContent.h" #include #include @@ -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) { diff --git a/src/libs/libKernel.cpp b/src/libs/libKernel.cpp index 81b9ca797..c6217b9ca 100644 --- a/src/libs/libKernel.cpp +++ b/src/libs/libKernel.cpp @@ -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 @@ -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; } diff --git a/src/libs/libPsml.cpp b/src/libs/libPsml.cpp index 8f642435c..1f610b2f8 100644 --- a/src/libs/libPsml.cpp +++ b/src/libs/libPsml.cpp @@ -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 +#include +#include +#include #include +#include +#include +#include +#include -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(0x8a810001u); -constexpr int PSML_ERROR_INVALID_OBJECT = static_cast(0x8a810005u); -constexpr int PSML_ERROR_INVALID_POINTER = static_cast(0x8a810009u); -constexpr int PSML_ERROR_INVALID_VALUE = static_cast(0x8a81000du); -constexpr int PSML_ERROR_NULL_OBJECT = static_cast(0x8a810014u); +constexpr int NOT_INITIALIZED = static_cast(0x8a810001u); +constexpr int ALREADY_INITIALIZED = static_cast(0x8a810002u); +constexpr int TOO_MANY_CONTEXTS = static_cast(0x8a810004u); +constexpr int INVALID_CONTEXT = static_cast(0x8a810005u); +constexpr int COMMAND_BUFFER_FULL = static_cast(0x8a810006u); +constexpr int COMMAND_BUFFER_BAD = static_cast(0x8a810007u); +constexpr int INSUFFICIENT_BUFFER = static_cast(0x8a810008u); +constexpr int ARGUMENT_NULL = static_cast(0x8a810009u); +constexpr int TEXTURE_NULL = static_cast(0x8a81000au); +constexpr int INVALID_TEXTURE = static_cast(0x8a81000bu); +constexpr int INVALID_PARAMETER = static_cast(0x8a81000du); +constexpr int UNSUPPORTED_RENDER_SIZE = static_cast(0x8a81000eu); +constexpr int INVALID_OUTPUT_TEXTURE = static_cast(0x8a81000fu); +constexpr int NOT_TRINITY_MODE = static_cast(0x8a810012u); +constexpr int UNSUPPORTED_OUTPUT_SIZE = static_cast(0x8a810013u); +constexpr int INVALID_SHARED_RESOURCES = static_cast(0x8a810014u); +constexpr int TOO_MANY_SHARED_RESOURCES = static_cast(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 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 g_shared; +std::unordered_map> 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(resources); - - *reinterpret_cast(bytes) = SHARED_RESOURCES_MAGIC; - *reinterpret_cast(bytes + 0x08) = params.blocks; - *reinterpret_cast(bytes + 0x18) = params.block_count; - *reinterpret_cast(bytes + 0x20) = params.type; - *reinterpret_cast(bytes + 0x28) = params.virtual_address_start; -} - -static bool HasKnownObjectMagic(const void* object) { - if (object == nullptr) { - return false; - } - const auto magic = *static_cast(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(); + *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(context); - auto* params_bytes = static_cast(params); - auto* shared_resources = *reinterpret_cast(params_bytes + 0x08); - if (shared_resources == nullptr) { - return PSML_ERROR_INVALID_POINTER; + if (memory->profile > 2) { + return INVALID_PARAMETER; } - - *reinterpret_cast(bytes) = CONTEXT_MAGIC; - *reinterpret_cast(bytes + 0x360) = shared_resources; - *reinterpret_cast(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 {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 : ¶ms->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 : ¶ms->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(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(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 diff --git a/src/loader/systemContent.cpp b/src/loader/systemContent.cpp index db1812702..3c1a691bb 100644 --- a/src/loader/systemContent.cpp +++ b/src/loader/systemContent.cpp @@ -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(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::Instance()->trinity_mode; +} + bool SystemContentParamSfoGetInt(const char* name, int32_t* value) { if (name == nullptr || value == nullptr) { return false; diff --git a/src/loader/systemContent.h b/src/loader/systemContent.h index 7407246e2..610fbe8ea 100644 --- a/src/loader/systemContent.h +++ b/src/loader/systemContent.h @@ -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); diff --git a/src/main.cpp b/src/main.cpp index 87cab9934..6c7d3cda2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 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}, diff --git a/tests/PsmlTests.cpp b/tests/PsmlTests.cpp new file mode 100644 index 000000000..74c14416e --- /dev/null +++ b/tests/PsmlTests.cpp @@ -0,0 +1,346 @@ +#include "common/abi.h" +#include "graphics/guest_gpu/pm4.h" +#include "graphics/guest_gpu/pssr.h" +#include "loader/symbolDatabase.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace Libs::LibPsml { +void InitPsml_1(Loader::SymbolDatabase *symbols); +} + +namespace { +bool g_trinity = true; +} + +namespace Loader { +bool SystemContentIsTrinityMode() { return g_trinity; } +} // namespace Loader + +namespace { + +namespace Pm4 = Libs::Graphics::Pm4; +namespace Pssr = Libs::Graphics::Pssr; + +constexpr int ERROR_ALREADY_INITIALIZED = static_cast(0x8a810002u); +constexpr int ERROR_TOO_MANY_CONTEXTS = static_cast(0x8a810004u); +constexpr int ERROR_INVALID_CONTEXT = static_cast(0x8a810005u); +constexpr int ERROR_NO_SPACE = static_cast(0x8a810006u); +constexpr int ERROR_BAD_COMMAND_BUFFER = static_cast(0x8a810007u); +constexpr int ERROR_NULL_ARGUMENT = static_cast(0x8a810009u); +constexpr int ERROR_NULL_TEXTURE = static_cast(0x8a81000au); +constexpr int ERROR_INVALID_TEXTURE = static_cast(0x8a81000bu); +constexpr int ERROR_RENDER_SIZE = static_cast(0x8a81000eu); +constexpr int ERROR_OUTPUT_TEXTURE = static_cast(0x8a81000fu); +constexpr int ERROR_NOT_TRINITY = static_cast(0x8a810012u); +constexpr int ERROR_INVALID_SHARED = static_cast(0x8a810014u); +constexpr uint64_t CANARY = 0xfedcba9876543210; + +struct MemoryBlock { + uint64_t offset = 0; + uint64_t size = 0x200000; +}; + +struct SharedParameters { + uint32_t profile = 0; + uint32_t reserved = 0; + const MemoryBlock *blocks = nullptr; + uint64_t block_count = 0; + uint64_t virtual_address = 0; +}; + +struct ContextParameters { + uint32_t profile = 0; + uint32_t reserved = 0; + const MemoryBlock *blocks = nullptr; + uint64_t block_count = 0; + uint16_t width = 960; + uint16_t height = 540; + uint32_t reserved1 = 0; + const void *depth_copy = nullptr; + const void *motion_copy = nullptr; + const char *name = nullptr; + void *shared = nullptr; +}; +static_assert(offsetof(ContextParameters, shared) == 0x38); + +struct CommandSpace { + uint32_t *cmd; + uint32_t dwords; +}; + +struct DispatchParameters { + const uint32_t *output; + const uint32_t *input; + std::array unused{}; + uint32_t width; + uint32_t height; +}; +static_assert(offsetof(DispatchParameters, width) == 0xf0); + +struct GuardedHandle { + uint64_t before = CANARY; + void *value = nullptr; + std::array after; + + GuardedHandle() { after.fill(CANARY); } + + bool Intact() const { + return before == CANARY && + std::all_of(after.begin(), after.end(), + [](uint64_t value) { return value == CANARY; }); + } +}; + +using Initialize = int(KYTY_SYSV_ABI *)(); +using CreateShared = int(KYTY_SYSV_ABI *)(void **, const SharedParameters *); +using CreateContext = int(KYTY_SYSV_ABI *)(void **, const ContextParameters *); +using Release = int(KYTY_SYSV_ABI *)(void *); +using GetSize = int(KYTY_SYSV_ABI *)(const void *, uint32_t *); +using Dispatch = int(KYTY_SYSV_ABI *)(const void *, const CommandSpace *, + const void *); +using GetBias = int(KYTY_SYSV_ABI *)(const void *, float *, uint32_t, uint32_t); +using Predicate = int(KYTY_SYSV_ABI *)(const void *); + +Loader::SymbolDatabase g_symbols; + +template T Function(const char *nid) { + const auto *symbol = g_symbols.FindByNid(nid, Loader::SymbolType::Func); + EXPECT_NE(symbol, nullptr); + return symbol == nullptr ? nullptr : reinterpret_cast(symbol->vaddr); +} + +std::array Texture(uint32_t width, uint32_t height, + uint32_t address) { + std::array result{}; + result[0] = address; + result[1] = (((width - 1) & 3) << 30) | (36u << 20); + result[2] = ((width - 1) >> 2) | ((height - 1) << 14); + result[3] = (9u << 28) | (1u << 20) | 0xfacu; + return result; +} + +class PsmlTest : public testing::Test { +protected: + static void SetUpTestSuite() { + Libs::LibPsml::InitPsml_1(&g_symbols); + g_trinity = false; + ASSERT_EQ(Function("3WVD91e12ZQ")(), ERROR_NOT_TRINITY); + g_trinity = true; + ASSERT_EQ(Function("3WVD91e12ZQ")(), 0); + } + + void SetUp() override { + SharedParameters params; + params.blocks = blocks.data(); + params.block_count = blocks.size(); + GuardedHandle result; + ASSERT_EQ(Function("eWoKNeB6V-k")(&result.value, ¶ms), 0); + ASSERT_TRUE(result.Intact()); + ASSERT_GT(reinterpret_cast(result.value), 0x10000u); + shared = result.value; + + context_params.blocks = blocks.data(); + context_params.block_count = 1; + context_params.shared = shared; + result.value = nullptr; + ASSERT_EQ( + Function("gxv3i+MTEzU")(&result.value, &context_params), + 0); + ASSERT_TRUE(result.Intact()); + ASSERT_GT(reinterpret_cast(result.value), 0x10000u); + context = result.value; + } + + void TearDown() override { + for (auto *extra : extra_contexts) { + EXPECT_EQ(Function("JaLBe0P3jSU")(extra), 0); + } + if (context != nullptr) { + EXPECT_EQ(Function("JaLBe0P3jSU")(context), 0); + } + if (shared != nullptr) { + EXPECT_EQ(Function("jEevBXmagOQ")(shared), 0); + } + } + + std::array blocks; + ContextParameters context_params; + void *shared = nullptr; + void *context = nullptr; + std::vector extra_contexts; +}; + +TEST_F(PsmlTest, HandlesPreserveAdjacentMemoryAndRejectReleasedObjects) { + EXPECT_EQ(Function("3WVD91e12ZQ")(), ERROR_ALREADY_INITIALIZED); + uint32_t size = 0; + EXPECT_EQ(Function("AHalTX9wFZY")(context, &size), 0); + EXPECT_EQ(size, Pssr::RequestDwords + 1); + EXPECT_EQ(Function("jEevBXmagOQ")(shared), ERROR_INVALID_CONTEXT); + EXPECT_EQ(Function("LXq+6mIxpCw")(context), 0); + EXPECT_EQ(Function("JaLBe0P3jSU")(context), 0); + EXPECT_EQ(Function("JaLBe0P3jSU")(context), ERROR_INVALID_CONTEXT); + EXPECT_EQ(Function("AHalTX9wFZY")(context, &size), + ERROR_INVALID_CONTEXT); + EXPECT_EQ(Function("LXq+6mIxpCw")(context), ERROR_INVALID_CONTEXT); + context = nullptr; + EXPECT_EQ(Function("jEevBXmagOQ")(shared), 0); + EXPECT_EQ(Function("jEevBXmagOQ")(shared), ERROR_INVALID_SHARED); + shared = nullptr; + EXPECT_EQ( + Function("AHalTX9wFZY")(reinterpret_cast(1), &size), + ERROR_INVALID_CONTEXT); +} + +TEST_F(PsmlTest, LegacyContextVariantsUseTheirOwnLayoutsAndOutputResolution) { + context_params.shared = reinterpret_cast(1); + GuardedHandle result; + ASSERT_EQ( + Function("gxv3i+MTEzU")(&result.value, &context_params), + ERROR_INVALID_SHARED); + ASSERT_TRUE(result.Intact()); + ASSERT_EQ(result.value, nullptr); + ASSERT_EQ( + Function("vUk2pWMx3KQ")(&result.value, &context_params), + 0); + ASSERT_TRUE(result.Intact()); + extra_contexts.push_back(result.value); + float bias = 0; + EXPECT_EQ(Function("GHna9-DvnUk")(result.value, &bias, 480, 270), 0); + EXPECT_FLOAT_EQ(bias, -2.0f); + ASSERT_EQ( + Function("2ecEbQaf9VU")(&result.value, &context_params), + 0); + ASSERT_TRUE(result.Intact()); + extra_contexts.push_back(result.value); + EXPECT_EQ(Function("GHna9-DvnUk")(result.value, &bias, 1920, 1080), + 0); + EXPECT_FLOAT_EQ(bias, -2.0f); +} + +TEST_F(PsmlTest, CapsLiveContextsAndReleasesCapacity) { + for (int i = 0; i < 3; ++i) { + void *extra = nullptr; + ASSERT_EQ(Function("gxv3i+MTEzU")(&extra, &context_params), + 0); + extra_contexts.push_back(extra); + } + void *fifth = nullptr; + EXPECT_EQ(Function("gxv3i+MTEzU")(&fifth, &context_params), + ERROR_TOO_MANY_CONTEXTS); + EXPECT_EQ(fifth, nullptr); + EXPECT_EQ(Function("JaLBe0P3jSU")(extra_contexts.back()), 0); + extra_contexts.pop_back(); + ASSERT_EQ(Function("gxv3i+MTEzU")(&fifth, &context_params), 0); + extra_contexts.push_back(fifth); +} + +TEST_F(PsmlTest, DispatchSnapshotsFixedSlotsAndActiveExtent) { + auto output = Texture(960, 540, 0x10000); + auto input = Texture(1920, 1080, 0x20000); + DispatchParameters params{output.data(), input.data(), {}, 640, 360}; + std::array command; + command.fill(0xdeadbeef); + const CommandSpace space{command.data() + 1, 28}; + ASSERT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), 0); + EXPECT_EQ(KYTY_PM4_LEN(command[1]), space.dwords); + EXPECT_EQ(KYTY_PM4_R(command[1]), Pm4::R_PSSR); + EXPECT_EQ((command[1] >> 8) & 0xffu, Pm4::IT_NOP); + Pssr::Request request; + std::memcpy(&request, command.data() + 2, sizeof(request)); + EXPECT_EQ(std::memcmp(request.output, output.data(), sizeof(request.output)), + 0); + EXPECT_EQ(std::memcmp(request.input, input.data(), sizeof(request.input)), 0); + EXPECT_EQ(request.width, 640u); + EXPECT_EQ(request.height, 360u); + EXPECT_EQ(command.front(), 0xdeadbeefu); + EXPECT_EQ(command[29], 0xdeadbeefu); + for (size_t i = 2 + Pssr::RequestDwords; i < 29; ++i) { + EXPECT_EQ(command[i], 0u); + } + output.fill(0); + input.fill(0); + params = {}; + Pssr::Request snapshot; + std::memcpy(&snapshot, command.data() + 2, sizeof(snapshot)); + EXPECT_EQ(std::memcmp(&snapshot, &request, sizeof(request)), 0); +} + +TEST_F(PsmlTest, DispatchAcceptsEqualSizedBuffersAndValidatesWithoutWriting) { + auto output = Texture(960, 540, 0x10000); + auto input = Texture(960, 540, 0x20000); + DispatchParameters params{output.data(), input.data(), {}, 960, 540}; + uint32_t dwords = 0; + ASSERT_EQ(Function("AHalTX9wFZY")(context, &dwords), 0); + std::vector command(dwords, 0xdeadbeef); + CommandSpace space{command.data(), dwords}; + ASSERT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), 0); + std::fill(command.begin(), command.end(), 0xdeadbeef); + space.dwords = dwords - 1; + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_NO_SPACE); + space.dwords = 0x4002; + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_BAD_COMMAND_BUFFER); + space.dwords = dwords; + params.width = 961; + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_RENDER_SIZE); + params.width = 960; + output = Texture(960, 539, 0x10000); + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_OUTPUT_TEXTURE); + params.input = nullptr; + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_NULL_TEXTURE); + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, nullptr), + ERROR_NULL_ARGUMENT); + EXPECT_TRUE(std::all_of(command.begin(), command.end(), + [](uint32_t value) { return value == 0xdeadbeef; })); +} + +TEST_F(PsmlTest, DispatchChecksDescriptorContractAndSelectedMip) { + auto output = Texture(960, 540, 0x10000); + auto input = Texture(1920, 1080, 0x20000); + input[3] |= (1u << 12) | (1u << 16); + input[5] |= 1u << 4; + DispatchParameters params{output.data(), input.data(), {}, 960, 540}; + std::array command{}; + const CommandSpace space{command.data(), command.size()}; + ASSERT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), 0); + command.fill(0xdeadbeef); + params.width = 961; + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_RENDER_SIZE); + params.width = 960; + input[1] &= ~(0x1ffu << 20); + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_INVALID_TEXTURE); + input = Texture(960, 540, 0x20000); + input[3] = (input[3] & ~(0xfu << 28)) | (14u << 28); + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_INVALID_TEXTURE); + input = Texture(960, 540, 0x20000); + output[3] &= ~(0x1fu << 20); + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_OUTPUT_TEXTURE); + output = Texture(960, 540, 0x20000); + EXPECT_EQ(Function("RUNLFro+qok")(context, &space, ¶ms), + ERROR_OUTPUT_TEXTURE); + EXPECT_TRUE(std::all_of(command.begin(), command.end(), + [](uint32_t value) { return value == 0xdeadbeef; })); +} + +} // namespace + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/PssrGpuTests.inc b/tests/PssrGpuTests.inc new file mode 100644 index 000000000..eb404df24 --- /dev/null +++ b/tests/PssrGpuTests.inc @@ -0,0 +1,228 @@ + void CheckPssr() { + constexpr const char *name = "PssrLinear"; + constexpr uintptr_t base = 0x0000000206a00000ull; + constexpr uint64_t size = 0x100000; + constexpr auto packed = Prospero::BufferFormat::k11_11_10Float; + constexpr auto linear = Prospero::TileMode::kLinear; + constexpr auto tiled = Prospero::TileMode::kStandard256B; + constexpr u32 red = 0x000003c0, green = 0x001e0000, blue = 0x78000000; + const auto descriptor = [](uint64_t address, u32 width, u32 height, + Prospero::BufferFormat format, Prospero::TileMode tile, + u32 swizzle = DstSel(4, 5, 6, 7)) { + ShaderTextureResource result{}; + result.fields[0] = static_cast(address >> 8); + result.fields[1] = static_cast(address >> 40) | + (static_cast(format) << 20) | (((width - 1) & 3) << 30); + result.fields[2] = ((width - 1) >> 2) | ((height - 1) << 14); + result.fields[3] = swizzle | (static_cast(tile) << 20) | + (static_cast(Prospero::ImageType::kColor2D) << 28); + result.fields[5] = 0x00700000; + return result; + }; + EnsureRuntimeContext(); + GraphicsInitJmpTables(); + int64_t physical = -1; + Require(name, "allocate", LibKernel::Memory::KernelAllocateDirectMemory( + 0, LibKernel::Memory::KernelGetDirectMemorySize(), size, 0x10000, 0, &physical) == 0, + "upscaling allocation failed"); + void *mapped = reinterpret_cast(base); + Require(name, "map", LibKernel::Memory::KernelMapDirectMemory( + &mapped, size, 0x3, 0x10, physical, 0x10000) == 0 && + mapped == reinterpret_cast(base), "upscaling mapping failed"); + std::memset(mapped, 0, size); + RenderContext context(m_runtime_context); + context.InitializeGpu(nullptr); + LibKernel::Memory::InstallGpuResources(&context); + context.GetGpu().SendCommandSync([&] { + CommandProcessor processor(context, 0); + processor.BufferInit(); + context.MapMemory(base, size); + auto &executor = context.GetRenderExecutor(); + auto &cache = context.GetTextureCache(); + auto &scheduler = context.GetCommandScheduler(); + const auto resolve = [&](const ShaderTextureResource &texture) { + ShaderRecompiler::IR::ImageResource resource{}; + resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled; + resource.numeric_class = Prospero::TextureNumericClass::Float; + resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D; + resource.read = true; + ShaderRecompiler::IR::DescriptorValue value{}; + value.dword_count = 8; + std::copy_n(texture.fields, 8, value.dwords.begin()); + return executor.ResolveTexture(resource, value); + }; + const auto write_linear = [&](const ShaderTextureResource &texture, + std::span texels) { + auto info = MakeTilingImage(texture.Format(), texture.Width5() + 1u, + texture.Height5() + 1u, 1, 1, linear, 0, false); + const u32 words_per_texel = info.bytes_per_block / sizeof(u32); + Require(name, "source size", texels.size() == + info.extent.width * info.extent.height * words_per_texel, + "source texel fixture size is wrong"); + std::vector words(info.data.size / sizeof(u32)); + for (u32 y = 0; y < info.extent.height; ++y) { + std::copy_n(texels.begin() + y * info.extent.width * words_per_texel, + info.extent.width * words_per_texel, + words.begin() + y * info.mip_layout[0].pitch * words_per_texel); + } + Require(name, "invalidate source", context.InvalidateMemory( + texture.Base40(), info.data.size), "source fixture is outside mapped memory"); + LibKernel::Memory::WriteBacking(texture.Base40(), words.data(), info.data.size); + }; + const auto run = [&](const ShaderTextureResource &input, + const ShaderTextureResource &output, u32 width, u32 height) { + Pssr::Request request{}; + std::copy_n(input.fields, 8, request.input); + std::copy_n(output.fields, 8, request.output); + request.width = width; + request.height = height; + // The reserved packet includes trailing words that the parser must consume. + constexpr u32 reserved_words = Pssr::RequestDwords + 9; + std::vector packet(reserved_words, 0xdeadbeef); + packet[0] = KYTY_PM4(reserved_words, Pm4::IT_NOP, Pm4::R_PSSR); + std::memcpy(packet.data() + 1, &request, sizeof(request)); + u32 sentinel = 0; + const auto address = reinterpret_cast(&sentinel); + packet.insert(packet.end(), {KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0), 0, + static_cast(address), static_cast(address >> 32), 33}); + Pm4Execution execution; + Require(name, "packet and suffix", processor.Process(execution, packet) == + Pm4ProcessResult::Complete && sentinel == 33, + "upscaling packet did not consume its reserved payload before the suffix"); + const auto binding = resolve(output); + return ReadCachedTexel(name, context, binding.image_id, {}, + {output.Width5() + 1u, output.Height5() + 1u, 1}, + binding.desc.view_info.base_layer, binding.desc.view_info.base_level); + }; + + auto input = descriptor(base, 2, 1, packed, linear); + auto output = descriptor(base + 0x10000, 4, 1, packed, tiled); + write_linear(input, std::array{red, green}); + Require(name, "CPU-first bilinear source", run(input, output, 2, 1) == + std::vector{red, 0x3a0 | (0x340 << 11), 0x340 | (0x3a0 << 11), green}, + "first-use source upload or linear interpolation is wrong"); + write_linear(input, std::array{blue, blue}); + Require(name, "CPU source refresh", run(input, output, 2, 1) == + std::vector(4, blue), "reused source retained stale GPU texels"); + + input = descriptor(base + 0x20000, 2, 1, packed, linear); + output = descriptor(base + 0x30000, 2, 1, packed, tiled); + write_linear(input, std::array{red, green}); + Require(name, "same-size copy", run(input, output, 2, 1) == + std::vector{red, green}, "same-size upscaling lost source pixels"); + + input = descriptor(base + 0x40000, 8, 4, packed, linear); + output = descriptor(base + 0x50000, 8, 4, packed, tiled); + std::vector cropped(32, blue); + cropped[0] = cropped[1] = cropped[8] = cropped[9] = red; + write_linear(input, cropped); + Require(name, "active rectangle", run(input, output, 2, 2) == + std::vector(32, red), + "equal allocation extents bypassed scaling or sampled outside the active rectangle"); + + input = descriptor(base + 0x60000, 2, 1, packed, linear, DstSel(6, 5, 4, 7)); + output = descriptor(base + 0x70000, 2, 1, packed, tiled); + write_linear(input, std::array{red, green}); + Require(name, "sampled view swizzle", run(input, output, 2, 1) == + std::vector{blue, green}, "sampled descriptor swizzle was ignored"); + output.fields[3] = (output.fields[3] & ~0xfffu) | DstSel(6, 5, 4, 7); + Require(name, "storage swizzle", run(input, output, 2, 1) == + std::vector{red, green}, "output channel mapping was ignored"); + + input = descriptor(base + 0x80000, 2, 1, + Prospero::BufferFormat::k16_16_16_16Float, linear); + output = descriptor(base + 0x90000, 2, 1, packed, tiled); + write_linear(input, std::array{0x00003c00, 0x3c000000, + 0x3c000000, 0x3c000000}); + Require(name, "float16 input", run(input, output, 2, 1) == + std::vector{red, green}, "float16 input conversion failed"); + + ImageDesc parent{}; + parent.type = BindingType::Texture; + parent.info = MakeTilingImage(packed, 4, 2, 1, 2, tiled, 0, false); + parent.info.data.address = base + 0xa0000; + parent.info.pixel_format = vk::Format::eB10G11R11UfloatPack32; + parent.view_info.format = parent.info.pixel_format; + parent.view_info.type = vk::ImageViewType::e2DArray; + parent.view_info.layer_count = 2; + const auto parent_id = cache.FindImage(parent); + (void)cache.FindTexture(parent_id, parent); + vk::ClearValue clear{}; + clear.color.float32 = std::array{1, 0, 0, 1}; + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear); + clear.color.float32 = std::array{0, 0, 1, 1}; + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear); + input = descriptor(parent.info.data.address, 4, 2, packed, tiled); + output = descriptor(parent.info.data.address + parent.info.tiled_slice_stride, + 4, 2, packed, tiled); + const auto resolved_output = resolve(output); + Require(name, "parent array resolution", resolved_output.image_id == parent_id && + resolved_output.desc.view_info.base_layer == 1, + "standalone output did not resolve into the existing array's second layer"); + Require(name, "resolved output layer", run(input, output, 4, 2) == + std::vector(8, red), "upscaling wrote layer zero instead of the resolved layer"); + Require(name, "source layer preserved", ReadCachedTexel(name, context, parent_id, + {}, {4, 2, 1}) == std::vector(8, red), + "upscaling modified the source layer of its shared native image"); + clear.color.float32 = std::array{0, 1, 0, 1}; + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear); + Require(name, "resolved source layer", run(output, input, 4, 2) == + std::vector(8, green), "upscaling read layer zero instead of the resolved layer"); + + input = descriptor(base + 0xc0000, 2, 1, packed, linear); + output = descriptor(base + 0xd0000, 2, 1, packed, tiled); + write_linear(input, std::array{red, green}); + ImageDesc raw{}; + raw.type = BindingType::Texture; + raw.info = MakeTilingImage(Prospero::BufferFormat::k32UInt, 2, 1, 1, 1, + linear, 0, false); + raw.info.data.address = input.Base40(); + raw.info.pixel_format = vk::Format::eR32Uint; + raw.view_info.format = raw.info.pixel_format; + const auto raw_id = cache.FindImage(raw); + (void)cache.FindTexture(raw_id, raw); + const auto float_binding = resolve(input); + Require(name, "reinterpreted sampled view", float_binding.image_id == raw_id && + cache.GetImage(raw_id).backing.format == vk::Format::eR32Uint && + float_binding.desc.view_info.format == vk::Format::eB10G11R11UfloatPack32, + "float sampled descriptor did not reuse the integer native allocation"); + Require(name, "sampled view format", run(input, output, 2, 1) == + std::vector{red, green}, "upscaling used the allocation format instead of its view"); + + ImageDesc mip_parent{}; + mip_parent.type = BindingType::Texture; + mip_parent.info = MakeTilingImage(packed, 128, 64, 2, 1, tiled, 0, false); + mip_parent.info.data.address = base + 0xe0000; + mip_parent.info.pixel_format = vk::Format::eB10G11R11UfloatPack32; + mip_parent.view_info.format = mip_parent.info.pixel_format; + mip_parent.view_info.level_count = 2; + const auto mip_parent_id = cache.FindImage(mip_parent); + (void)cache.FindTexture(mip_parent_id, mip_parent); + clear.color.float32 = std::array{0, 0, 1, 1}; + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), mip_parent_id, + {vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear); + clear.color.float32 = std::array{0, 1, 0, 1}; + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), mip_parent_id, + {vk::ImageAspectFlagBits::eColor, 1, 1, 0, 1}, clear); + input = descriptor(mip_parent.info.data.address + mip_parent.info.mip_layout[1].offset, + 64, 32, packed, tiled); + output = descriptor(base + 0xf0000, 64, 32, packed, tiled); + const auto resolved_mip = resolve(input); + Require(name, "parent mip resolution", resolved_mip.image_id == mip_parent_id && + resolved_mip.desc.view_info.base_level == 1, + "standalone source did not resolve into the existing image's second mip"); + Require(name, "resolved source mip", run(input, output, 64, 32) == + std::vector(64 * 32, green), + "upscaling read mip zero instead of the resolved mip"); + context.UnmapMemory(base, size); + scheduler.Finish(); + }); + LibKernel::Memory::InstallGpuResources(nullptr); + context.ShutdownGpu(); + Require(name, "release", LibKernel::Memory::KernelCheckedReleaseDirectMemory( + physical, size) == 0, "upscaling allocation release failed"); + std::printf("[gpu] %-32s ok\n", name); + } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ec4cc87cc..885f2b20b 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -16,6 +16,7 @@ #include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/pm4.h" +#include "graphics/guest_gpu/pssr.h" #include "graphics/guest_gpu/tile.h" #include "graphics/host_gpu/hostMemory.h" #include "graphics/host_gpu/memoryTracker.h" @@ -21825,6 +21826,8 @@ public: case_index, format_cases); } +#include "PssrGpuTests.inc" + private: bool m_rasterization_supported = true; u32 m_skipped_cases = 0; @@ -51364,13 +51367,16 @@ void CheckPm4IndirectUconfigRegisters(RenderContext &renderer) { static_cast(address), static_cast(address >> 32u), 0x80000000u, 1u, }; - Pm4Execution execution; const bool handler_present = g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] != nullptr; - Require("Pm4IndirectUconfigRegisters", "indirect UCONFIG register stream", - handler_present && - processor.Process(execution, command) == Pm4ProcessResult::Complete, - "UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT"); + for (uint32_t mode = 0; mode <= 1; ++mode) { + command[0] = KYTY_PM4(5, Pm4::IT_SET_UCONFIG_REG_INDIRECT, mode); + Pm4Execution execution; + Require("Pm4IndirectUconfigRegisters", "indirect UCONFIG register stream", + handler_present && + processor.Process(execution, command) == Pm4ProcessResult::Complete, + "UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT"); + } std::printf("[host] %-32s ok\n", "Pm4IndirectUconfigRegisters"); } @@ -53835,6 +53841,12 @@ int main(int argc, char **argv) { CheckPm4ContextStateOperations(vulkan.RuntimeRenderer()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--pssr-only") == 0) { + VulkanHarness vulkan; + vulkan.CheckPssr(); + CheckPm4IndirectUconfigRegisters(vulkan.RuntimeRenderer()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--pm4-wait-only") == 0) { VulkanHarness vulkan; CheckPm4AcquireMemPackets(vulkan.RuntimeRenderer()); diff --git a/tests/TrinityTests.cpp b/tests/TrinityTests.cpp new file mode 100644 index 000000000..fe02e27cc --- /dev/null +++ b/tests/TrinityTests.cpp @@ -0,0 +1,129 @@ +#include "common/emulatorConfig.h" +#include "common/logging/log.h" +#include "common/subsystems.h" +#include "common/threads.h" +#include "common/virtualMemory.h" +#include "graphics/guest_gpu/pm4.h" +#include "kernel/memory.h" +#include "libs/agc.h" +#include "loader/symbolDatabase.h" +#include "loader/systemContent.h" + +#include + +#include +#include +#include + +namespace Libs { +void InitLibKernel_1(Loader::SymbolDatabase*); +} + +namespace { + +namespace Pm4 = Libs::Graphics::Pm4; + +TEST(Trinity, ModeQueriesPacketsAndMemoryAgree) { + static Common::Subsystems subsystems; + Common::VirtualMemory::Init(); + Common::InitializeThreads(); + subsystems.Initialize(); + Config::ConfigOptions options; + options.printf_direction = Config::LogDirection::Silent; + Config::Load(options); + subsystems.Initialize(); + Loader::SymbolDatabase symbols; + Libs::InitLibKernel_1(&symbols); + const auto* query = symbols.FindByNid("tU5e3f9gSiU", Loader::SymbolType::Func); + const auto* operation = symbols.FindByNid("NH6xARDOVv8", Loader::SymbolType::Func); + ASSERT_NE(query, nullptr); + ASSERT_NE(operation, nullptr); + const auto is_trinity = reinterpret_cast(query->vaddr); + const auto operation_mode = reinterpret_cast(operation->vaddr); + + struct CommandBuffer { + uint32_t* bottom; + uint32_t* top; + uint32_t* cursor_up; + uint32_t* cursor_down; + void* callback; + void* user_data; + uint32_t reserved_dw; + }; + const auto path = std::filesystem::temp_directory_path() / + ("kyty_trinity_" + std::to_string(reinterpret_cast(&symbols)) + ".json"); + struct Case { bool enabled; uint32_t attribute; bool expected; }; + for (const auto test: {Case {false, 0, false}, Case {false, 1u << 22, false}, Case {true, 0, false}, + Case {true, 1u << 21, false}, Case {true, 1u << 22, true}}) { + options.pro_enabled = test.enabled; + Config::Load(options); + { + std::ofstream file(path); + file << "{\"attribute3\":" << test.attribute + << R"(,"kernel":{"flexibleMemorySize":3221225472,"cpuPageTableSize":134217728,"gpuPageTableSize":0},"amm":{"pagetableMemorySizeInMib":96}})"; + ASSERT_TRUE(file.good()); + } + testing::internal::CaptureStdout(); + Loader::SystemContentLoadParamSfo(path); + std::filesystem::remove(path); + EXPECT_EQ(Loader::SystemContentIsTrinityMode(), test.expected); + EXPECT_EQ(is_trinity(), test.expected ? 1 : 0); + int mode = -1, submode = -1; + EXPECT_EQ(operation_mode(&mode, &submode), 0); + EXPECT_EQ(mode, test.expected ? 3 : 2); + EXPECT_EQ(submode, 0); + uint8_t graphics_mode = 0xff; + Libs::Graphics::Gen5::AgcGetIsTrinityMode(&graphics_mode); + EXPECT_EQ(graphics_mode, test.expected ? 1 : 0); + std::array packet {}; + CommandBuffer buffer {packet.data(), packet.data() + packet.size(), packet.data(), + packet.data() + packet.size(), nullptr, nullptr, 0}; + Libs::Graphics::Gen5::AgcDcbSetUcRegistersIndirect( + reinterpret_cast(&buffer), nullptr, 0); + EXPECT_EQ(packet[0], KYTY_PM4(5, Pm4::IT_SET_UCONFIG_REG_INDIRECT, + test.expected ? 1 : 0)); + const auto console = testing::internal::GetCapturedStdout(); + if (test.enabled) { + EXPECT_EQ(console, test.expected ? "[PS5] title Trinity support=yes\n" + : "[PS5] title Trinity support=no\n"); + } else { + EXPECT_TRUE(console.empty()) << console; + } + } + + using namespace Libs::LibKernel::Memory; + subsystems.Initialize(); + constexpr uint64_t mib = 1024 * 1024; + constexpr uint64_t backing = (12800 + 1216 - 64 - 96) * mib; + constexpr uint64_t flexible = 3072 * mib; + EXPECT_EQ(TestGuestBackingSize(), backing); + EXPECT_EQ(KernelGetDirectMemorySize(), backing - flexible); + size_t configured = 0; + ASSERT_EQ(KernelConfiguredFlexibleMemorySize(&configured), 0); + EXPECT_EQ(configured, flexible); + int64_t available_start = -1; + size_t available_size = 0; + ASSERT_EQ(KernelAvailableDirectMemorySize(0, backing - flexible, 0x4000, + &available_start, &available_size), 0); + EXPECT_EQ(available_start, 0); + EXPECT_EQ(available_size, backing - flexible); + int64_t physical = -1; + ASSERT_EQ(KernelAllocateDirectMemory(backing - flexible - 0x4000, backing - flexible, + 0x4000, 0x4000, 0, &physical), 0); + void* mapped = nullptr; + ASSERT_EQ(KernelMapDirectMemory(&mapped, 0x4000, 2, 0, physical, 0), 0); + *static_cast(mapped) = 0x123456789abcdef0ull; + EXPECT_EQ(*static_cast(mapped), 0x123456789abcdef0ull); + EXPECT_EQ(KernelMunmap(reinterpret_cast(mapped), 0x4000), 0); + EXPECT_EQ(KernelCheckedReleaseDirectMemory(physical, 0x4000), 0); +} + +} // namespace + +int RunTrinityTests() { + int argc = 1; + char name[] = "trinity_tests"; + char* argv[] = {name, nullptr}; + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 1a313fd0b..24c6871e4 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -5708,7 +5708,12 @@ void TestTlsStubsPreserveFlags() { } #endif +int RunTrinityTests(); + int main(int argc, char** argv) { + if (argc == 2 && std::strcmp(argv[1], "--trinity-only") == 0) { + return RunTrinityTests(); + } InitSubsystems(); if (argc == 2 && std::strcmp(argv[1], "--memory-bottlenecks-only") == 0) { RunTest(TestFlexibleZeroingTracksBackingLifetime);