mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-30 01:01:21 +00:00
graphics: add experimental Pro mode and linear PSSR
Co-authored-by: Joshua Jayasingh <joshuajayasingh08@gmail.com>
This commit is contained in:
1 parent
5e837a9bea
commit
651ef73a03
30 files changed
+1333
-271
No files matched your search
@@ -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_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_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_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 ${gpu_blit_shader_headers})
|
||||||
|
|
||||||
list(APPEND kyty_emulator_src
|
list(APPEND kyty_emulator_src
|
||||||
@@ -631,6 +632,19 @@ target_sources(shader_recompiler_compute_tests PRIVATE
|
|||||||
"${gpu_test_ms_depth_header}")
|
"${gpu_test_ms_depth_header}")
|
||||||
|
|
||||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
|
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
|
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
|
||||||
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
|
||||||
|
|
||||||
@@ -710,6 +724,10 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
|
||||||
add_test(NAME virtual_memory_allocation
|
add_test(NAME virtual_memory_allocation
|
||||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
|
||||||
|
add_test(NAME trinity_mode
|
||||||
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --trinity-only)
|
||||||
|
add_test(NAME psml COMMAND $<TARGET_FILE:psml_tests>)
|
||||||
|
add_test(NAME pssr COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --pssr-only)
|
||||||
add_test(NAME guest_red_zone_patcher
|
add_test(NAME guest_red_zone_patcher
|
||||||
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
|
||||||
add_test(NAME command_scheduler_timeline
|
add_test(NAME command_scheduler_timeline
|
||||||
|
|||||||
@@ -113,6 +113,10 @@ bool VrEnabled() {
|
|||||||
return g_config->vr_enabled;
|
return g_config->vr_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Ps5ProEnabled() {
|
||||||
|
return g_config->pro_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool AmdCpuEnabled() {
|
bool AmdCpuEnabled() {
|
||||||
return g_config->amd_cpu_enabled;
|
return g_config->amd_cpu_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ struct ConfigOptions {
|
|||||||
bool stretch_enabled = false;
|
bool stretch_enabled = false;
|
||||||
bool splash_enabled = true;
|
bool splash_enabled = true;
|
||||||
bool vr_enabled = false;
|
bool vr_enabled = false;
|
||||||
|
bool pro_enabled = false;
|
||||||
bool amd_cpu_enabled = false;
|
bool amd_cpu_enabled = false;
|
||||||
bool network_connected = false;
|
bool network_connected = false;
|
||||||
uint32_t vblank_frequency = 60;
|
uint32_t vblank_frequency = 60;
|
||||||
@@ -128,6 +129,7 @@ bool HdrEnabled();
|
|||||||
bool HideCursorEnabled();
|
bool HideCursorEnabled();
|
||||||
bool StretchEnabled();
|
bool StretchEnabled();
|
||||||
bool VrEnabled();
|
bool VrEnabled();
|
||||||
|
bool Ps5ProEnabled();
|
||||||
bool AmdCpuEnabled();
|
bool AmdCpuEnabled();
|
||||||
bool IsNetworkConnected();
|
bool IsNetworkConnected();
|
||||||
uint32_t GetVblankFrequency();
|
uint32_t GetVblankFrequency();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "graphics/guest_gpu/hardwareContext.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/render.h"
|
||||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ public:
|
|||||||
uint32_t mode);
|
uint32_t mode);
|
||||||
void DispatchIndirect(uint64_t args_addr, 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 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 TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||||
|
|
||||||
void WaitForRewind(bool valid);
|
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_POP_MARKER] = CpOpPopMarker;
|
||||||
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
|
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_CONTEXT_STATE] = CpOpContextState;
|
||||||
|
g_cp_op_custom_func[Pm4::R_PSSR] = CpOpPssr;
|
||||||
|
|
||||||
return g_cp_op_custom_func;
|
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 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 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 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
|
} // namespace Libs::Graphics
|
||||||
|
|
||||||
|
|||||||
@@ -1706,6 +1706,17 @@ KYTY_CP_OP_PARSER(CpOpFlip) {
|
|||||||
return 5;
|
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_CP_OP_PARSER(CpOpIndexType) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||||
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
|
||||||
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ constexpr uint32_t R_WRITE_DATA = 0x15;
|
|||||||
constexpr uint32_t R_FLIP = 0x17;
|
constexpr uint32_t R_FLIP = 0x17;
|
||||||
constexpr uint32_t R_DMA_DATA = 0x19;
|
constexpr uint32_t R_DMA_DATA = 0x19;
|
||||||
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
||||||
|
constexpr uint32_t R_PSSR = 0x1B;
|
||||||
|
|
||||||
constexpr uint32_t R_NUM = 0x3F + 1;
|
constexpr uint32_t R_NUM = 0x3F + 1;
|
||||||
|
|
||||||
|
|||||||
@@ -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_
|
||||||
@@ -150,15 +150,21 @@ private:
|
|||||||
friend class CommandScheduler;
|
friend class CommandScheduler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace Pssr {
|
||||||
|
struct Request;
|
||||||
|
}
|
||||||
|
|
||||||
class RenderExecutor {
|
class RenderExecutor {
|
||||||
public:
|
public:
|
||||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||||
|
~RenderExecutor();
|
||||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||||
|
|
||||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
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);
|
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,
|
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
|
||||||
uint32_t mode, bool async_compute);
|
uint32_t mode, bool async_compute);
|
||||||
|
void ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request);
|
||||||
|
|
||||||
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
|
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
|
||||||
void FindBuffers(std::span<PreparedBindings* const> stages);
|
void FindBuffers(std::span<PreparedBindings* const> stages);
|
||||||
@@ -219,6 +225,9 @@ private:
|
|||||||
std::vector<ImageId> m_bound_images;
|
std::vector<ImageId> m_bound_images;
|
||||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
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 class CommandProcessor;
|
||||||
friend struct RenderExecutorTestAccess;
|
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), ¶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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -1006,8 +1006,10 @@ void Initialize() {
|
|||||||
const auto table_overhead = g_cpu_table_memory + g_gpu_table_memory +
|
const auto table_overhead = g_cpu_table_memory + g_gpu_table_memory +
|
||||||
std::max(params.amm_table_memory, INCLUDED_TABLE_MEMORY) -
|
std::max(params.amm_table_memory, INCLUDED_TABLE_MEMORY) -
|
||||||
3 * INCLUDED_TABLE_MEMORY;
|
3 * INCLUDED_TABLE_MEMORY;
|
||||||
EXIT_IF(g_flexible_memory_size + table_overhead >= GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE);
|
const auto memory_budget = GUEST_MEMORY_BUDGET +
|
||||||
g_guest_memory_size = GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE - table_overhead;
|
(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",
|
LOGF("\t flexible memory size = %" PRIu64 " MiB, direct memory size = %" PRIu64 " MiB\n",
|
||||||
g_flexible_memory_size / (1024ull * 1024ull),
|
g_flexible_memory_size / (1024ull * 1024ull),
|
||||||
(g_guest_memory_size - 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">
|
<widget class="QGroupBox" name="general_group">
|
||||||
<property name="title"><string>General</string></property>
|
<property name="title"><string>General</string></property>
|
||||||
<layout class="QVBoxLayout" name="generalLayout">
|
<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>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_fullscreen">
|
<widget class="QCheckBox" name="checkBox_fullscreen">
|
||||||
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ public:
|
|||||||
QString printf_output_file = "_kyty.txt";
|
QString printf_output_file = "_kyty.txt";
|
||||||
bool profiler_enabled = false;
|
bool profiler_enabled = false;
|
||||||
bool renderdoc_enabled = false;
|
bool renderdoc_enabled = false;
|
||||||
|
bool pro_enabled = false;
|
||||||
bool amd_cpu_enabled = false;
|
bool amd_cpu_enabled = false;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
bool red_zone_protection_enabled = false;
|
bool red_zone_protection_enabled = false;
|
||||||
@@ -184,6 +185,7 @@ public:
|
|||||||
printf_output_file = other.printf_output_file;
|
printf_output_file = other.printf_output_file;
|
||||||
profiler_enabled = other.profiler_enabled;
|
profiler_enabled = other.profiler_enabled;
|
||||||
renderdoc_enabled = other.renderdoc_enabled;
|
renderdoc_enabled = other.renderdoc_enabled;
|
||||||
|
pro_enabled = other.pro_enabled;
|
||||||
amd_cpu_enabled = other.amd_cpu_enabled;
|
amd_cpu_enabled = other.amd_cpu_enabled;
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
red_zone_protection_enabled = other.red_zone_protection_enabled;
|
red_zone_protection_enabled = other.red_zone_protection_enabled;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
KYTY_CFG_SET(printf_output_file);
|
KYTY_CFG_SET(printf_output_file);
|
||||||
KYTY_CFG_SET(profiler_enabled);
|
KYTY_CFG_SET(profiler_enabled);
|
||||||
KYTY_CFG_SET(renderdoc_enabled);
|
KYTY_CFG_SET(renderdoc_enabled);
|
||||||
|
KYTY_CFG_SET(pro_enabled);
|
||||||
KYTY_CFG_SET(amd_cpu_enabled);
|
KYTY_CFG_SET(amd_cpu_enabled);
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
KYTY_CFG_SET(red_zone_protection_enabled);
|
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(printf_output_file);
|
||||||
KYTY_CFG_GET(profiler_enabled);
|
KYTY_CFG_GET(profiler_enabled);
|
||||||
KYTY_CFG_GET(renderdoc_enabled);
|
KYTY_CFG_GET(renderdoc_enabled);
|
||||||
|
pro_enabled = s.value("pro_enabled", false).toBool();
|
||||||
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
|
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
red_zone_protection_enabled =
|
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_shader_validation->setChecked(info.shader_validation_enabled);
|
||||||
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
|
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
|
||||||
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_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);
|
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
m_ui->checkBox_amd_cpu->setVisible(false);
|
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.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
|
||||||
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
|
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
|
||||||
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->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();
|
info.amd_cpu_enabled = ui.checkBox_amd_cpu->isChecked();
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
|
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
|||||||
args << "--printf-direction" << EnumToText(info.printf_direction);
|
args << "--printf-direction" << EnumToText(info.printf_direction);
|
||||||
args << "--printf-output-file" << info.printf_output_file;
|
args << "--printf-output-file" << info.printf_output_file;
|
||||||
AddFlag(args, info.profiler_enabled, "--profile");
|
AddFlag(args, info.profiler_enabled, "--profile");
|
||||||
|
AddFlag(args, info.pro_enabled, "--trinity");
|
||||||
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
|
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
|
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
|
||||||
|
|||||||
+3
-2
@@ -24,6 +24,7 @@
|
|||||||
#include "kernel/pthread.h"
|
#include "kernel/pthread.h"
|
||||||
#include "libs/errno.h"
|
#include "libs/errno.h"
|
||||||
#include "libs/libs.h"
|
#include "libs/libs.h"
|
||||||
|
#include "loader/systemContent.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#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);
|
return ((cmd[0] >> 8u) & 0xffu) == reg_indirect_native_op(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr bool is_trinity_mode() {
|
static bool is_trinity_mode() {
|
||||||
return false;
|
return Loader::SystemContentIsTrinityMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
|
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
|
||||||
|
|||||||
+3
-13
@@ -20,6 +20,7 @@
|
|||||||
#include "libs/network.h"
|
#include "libs/network.h"
|
||||||
#include "loader/elf.h"
|
#include "loader/elf.h"
|
||||||
#include "loader/runtimeLinker.h"
|
#include "loader/runtimeLinker.h"
|
||||||
|
#include "loader/systemContent.h"
|
||||||
#include "loader/symbolDatabase.h"
|
#include "loader/symbolDatabase.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -1321,23 +1322,12 @@ static void* KYTY_SYSV_ABI KernelGetProcParam() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
|
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
|
||||||
static std::atomic_uint32_t call_count {0};
|
return Loader::SystemContentIsTrinityMode() ? 1 : 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
|
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
|
||||||
PRINT_NAME();
|
*mode = Loader::SystemContentIsTrinityMode() ? 3 : 2;
|
||||||
|
|
||||||
*mode = 2; // PS5 Base
|
|
||||||
*submode = 0; // None
|
*submode = 0; // None
|
||||||
LOGF("\t mode = %d, submode = %d\n", *mode, *submode);
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+280
-253
@@ -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/errno.h"
|
||||||
#include "libs/libs.h"
|
#include "libs/libs.h"
|
||||||
#include "loader/symbolDatabase.h"
|
#include "loader/symbolDatabase.h"
|
||||||
|
#include "loader/systemContent.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace Libs {
|
namespace Libs::LibPsml {
|
||||||
|
|
||||||
namespace LibPsml {
|
|
||||||
|
|
||||||
LIB_VERSION("Psml", 1, "Psml", 1, 1);
|
LIB_VERSION("Psml", 1, "Psml", 1, 1);
|
||||||
|
|
||||||
namespace Psml {
|
namespace {
|
||||||
|
|
||||||
constexpr int PSML_ERROR_NOT_INITIALIZED = static_cast<int>(0x8a810001u);
|
constexpr int NOT_INITIALIZED = static_cast<int>(0x8a810001u);
|
||||||
constexpr int PSML_ERROR_INVALID_OBJECT = static_cast<int>(0x8a810005u);
|
constexpr int ALREADY_INITIALIZED = static_cast<int>(0x8a810002u);
|
||||||
constexpr int PSML_ERROR_INVALID_POINTER = static_cast<int>(0x8a810009u);
|
constexpr int TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
|
||||||
constexpr int PSML_ERROR_INVALID_VALUE = static_cast<int>(0x8a81000du);
|
constexpr int INVALID_CONTEXT = static_cast<int>(0x8a810005u);
|
||||||
constexpr int PSML_ERROR_NULL_OBJECT = static_cast<int>(0x8a810014u);
|
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;
|
struct MemoryRequirements {
|
||||||
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 {
|
|
||||||
uint64_t block_size;
|
uint64_t block_size;
|
||||||
uint64_t alignment;
|
uint64_t alignment;
|
||||||
uint64_t block_count;
|
uint64_t block_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MainMemoryParameters {
|
|
||||||
uint32_t type;
|
|
||||||
uint32_t reserved;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DirectMemoryBlock {
|
struct DirectMemoryBlock {
|
||||||
uint64_t address;
|
uint64_t offset;
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SharedResourcesInitParameters {
|
struct MemoryParameters {
|
||||||
uint32_t type;
|
uint32_t profile;
|
||||||
uint32_t reserved;
|
uint32_t flags;
|
||||||
const DirectMemoryBlock* blocks;
|
const DirectMemoryBlock* blocks;
|
||||||
uint64_t block_count;
|
uint64_t block_count;
|
||||||
uint64_t virtual_address_start;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(MainMemoryRequirements) == 24);
|
struct SharedParameters {
|
||||||
static_assert(sizeof(DirectMemoryBlock) == 16);
|
MemoryParameters memory;
|
||||||
|
uint64_t virtual_address;
|
||||||
|
};
|
||||||
|
|
||||||
static bool g_initialized = false;
|
struct ContextParameters {
|
||||||
static uint32_t g_supported_modes = 0x3;
|
MemoryParameters memory;
|
||||||
static std::atomic<int32_t> g_shared_ref_count = 0;
|
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) {
|
struct SharedResources {
|
||||||
// The native implementation indexes this table before adding one 6 MiB range.
|
uint32_t contexts = 0;
|
||||||
switch (type) {
|
};
|
||||||
case 0: return 0x06200000;
|
|
||||||
case 1: return 0x18200000;
|
struct Context {
|
||||||
case 2: return 0x12200000;
|
uint32_t width;
|
||||||
default: return 0;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsSupportedType(uint32_t type) {
|
int CheckContext(const void* context) {
|
||||||
switch (type) {
|
if (!g_initialized) {
|
||||||
case 0: return true;
|
return NOT_INITIALIZED;
|
||||||
case 1: return (g_supported_modes & 0x1u) != 0;
|
|
||||||
case 2: return (g_supported_modes & 0x2u) != 0;
|
|
||||||
default: return false;
|
|
||||||
}
|
}
|
||||||
|
return g_contexts.contains(context) ? OK : INVALID_CONTEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t RequiredBlockCount(uint32_t type) {
|
int KYTY_SYSV_ABI Initialize() {
|
||||||
const auto base = BaseVaBytes(type);
|
std::lock_guard lock(g_mutex);
|
||||||
if (base == 0) {
|
if (!Loader::SystemContentIsTrinityMode()) {
|
||||||
return 0;
|
return NOT_TRINITY_MODE;
|
||||||
}
|
}
|
||||||
return (base + EXTRA_VA_BYTES) / MAIN_MEMORY_BLOCK_SIZE;
|
if (g_initialized) {
|
||||||
|
return ALREADY_INITIALIZED;
|
||||||
}
|
}
|
||||||
|
|
||||||
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_initialized = true;
|
||||||
g_supported_modes = 0x3;
|
|
||||||
g_shared_ref_count = 0;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlGetMainMemoryRequirements(MainMemoryRequirements* out,
|
int KYTY_SYSV_ABI GetSharedRequirements(MemoryRequirements* out, const MemoryParameters* params) {
|
||||||
const MainMemoryParameters* params) {
|
std::lock_guard lock(g_mutex);
|
||||||
PRINT_NAME();
|
if (!g_initialized) {
|
||||||
|
return NOT_INITIALIZED;
|
||||||
const auto init_error = CheckInitialized();
|
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (out == nullptr || params == nullptr) {
|
if (out == nullptr || params == nullptr) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
if (!IsSupportedType(params->type)) {
|
const auto blocks = SharedBlockCount(params->profile);
|
||||||
return PSML_ERROR_INVALID_VALUE;
|
if (blocks == 0) {
|
||||||
|
return INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, blocks};
|
||||||
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
|
|
||||||
out->alignment = MAIN_MEMORY_ALIGNMENT;
|
|
||||||
out->block_count = RequiredBlockCount(params->type);
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI
|
int KYTY_SYSV_ABI CreateSharedResources(void** out, const SharedParameters* params) {
|
||||||
PsmlSharedResourcesInitialize(void* resources, const SharedResourcesInitParameters* params) {
|
std::lock_guard lock(g_mutex);
|
||||||
PRINT_NAME();
|
if (!g_initialized) {
|
||||||
|
return NOT_INITIALIZED;
|
||||||
const auto init_error = CheckInitialized();
|
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (resources == nullptr || params == nullptr || params->blocks == nullptr) {
|
if (out == nullptr || params == nullptr || params->memory.blocks == nullptr) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
if (params->reserved != 0 || !IsSupportedType(params->type) ||
|
const auto required = SharedBlockCount(params->memory.profile);
|
||||||
params->block_count < RequiredBlockCount(params->type)) {
|
if (required == 0 || params->memory.flags != 0) {
|
||||||
return PSML_ERROR_INVALID_VALUE;
|
return INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
if (params->memory.block_count < required) {
|
||||||
MarkSharedResources(resources, *params);
|
return INSUFFICIENT_BUFFER;
|
||||||
g_shared_ref_count.fetch_add(1, std::memory_order_relaxed);
|
}
|
||||||
|
if (g_shared) {
|
||||||
|
return TOO_MANY_SHARED_RESOURCES;
|
||||||
|
}
|
||||||
|
g_shared = std::make_unique<SharedResources>();
|
||||||
|
*out = g_shared.get();
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlSharedResourcesFinalize(void* resources) {
|
int KYTY_SYSV_ABI ReleaseSharedResources(void* resources) {
|
||||||
PRINT_NAME();
|
std::lock_guard lock(g_mutex);
|
||||||
|
if (!g_initialized) {
|
||||||
const auto init_error = CheckInitialized();
|
return NOT_INITIALIZED;
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (resources == nullptr) {
|
if (!g_shared || resources != g_shared.get()) {
|
||||||
return PSML_ERROR_NULL_OBJECT;
|
return INVALID_SHARED_RESOURCES;
|
||||||
}
|
}
|
||||||
|
if (g_shared->contexts != 0) {
|
||||||
g_shared_ref_count.fetch_sub(1, std::memory_order_relaxed);
|
return INVALID_CONTEXT;
|
||||||
|
}
|
||||||
|
g_shared.reset();
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlGetContextMemoryRequirements(MainMemoryRequirements* out,
|
int KYTY_SYSV_ABI GetContextRequirements(MemoryRequirements* out, const void* params) {
|
||||||
uint64_t /*params*/) {
|
std::lock_guard lock(g_mutex);
|
||||||
PRINT_NAME();
|
if (!g_initialized) {
|
||||||
|
return NOT_INITIALIZED;
|
||||||
const auto init_error = CheckInitialized();
|
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (out == nullptr) {
|
if (out == nullptr || params == nullptr) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
|
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, 1};
|
||||||
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
|
|
||||||
out->alignment = MAIN_MEMORY_ALIGNMENT;
|
|
||||||
out->block_count = 1;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlContextInitialize(void* context, const void* params) {
|
int CreateContext(void** out, const MemoryParameters* memory, uint32_t width, uint32_t height,
|
||||||
PRINT_NAME();
|
void* shared, bool use_shared) {
|
||||||
|
std::lock_guard lock(g_mutex);
|
||||||
const auto init_error = CheckInitialized();
|
if (!g_initialized) {
|
||||||
if (init_error != OK) {
|
return NOT_INITIALIZED;
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (context == nullptr || params == nullptr) {
|
if (out == nullptr || memory == nullptr || memory->blocks == nullptr) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
|
if (memory->profile > 2) {
|
||||||
auto* bytes = static_cast<uint8_t*>(context);
|
return INVALID_PARAMETER;
|
||||||
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->block_count == 0) {
|
||||||
*reinterpret_cast<uint32_t*>(bytes) = CONTEXT_MAGIC;
|
return INSUFFICIENT_BUFFER;
|
||||||
*reinterpret_cast<void**>(bytes + 0x360) = shared_resources;
|
}
|
||||||
*reinterpret_cast<uint8_t*>(bytes + 0x368) = 0;
|
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;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlContextFinalize(void* context) {
|
int KYTY_SYSV_ABI CreateFixedContext(void** out, const MemoryParameters* params) {
|
||||||
PRINT_NAME();
|
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;
|
|
||||||
}
|
|
||||||
if (!HasKnownObjectMagic(context)) {
|
|
||||||
return PSML_ERROR_INVALID_OBJECT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*static_cast<uint32_t*>(context) = 0;
|
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;
|
||||||
|
}
|
||||||
|
auto& record = g_contexts.at(context);
|
||||||
|
if (record->shared != nullptr) {
|
||||||
|
--record->shared->contexts;
|
||||||
|
}
|
||||||
|
g_contexts.erase(context);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlGetWorkAreaSize(const void* object, uint32_t* out_size) {
|
int KYTY_SYSV_ABI GetPacketSize(const void* context, uint32_t* out_size) {
|
||||||
PRINT_NAME();
|
std::lock_guard lock(g_mutex);
|
||||||
|
if (const int error = CheckContext(context); error != OK) {
|
||||||
const auto init_error = CheckInitialized();
|
return error;
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
|
||||||
if (!HasKnownObjectMagic(object)) {
|
|
||||||
return PSML_ERROR_INVALID_OBJECT;
|
|
||||||
}
|
}
|
||||||
if (out_size == nullptr) {
|
if (out_size == nullptr) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
|
*out_size = PACKET_DWORDS;
|
||||||
*out_size = 0x600;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlDispatch(void* context, uint64_t command, uint64_t params) {
|
int KYTY_SYSV_ABI Dispatch(const void* context, const CommandSpace* space, const void* params) {
|
||||||
PRINT_NAME();
|
std::lock_guard lock(g_mutex);
|
||||||
|
if (const int error = CheckContext(context); error != OK) {
|
||||||
const auto init_error = CheckInitialized();
|
return error;
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (!HasKnownObjectMagic(context)) {
|
if (space == nullptr || params == nullptr || space->cmd == nullptr) {
|
||||||
return PSML_ERROR_INVALID_OBJECT;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
if (command == 0 || params == 0) {
|
if (space->dwords < PACKET_DWORDS) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
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;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlGetProgress(const void* object, float* out_progress,
|
int KYTY_SYSV_ABI GetMipmapBias(const void* context, float* bias, uint32_t width, uint32_t height) {
|
||||||
uint32_t /*frame_index*/) {
|
std::lock_guard lock(g_mutex);
|
||||||
PRINT_NAME();
|
if (const int error = CheckContext(context); error != OK) {
|
||||||
|
return error;
|
||||||
const auto init_error = CheckInitialized();
|
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
}
|
||||||
if (!HasKnownObjectMagic(object)) {
|
if (bias == nullptr) {
|
||||||
return PSML_ERROR_INVALID_OBJECT;
|
return ARGUMENT_NULL;
|
||||||
}
|
}
|
||||||
if (out_progress == nullptr) {
|
if (width == 0 || height == 0) {
|
||||||
return PSML_ERROR_INVALID_POINTER;
|
return UNSUPPORTED_RENDER_SIZE;
|
||||||
}
|
}
|
||||||
|
*bias = std::log2(float(width) / float(g_contexts.at(context)->width)) - 1.0f;
|
||||||
*out_progress = 0.0f;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int KYTY_SYSV_ABI PsmlRequestCapture(const void* object) {
|
int KYTY_SYSV_ABI ValidateContext(const void* context) {
|
||||||
PRINT_NAME();
|
std::lock_guard lock(g_mutex);
|
||||||
|
return CheckContext(context);
|
||||||
const auto init_error = CheckInitialized();
|
|
||||||
if (init_error != OK) {
|
|
||||||
return init_error;
|
|
||||||
}
|
|
||||||
if (!HasKnownObjectMagic(object)) {
|
|
||||||
return PSML_ERROR_INVALID_OBJECT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return OK;
|
} // namespace
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
LIB_DEFINE(InitPsml_1) {
|
LIB_DEFINE(InitPsml_1) {
|
||||||
LIB_FUNC("3WVD91e12ZQ", Psml::PsmlInitialize);
|
LIB_FUNC("3WVD91e12ZQ", Initialize);
|
||||||
LIB_FUNC("+2KpvixvL6E", Psml::PsmlGetMainMemoryRequirements);
|
LIB_FUNC("+2KpvixvL6E", GetSharedRequirements);
|
||||||
LIB_FUNC("eWoKNeB6V-k", Psml::PsmlSharedResourcesInitialize);
|
LIB_FUNC("eWoKNeB6V-k", CreateSharedResources);
|
||||||
LIB_FUNC("jEevBXmagOQ", Psml::PsmlSharedResourcesFinalize);
|
LIB_FUNC("jEevBXmagOQ", ReleaseSharedResources);
|
||||||
LIB_FUNC("EO9YQXmJEN8", Psml::PsmlGetContextMemoryRequirements);
|
LIB_FUNC("EO9YQXmJEN8", GetContextRequirements);
|
||||||
LIB_FUNC("IMlj247LdTo", Psml::PsmlGetContextMemoryRequirements);
|
LIB_FUNC("IMlj247LdTo", GetContextRequirements);
|
||||||
LIB_FUNC("sjTyYheKTrU", Psml::PsmlGetContextMemoryRequirements);
|
LIB_FUNC("sjTyYheKTrU", GetContextRequirements);
|
||||||
LIB_FUNC("ArakEpzsZo0", Psml::PsmlGetContextMemoryRequirements);
|
LIB_FUNC("ArakEpzsZo0", GetContextRequirements);
|
||||||
LIB_FUNC("VGjrQa-WqdU", Psml::PsmlGetContextMemoryRequirements);
|
LIB_FUNC("VGjrQa-WqdU", GetContextRequirements);
|
||||||
LIB_FUNC("2ecEbQaf9VU", Psml::PsmlContextInitialize);
|
LIB_FUNC("2ecEbQaf9VU", CreateFixedContext);
|
||||||
LIB_FUNC("vUk2pWMx3KQ", Psml::PsmlContextInitialize);
|
LIB_FUNC("vUk2pWMx3KQ", CreatePrivateContext);
|
||||||
LIB_FUNC("gxv3i+MTEzU", Psml::PsmlContextInitialize);
|
LIB_FUNC("gxv3i+MTEzU", CreateSharedContext);
|
||||||
LIB_FUNC("JaLBe0P3jSU", Psml::PsmlContextFinalize);
|
LIB_FUNC("JaLBe0P3jSU", ReleaseContext);
|
||||||
LIB_FUNC("AHalTX9wFZY", Psml::PsmlGetWorkAreaSize);
|
LIB_FUNC("AHalTX9wFZY", GetPacketSize);
|
||||||
LIB_FUNC("RUNLFro+qok", Psml::PsmlDispatch);
|
LIB_FUNC("RUNLFro+qok", Dispatch);
|
||||||
LIB_FUNC("GHna9-DvnUk", Psml::PsmlGetProgress);
|
LIB_FUNC("GHna9-DvnUk", GetMipmapBias);
|
||||||
LIB_FUNC("GJY0MvuTcs8", Psml::PsmlRequestCapture);
|
LIB_FUNC("GJY0MvuTcs8", ValidateContext);
|
||||||
LIB_FUNC("LXq+6mIxpCw", Psml::PsmlValidateObject);
|
LIB_FUNC("LXq+6mIxpCw", ValidateContext);
|
||||||
LIB_FUNC("FSGaTQze0UY", Psml::PsmlValidateObject);
|
LIB_FUNC("FSGaTQze0UY", ValidateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace LibPsml
|
} // namespace Libs::LibPsml
|
||||||
|
|
||||||
} // namespace Libs
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
|
#include "common/emulatorConfig.h"
|
||||||
#include "common/file.h"
|
#include "common/file.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
@@ -90,6 +91,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SystemContent {
|
struct SystemContent {
|
||||||
|
bool trinity_mode = false;
|
||||||
std::filesystem::path psf_path;
|
std::filesystem::path psf_path;
|
||||||
Psf psf;
|
Psf psf;
|
||||||
std::filesystem::path playgo_path;
|
std::filesystem::path playgo_path;
|
||||||
@@ -603,6 +605,14 @@ void SystemContentLoadParamSfo(const std::filesystem::path& file_name) {
|
|||||||
|
|
||||||
sc->psf_path = file_name;
|
sc->psf_path = file_name;
|
||||||
sc->psf.DbgPrint();
|
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";
|
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) {
|
bool SystemContentParamSfoGetInt(const char* name, int32_t* value) {
|
||||||
if (name == nullptr || value == nullptr) {
|
if (name == nullptr || value == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
namespace Loader {
|
namespace Loader {
|
||||||
|
|
||||||
void SystemContentLoadParamSfo(const std::filesystem::path& file_name);
|
void SystemContentLoadParamSfo(const std::filesystem::path& file_name);
|
||||||
|
bool SystemContentIsTrinityMode();
|
||||||
bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
|
bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
|
||||||
bool SystemContentParamSfoGetString(const char* name, std::string* value);
|
bool SystemContentParamSfoGetString(const char* name, std::string* value);
|
||||||
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
|
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ static void PrintUsage() {
|
|||||||
" --stretch Stretch the image to the window. Default: off.\n");
|
" --stretch Stretch the image to the window. Default: off.\n");
|
||||||
::printf(" --no-splash Disable the startup splash screen.\n");
|
::printf(" --no-splash Disable the startup splash screen.\n");
|
||||||
::printf(" --vr Enable the virtual VR headset.\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(" --amd-cpu Apply AMD CPU instruction patches.\n");
|
||||||
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
|
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
|
||||||
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\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},
|
{"--stretch", &ConfigOptions::stretch_enabled},
|
||||||
{"--no-splash", &ConfigOptions::splash_enabled, false},
|
{"--no-splash", &ConfigOptions::splash_enabled, false},
|
||||||
{"--vr", &ConfigOptions::vr_enabled},
|
{"--vr", &ConfigOptions::vr_enabled},
|
||||||
|
{"--trinity", &ConfigOptions::pro_enabled},
|
||||||
{"--amd-cpu", &ConfigOptions::amd_cpu_enabled},
|
{"--amd-cpu", &ConfigOptions::amd_cpu_enabled},
|
||||||
{"--network-connected", &ConfigOptions::network_connected},
|
{"--network-connected", &ConfigOptions::network_connected},
|
||||||
{"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled},
|
{"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled},
|
||||||
|
|||||||
@@ -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 <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<int>(0x8a810002u);
|
||||||
|
constexpr int ERROR_TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
|
||||||
|
constexpr int ERROR_INVALID_CONTEXT = static_cast<int>(0x8a810005u);
|
||||||
|
constexpr int ERROR_NO_SPACE = static_cast<int>(0x8a810006u);
|
||||||
|
constexpr int ERROR_BAD_COMMAND_BUFFER = static_cast<int>(0x8a810007u);
|
||||||
|
constexpr int ERROR_NULL_ARGUMENT = static_cast<int>(0x8a810009u);
|
||||||
|
constexpr int ERROR_NULL_TEXTURE = static_cast<int>(0x8a81000au);
|
||||||
|
constexpr int ERROR_INVALID_TEXTURE = static_cast<int>(0x8a81000bu);
|
||||||
|
constexpr int ERROR_RENDER_SIZE = static_cast<int>(0x8a81000eu);
|
||||||
|
constexpr int ERROR_OUTPUT_TEXTURE = static_cast<int>(0x8a81000fu);
|
||||||
|
constexpr int ERROR_NOT_TRINITY = static_cast<int>(0x8a810012u);
|
||||||
|
constexpr int ERROR_INVALID_SHARED = static_cast<int>(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<uint8_t, 0xe0> unused{};
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
};
|
||||||
|
static_assert(offsetof(DispatchParameters, width) == 0xf0);
|
||||||
|
|
||||||
|
struct GuardedHandle {
|
||||||
|
uint64_t before = CANARY;
|
||||||
|
void *value = nullptr;
|
||||||
|
std::array<uint64_t, 128> 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 <typename T> 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<T>(symbol->vaddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint32_t, 8> Texture(uint32_t width, uint32_t height,
|
||||||
|
uint32_t address) {
|
||||||
|
std::array<uint32_t, 8> 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<Initialize>("3WVD91e12ZQ")(), ERROR_NOT_TRINITY);
|
||||||
|
g_trinity = true;
|
||||||
|
ASSERT_EQ(Function<Initialize>("3WVD91e12ZQ")(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
SharedParameters params;
|
||||||
|
params.blocks = blocks.data();
|
||||||
|
params.block_count = blocks.size();
|
||||||
|
GuardedHandle result;
|
||||||
|
ASSERT_EQ(Function<CreateShared>("eWoKNeB6V-k")(&result.value, ¶ms), 0);
|
||||||
|
ASSERT_TRUE(result.Intact());
|
||||||
|
ASSERT_GT(reinterpret_cast<uintptr_t>(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<CreateContext>("gxv3i+MTEzU")(&result.value, &context_params),
|
||||||
|
0);
|
||||||
|
ASSERT_TRUE(result.Intact());
|
||||||
|
ASSERT_GT(reinterpret_cast<uintptr_t>(result.value), 0x10000u);
|
||||||
|
context = result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
for (auto *extra : extra_contexts) {
|
||||||
|
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(extra), 0);
|
||||||
|
}
|
||||||
|
if (context != nullptr) {
|
||||||
|
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), 0);
|
||||||
|
}
|
||||||
|
if (shared != nullptr) {
|
||||||
|
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<MemoryBlock, 52> blocks;
|
||||||
|
ContextParameters context_params;
|
||||||
|
void *shared = nullptr;
|
||||||
|
void *context = nullptr;
|
||||||
|
std::vector<void *> extra_contexts;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(PsmlTest, HandlesPreserveAdjacentMemoryAndRejectReleasedObjects) {
|
||||||
|
EXPECT_EQ(Function<Initialize>("3WVD91e12ZQ")(), ERROR_ALREADY_INITIALIZED);
|
||||||
|
uint32_t size = 0;
|
||||||
|
EXPECT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &size), 0);
|
||||||
|
EXPECT_EQ(size, Pssr::RequestDwords + 1);
|
||||||
|
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), ERROR_INVALID_CONTEXT);
|
||||||
|
EXPECT_EQ(Function<Predicate>("LXq+6mIxpCw")(context), 0);
|
||||||
|
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), 0);
|
||||||
|
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), ERROR_INVALID_CONTEXT);
|
||||||
|
EXPECT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &size),
|
||||||
|
ERROR_INVALID_CONTEXT);
|
||||||
|
EXPECT_EQ(Function<Predicate>("LXq+6mIxpCw")(context), ERROR_INVALID_CONTEXT);
|
||||||
|
context = nullptr;
|
||||||
|
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), 0);
|
||||||
|
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), ERROR_INVALID_SHARED);
|
||||||
|
shared = nullptr;
|
||||||
|
EXPECT_EQ(
|
||||||
|
Function<GetSize>("AHalTX9wFZY")(reinterpret_cast<void *>(1), &size),
|
||||||
|
ERROR_INVALID_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PsmlTest, LegacyContextVariantsUseTheirOwnLayoutsAndOutputResolution) {
|
||||||
|
context_params.shared = reinterpret_cast<void *>(1);
|
||||||
|
GuardedHandle result;
|
||||||
|
ASSERT_EQ(
|
||||||
|
Function<CreateContext>("gxv3i+MTEzU")(&result.value, &context_params),
|
||||||
|
ERROR_INVALID_SHARED);
|
||||||
|
ASSERT_TRUE(result.Intact());
|
||||||
|
ASSERT_EQ(result.value, nullptr);
|
||||||
|
ASSERT_EQ(
|
||||||
|
Function<CreateContext>("vUk2pWMx3KQ")(&result.value, &context_params),
|
||||||
|
0);
|
||||||
|
ASSERT_TRUE(result.Intact());
|
||||||
|
extra_contexts.push_back(result.value);
|
||||||
|
float bias = 0;
|
||||||
|
EXPECT_EQ(Function<GetBias>("GHna9-DvnUk")(result.value, &bias, 480, 270), 0);
|
||||||
|
EXPECT_FLOAT_EQ(bias, -2.0f);
|
||||||
|
ASSERT_EQ(
|
||||||
|
Function<CreateContext>("2ecEbQaf9VU")(&result.value, &context_params),
|
||||||
|
0);
|
||||||
|
ASSERT_TRUE(result.Intact());
|
||||||
|
extra_contexts.push_back(result.value);
|
||||||
|
EXPECT_EQ(Function<GetBias>("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<CreateContext>("gxv3i+MTEzU")(&extra, &context_params),
|
||||||
|
0);
|
||||||
|
extra_contexts.push_back(extra);
|
||||||
|
}
|
||||||
|
void *fifth = nullptr;
|
||||||
|
EXPECT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&fifth, &context_params),
|
||||||
|
ERROR_TOO_MANY_CONTEXTS);
|
||||||
|
EXPECT_EQ(fifth, nullptr);
|
||||||
|
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(extra_contexts.back()), 0);
|
||||||
|
extra_contexts.pop_back();
|
||||||
|
ASSERT_EQ(Function<CreateContext>("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<uint32_t, 32> command;
|
||||||
|
command.fill(0xdeadbeef);
|
||||||
|
const CommandSpace space{command.data() + 1, 28};
|
||||||
|
ASSERT_EQ(Function<Dispatch>("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<GetSize>("AHalTX9wFZY")(context, &dwords), 0);
|
||||||
|
std::vector<uint32_t> command(dwords, 0xdeadbeef);
|
||||||
|
CommandSpace space{command.data(), dwords};
|
||||||
|
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms), 0);
|
||||||
|
std::fill(command.begin(), command.end(), 0xdeadbeef);
|
||||||
|
space.dwords = dwords - 1;
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_NO_SPACE);
|
||||||
|
space.dwords = 0x4002;
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_BAD_COMMAND_BUFFER);
|
||||||
|
space.dwords = dwords;
|
||||||
|
params.width = 961;
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_RENDER_SIZE);
|
||||||
|
params.width = 960;
|
||||||
|
output = Texture(960, 539, 0x10000);
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_OUTPUT_TEXTURE);
|
||||||
|
params.input = nullptr;
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_NULL_TEXTURE);
|
||||||
|
EXPECT_EQ(Function<Dispatch>("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<uint32_t, Pssr::RequestDwords + 1> command{};
|
||||||
|
const CommandSpace space{command.data(), command.size()};
|
||||||
|
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms), 0);
|
||||||
|
command.fill(0xdeadbeef);
|
||||||
|
params.width = 961;
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_RENDER_SIZE);
|
||||||
|
params.width = 960;
|
||||||
|
input[1] &= ~(0x1ffu << 20);
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_INVALID_TEXTURE);
|
||||||
|
input = Texture(960, 540, 0x20000);
|
||||||
|
input[3] = (input[3] & ~(0xfu << 28)) | (14u << 28);
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_INVALID_TEXTURE);
|
||||||
|
input = Texture(960, 540, 0x20000);
|
||||||
|
output[3] &= ~(0x1fu << 20);
|
||||||
|
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||||
|
ERROR_OUTPUT_TEXTURE);
|
||||||
|
output = Texture(960, 540, 0x20000);
|
||||||
|
EXPECT_EQ(Function<Dispatch>("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();
|
||||||
|
}
|
||||||
@@ -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<u32>(address >> 8);
|
||||||
|
result.fields[1] = static_cast<u32>(address >> 40) |
|
||||||
|
(static_cast<u32>(format) << 20) | (((width - 1) & 3) << 30);
|
||||||
|
result.fields[2] = ((width - 1) >> 2) | ((height - 1) << 14);
|
||||||
|
result.fields[3] = swizzle | (static_cast<u32>(tile) << 20) |
|
||||||
|
(static_cast<u32>(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<void *>(base);
|
||||||
|
Require(name, "map", LibKernel::Memory::KernelMapDirectMemory(
|
||||||
|
&mapped, size, 0x3, 0x10, physical, 0x10000) == 0 &&
|
||||||
|
mapped == reinterpret_cast<void *>(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<const u32> 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<u32> 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<u32> 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<uint64_t>(&sentinel);
|
||||||
|
packet.insert(packet.end(), {KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0), 0,
|
||||||
|
static_cast<u32>(address), static_cast<u32>(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<u32, 2>{red, green});
|
||||||
|
Require(name, "CPU-first bilinear source", run(input, output, 2, 1) ==
|
||||||
|
std::vector<u32>{red, 0x3a0 | (0x340 << 11), 0x340 | (0x3a0 << 11), green},
|
||||||
|
"first-use source upload or linear interpolation is wrong");
|
||||||
|
write_linear(input, std::array<u32, 2>{blue, blue});
|
||||||
|
Require(name, "CPU source refresh", run(input, output, 2, 1) ==
|
||||||
|
std::vector<u32>(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<u32, 2>{red, green});
|
||||||
|
Require(name, "same-size copy", run(input, output, 2, 1) ==
|
||||||
|
std::vector<u32>{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<u32> 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<u32>(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<u32, 2>{red, green});
|
||||||
|
Require(name, "sampled view swizzle", run(input, output, 2, 1) ==
|
||||||
|
std::vector<u32>{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<u32>{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<u32, 4>{0x00003c00, 0x3c000000,
|
||||||
|
0x3c000000, 0x3c000000});
|
||||||
|
Require(name, "float16 input", run(input, output, 2, 1) ==
|
||||||
|
std::vector<u32>{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<float, 4>{1, 0, 0, 1};
|
||||||
|
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
|
||||||
|
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear);
|
||||||
|
clear.color.float32 = std::array<float, 4>{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<u32>(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<u32>(8, red),
|
||||||
|
"upscaling modified the source layer of its shared native image");
|
||||||
|
clear.color.float32 = std::array<float, 4>{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<u32>(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<u32, 2>{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<u32>{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<float, 4>{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<float, 4>{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<u32>(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);
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "graphics/guest_gpu/graphicsRun.h"
|
#include "graphics/guest_gpu/graphicsRun.h"
|
||||||
#include "graphics/guest_gpu/hardwareContext.h"
|
#include "graphics/guest_gpu/hardwareContext.h"
|
||||||
#include "graphics/guest_gpu/pm4.h"
|
#include "graphics/guest_gpu/pm4.h"
|
||||||
|
#include "graphics/guest_gpu/pssr.h"
|
||||||
#include "graphics/guest_gpu/tile.h"
|
#include "graphics/guest_gpu/tile.h"
|
||||||
#include "graphics/host_gpu/hostMemory.h"
|
#include "graphics/host_gpu/hostMemory.h"
|
||||||
#include "graphics/host_gpu/memoryTracker.h"
|
#include "graphics/host_gpu/memoryTracker.h"
|
||||||
@@ -21825,6 +21826,8 @@ public:
|
|||||||
case_index, format_cases);
|
case_index, format_cases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "PssrGpuTests.inc"
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_rasterization_supported = true;
|
bool m_rasterization_supported = true;
|
||||||
u32 m_skipped_cases = 0;
|
u32 m_skipped_cases = 0;
|
||||||
@@ -51364,13 +51367,16 @@ void CheckPm4IndirectUconfigRegisters(RenderContext &renderer) {
|
|||||||
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
|
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
|
||||||
0x80000000u, 1u,
|
0x80000000u, 1u,
|
||||||
};
|
};
|
||||||
Pm4Execution execution;
|
|
||||||
const bool handler_present =
|
const bool handler_present =
|
||||||
g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] != nullptr;
|
g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] != nullptr;
|
||||||
|
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",
|
Require("Pm4IndirectUconfigRegisters", "indirect UCONFIG register stream",
|
||||||
handler_present &&
|
handler_present &&
|
||||||
processor.Process(execution, command) == Pm4ProcessResult::Complete,
|
processor.Process(execution, command) == Pm4ProcessResult::Complete,
|
||||||
"UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT");
|
"UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT");
|
||||||
|
}
|
||||||
std::printf("[host] %-32s ok\n", "Pm4IndirectUconfigRegisters");
|
std::printf("[host] %-32s ok\n", "Pm4IndirectUconfigRegisters");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53835,6 +53841,12 @@ int main(int argc, char **argv) {
|
|||||||
CheckPm4ContextStateOperations(vulkan.RuntimeRenderer());
|
CheckPm4ContextStateOperations(vulkan.RuntimeRenderer());
|
||||||
return 0;
|
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) {
|
if (argc == 2 && std::strcmp(argv[1], "--pm4-wait-only") == 0) {
|
||||||
VulkanHarness vulkan;
|
VulkanHarness vulkan;
|
||||||
CheckPm4AcquireMemPackets(vulkan.RuntimeRenderer());
|
CheckPm4AcquireMemPackets(vulkan.RuntimeRenderer());
|
||||||
|
|||||||
@@ -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 <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
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::Lifecycle>();
|
||||||
|
Config::ConfigOptions options;
|
||||||
|
options.printf_direction = Config::LogDirection::Silent;
|
||||||
|
Config::Load(options);
|
||||||
|
subsystems.Initialize<Log::Lifecycle>();
|
||||||
|
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<int(KYTY_SYSV_ABI*)()>(query->vaddr);
|
||||||
|
const auto operation_mode = reinterpret_cast<int(KYTY_SYSV_ABI*)(int*, int*)>(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<uintptr_t>(&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<uint32_t, 5> packet {};
|
||||||
|
CommandBuffer buffer {packet.data(), packet.data() + packet.size(), packet.data(),
|
||||||
|
packet.data() + packet.size(), nullptr, nullptr, 0};
|
||||||
|
Libs::Graphics::Gen5::AgcDcbSetUcRegistersIndirect(
|
||||||
|
reinterpret_cast<Libs::Graphics::Gen5::CommandBuffer*>(&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<Lifecycle>();
|
||||||
|
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<volatile uint64_t*>(mapped) = 0x123456789abcdef0ull;
|
||||||
|
EXPECT_EQ(*static_cast<volatile uint64_t*>(mapped), 0x123456789abcdef0ull);
|
||||||
|
EXPECT_EQ(KernelMunmap(reinterpret_cast<uint64_t>(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();
|
||||||
|
}
|
||||||
@@ -5708,7 +5708,12 @@ void TestTlsStubsPreserveFlags() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int RunTrinityTests();
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
if (argc == 2 && std::strcmp(argv[1], "--trinity-only") == 0) {
|
||||||
|
return RunTrinityTests();
|
||||||
|
}
|
||||||
InitSubsystems();
|
InitSubsystems();
|
||||||
if (argc == 2 && std::strcmp(argv[1], "--memory-bottlenecks-only") == 0) {
|
if (argc == 2 && std::strcmp(argv[1], "--memory-bottlenecks-only") == 0) {
|
||||||
RunTest(TestFlexibleZeroingTracksBackingLifetime);
|
RunTest(TestFlexibleZeroingTracksBackingLifetime);
|
||||||
|
|||||||
Reference in new issue
Block a user