Compare commits

...
Author SHA1 Message Date
nmzikandPhr3d13 b65dd18224 ngs2: let CLEAR reopen a closed waveform block list
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
nmzikandPhr3d13 2f6dccb10b ngs2: mix ports without matrix levels with the default matrix
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
nmzikandPhr3d13 08a7d7fbed ngs2: render the reverb voice dry signal
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
nmzikandPhr3d13 2aa0d58ffb ngs2: report voice port routing and volume
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
nmzikandPhr3d13 9bf311c6c7 ngs2: report voice setup channels from the setup query
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
nmzikandPhr3d13 e66a7f52b5 ngs2: apply mastering voice full band and LFE gain
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
nmzikandPhr3d13 67f8276751 ngs2: patch to a rack handle connects to its voice 0
Co-authored-by: Phr3d13 <phr3d13@gmail.com>
2026-10-30 01:57:41 +01:00
KairosMakkiandAjakfial 3ad21e35cc Rename near identifier to avoid Windows macro clash (#1983)
Co-authored-by: Ajakfial <ajakafial@tutamail.com>
2026-10-30 00:41:22 +01:00
nmzik 5eab0ec738 shader: add BVH8 intersections and instance transforms 2026-10-29 18:37:29 +01:00
nmzik bfd4c34b88 shader: add BVH stack operations 2026-10-29 18:37:29 +01:00
nmzik 6ff6e7551d graphics: use budget-aware preferred-device image allocation 2026-10-29 18:37:29 +01:00
nmzik 25f3ba4eae tests: cover low-alpha packed tiler conversion in both directions 2026-10-29 18:37:29 +01:00
Chai T 4592e4c178 kernel: destroy untouched static rwlocks without allocation 2026-10-29 18:32:14 +01:00
Chai T 97bef2992f appcontent: reject duplicate mounts without transferring ownership 2026-10-29 18:32:14 +01:00
nmzikandChai T 15452a5fee np: separate PSN emulation from host networking
Co-authored-by: Chai T <chaitukca@gmail.com>
2026-10-29 08:30:55 +01:00
nmzikandJoshua Jayasingh 651ef73a03 graphics: add experimental Pro mode and linear PSSR
Co-authored-by: Joshua Jayasingh <joshuajayasingh08@gmail.com>
2026-10-29 07:55:50 +01:00
64 changed files with 3432 additions and 388 deletions

No files matched your search

+18
View File
@@ -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
+8
View File
@@ -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;
} }
@@ -133,6 +137,10 @@ bool IsNetworkConnected() {
return g_config->network_connected; return g_config->network_connected;
} }
bool PsnEmulationEnabled() {
return g_config->psn_emulation_enabled;
}
bool ShaderValidationEnabled() { bool ShaderValidationEnabled() {
return g_config->shader_validation_enabled; return g_config->shader_validation_enabled;
} }
+4
View File
@@ -67,8 +67,10 @@ 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;
bool psn_emulation_enabled = false;
uint32_t vblank_frequency = 60; uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE; uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false; bool vulkan_validation_enabled = false;
@@ -128,8 +130,10 @@ bool HdrEnabled();
bool HideCursorEnabled(); bool HideCursorEnabled();
bool StretchEnabled(); bool StretchEnabled();
bool VrEnabled(); bool VrEnabled();
bool Ps5ProEnabled();
bool AmdCpuEnabled(); bool AmdCpuEnabled();
bool IsNetworkConnected(); bool IsNetworkConnected();
bool PsnEmulationEnabled();
uint32_t GetVblankFrequency(); uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage(); uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled(); bool VulkanValidationEnabled();
@@ -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();
+5
View File
@@ -929,6 +929,11 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
} }
} }
void CommandProcessor::ProcessPssr(const Pssr::Request& request) {
EXIT_NOT_IMPLEMENTED(IsAsyncComputeQueue());
m_renderer.GetRenderExecutor().ProcessPssr(CurrentBuffer(), request);
}
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) { 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) {
+1
View File
@@ -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;
+20
View File
@@ -0,0 +1,20 @@
#ifndef EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
#define EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
#include <cstdint>
namespace Libs::Graphics::Pssr {
struct Request {
uint32_t output[8];
uint32_t input[8];
uint32_t width;
uint32_t height;
};
constexpr uint32_t RequestDwords = sizeof(Request) / sizeof(uint32_t);
static_assert(sizeof(Request) == 72);
} // namespace Libs::Graphics::Pssr
#endif // EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
+9
View File
@@ -150,15 +150,21 @@ private:
friend class CommandScheduler; 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), &parameters);
command.dispatch((parameters.output_size[0] + 7) / 8,
(parameters.output_size[1] + 7) / 8, 1);
ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader);
ResetBindings();
}
} // namespace Libs::Graphics
@@ -0,0 +1,33 @@
#version 450 core
#extension GL_EXT_samplerless_texture_functions : require
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform texture2D source;
layout(binding = 1) writeonly uniform image2D destination;
layout(push_constant) uniform Parameters {
uvec2 input_size;
uvec2 output_size;
uvec4 output_channels;
} parameters;
vec4 fetch_color(ivec2 coordinate) {
return texelFetch(source, clamp(coordinate, ivec2(0), ivec2(parameters.input_size) - 1), 0);
}
void main() {
uvec2 output_coordinate = gl_GlobalInvocationID.xy;
if (any(greaterThanEqual(output_coordinate, parameters.output_size))) return;
vec2 input_coordinate = (vec2(output_coordinate) + 0.5) *
vec2(parameters.input_size) / vec2(parameters.output_size) - 0.5;
ivec2 lower = ivec2(floor(input_coordinate));
vec2 weight = fract(input_coordinate);
vec4 color = mix(mix(fetch_color(lower), fetch_color(lower + ivec2(1, 0)), weight.x),
mix(fetch_color(lower + ivec2(0, 1)), fetch_color(lower + ivec2(1, 1)), weight.x),
weight.y);
vec4 stored = vec4(0.0);
for (uint channel = 0; channel < 3; ++channel) {
uint selected = parameters.output_channels[channel];
if (selected < 4) stored[channel] = color[selected];
}
imageStore(destination, ivec2(output_coordinate), stored);
}
+2 -1
View File
@@ -151,7 +151,8 @@ bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanIm
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr); EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
VmaAllocationCreateInfo alloc_info {}; VmaAllocationCreateInfo alloc_info {};
alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; alloc_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
alloc_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
vk::Image::CType native_image = VK_NULL_HANDLE; vk::Image::CType native_image = VK_NULL_HANDLE;
const auto result = static_cast<vk::Result>( const auto result = static_cast<vk::Result>(
@@ -30,16 +30,7 @@ uint32_t NodeWord(EmitterState& s, uint32_t block, uint32_t word) {
uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary, uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
Vec3 origin, Vec3 direction) { Vec3 origin, Vec3 direction) {
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s); const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
const auto zero = ConstantF32Value(s, 0.0f);
const auto op = [&](spv::Op code, uint32_t x, uint32_t y) {
const auto value = Binary(s, code, f, x, y);
s.builder.AddAnnotation(spv::OpDecorate, value, spv::DecorationNoContraction);
return value;
};
const auto cmp = [&](spv::Op code, uint32_t x, uint32_t y) { return Binary(s, code, b, x, y); }; const auto cmp = [&](spv::Op code, uint32_t x, uint32_t y) { return Binary(s, code, b, x, y); };
const auto either = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); };
const auto both = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); };
const auto abs = [&](uint32_t x) { return EmitGlsl<GLSLstd450FAbs, IR::Type::F32>(s, x); };
const auto is_kind = [&](uint32_t n) { return cmp(spv::OpIEqual, kind, ConstantU32(s, n)); }; const auto is_kind = [&](uint32_t n) { return cmp(spv::OpIEqual, kind, ConstantU32(s, n)); };
// The four triangles are (0,1,2), (1,3,2), (2,3,4), (2,4,0). // The four triangles are (0,1,2), (1,3,2), (2,3,4), (2,4,0).
const std::array vertices { const std::array vertices {
@@ -56,6 +47,28 @@ uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
NodeWord(s, block, Binary(s, spv::OpIAdd, u, first, ConstantU32(s, axis)))); NodeWord(s, block, Binary(s, spv::OpIAdd, u, first, ConstantU32(s, axis))));
} }
const auto flag = NodeWord(s, block, ConstantU32(s, 15)); const auto flag = NodeWord(s, block, ConstantU32(s, 15));
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag,
Binary(s, spv::OpIMul, u, kind, ConstantU32(s, 8)));
return EmitBvhTriangle(s, positions, origin, direction, remap,
Binary(s, spv::OpIAdd, u, flag, kind), bary);
}
} // namespace
uint32_t EmitBvhTriangle(EmitterState& s, const std::array<Vec3, 3>& positions,
Vec3 origin, Vec3 direction, uint32_t remap,
uint32_t triangle_id, uint32_t bary) {
const auto f = TypeF32(s), u = TypeU32(s), b = TypeBool(s);
const auto zero = ConstantF32Value(s, 0.0f);
const auto op = [&](spv::Op code, uint32_t x, uint32_t y) {
const auto value = Binary(s, code, f, x, y);
s.builder.AddAnnotation(spv::OpDecorate, value, spv::DecorationNoContraction);
return value;
};
const auto cmp = [&](spv::Op code, uint32_t x, uint32_t y) { return Binary(s, code, b, x, y); };
const auto either = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); };
const auto both = [&](uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); };
const auto abs = [&](uint32_t x) { return EmitGlsl<GLSLstd450FAbs, IR::Type::F32>(s, x); };
const auto y_largest = cmp(spv::OpFOrdLessThan, abs(direction[0]), abs(direction[1])); const auto y_largest = cmp(spv::OpFOrdLessThan, abs(direction[0]), abs(direction[1]));
const auto z_largest = cmp(spv::OpFOrdLessThan, const auto z_largest = cmp(spv::OpFOrdLessThan,
Select(s, f, y_largest, abs(direction[1]), abs(direction[0])), abs(direction[2])); Select(s, f, y_largest, abs(direction[1]), abs(direction[0])), abs(direction[2]));
@@ -112,8 +125,6 @@ uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
cmp(spv::OpFOrdGreaterThan, projected[i][1], zero), right); cmp(spv::OpFOrdGreaterThan, projected[i][1], zero), right);
missed = either(missed, both(cmp(spv::OpFOrdEqual, edge[i], zero), excluded)); missed = either(missed, both(cmp(spv::OpFOrdEqual, edge[i], zero), excluded));
} }
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag,
Binary(s, spv::OpIMul, u, kind, ConstantU32(s, 8)));
const auto coordinate = [&](uint32_t shift) { const auto coordinate = [&](uint32_t shift) {
const auto index = Binary(s, spv::OpBitwiseAnd, u, const auto index = Binary(s, spv::OpBitwiseAnd, u,
Binary(s, spv::OpShiftRightLogical, u, remap, ConstantU32(s, shift)), ConstantU32(s, 3)); Binary(s, spv::OpShiftRightLogical, u, remap, ConstantU32(s, shift)), ConstantU32(s, 3));
@@ -124,11 +135,13 @@ uint32_t Triangle(EmitterState& s, uint32_t block, uint32_t kind, uint32_t bary,
const std::array result { const std::array result {
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32(s, 0x7f800000), numerator)), Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32(s, 0x7f800000), numerator)),
Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32Value(s, 1.0f), denominator)), Unary(s, spv::OpBitcast, u, Select(s, f, missed, ConstantF32Value(s, 1.0f), denominator)),
Select(s, u, bary, coordinate(0), Binary(s, spv::OpIAdd, u, flag, kind)), Select(s, u, bary, coordinate(0), triangle_id),
Select(s, u, bary, coordinate(2), Select(s, u, missed, ConstantU32(s, 0), ConstantU32(s, 1))) }; Select(s, u, bary, coordinate(2), Select(s, u, missed, ConstantU32(s, 0), ConstantU32(s, 1))) };
return Vector(s, TypeU32Vector(s, 4), result); return Vector(s, TypeU32Vector(s, 4), result);
} }
namespace {
std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second, std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second,
uint32_t half, uint32_t child) { uint32_t half, uint32_t child) {
const auto f = TypeF32(s); const auto f = TypeF32(s);
@@ -338,6 +338,9 @@ inline constexpr auto EmitDataConsume = EmitAppendConsume;
uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitSwizzleU32(ValueEmitContext& ctx, const IR::Inst& inst);
void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst); void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitBvhIntersect(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t EmitBvhIntersect(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitBvh8Intersect(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitBvh8Extract(ValueEmitContext& ctx, const IR::Inst& inst);
uint32_t EmitBvhStack(ValueEmitContext& ctx, const IR::Inst& inst);
inline constexpr auto EmitImageQueryDimensions = EmitImage; inline constexpr auto EmitImageQueryDimensions = EmitImage;
inline constexpr auto EmitImageQueryLod = EmitImage; inline constexpr auto EmitImageQueryLod = EmitImage;
inline constexpr auto EmitImageRead = EmitImage; inline constexpr auto EmitImageRead = EmitImage;
@@ -120,6 +120,9 @@ struct EmitterState {
uint32_t indirect_format_geometry_function = 0; uint32_t indirect_format_geometry_function = 0;
std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions; std::map<std::array<uint32_t, 4>, uint32_t> indirect_buffer_load_functions;
uint32_t bvh_intersect_function = 0; uint32_t bvh_intersect_function = 0;
uint32_t bvh8_result_type = 0;
uint32_t bvh8_intersect_function = 0;
uint32_t bvh_stack_function = 0;
uint32_t gds_variable = 0; uint32_t gds_variable = 0;
uint32_t gds_length = 0; uint32_t gds_length = 0;
uint32_t push_constant_variable = 0; uint32_t push_constant_variable = 0;
@@ -169,6 +172,7 @@ uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
uint32_t TypeU32(EmitterState& state); uint32_t TypeU32(EmitterState& state);
uint32_t TypeU64(EmitterState& state); uint32_t TypeU64(EmitterState& state);
uint32_t TypeU32Pair(EmitterState& state); uint32_t TypeU32Pair(EmitterState& state);
uint32_t TypeBvh8Result(EmitterState& state);
uint32_t TypeI32(EmitterState& state); uint32_t TypeI32(EmitterState& state);
uint32_t TypeI32Pair(EmitterState& state); uint32_t TypeI32Pair(EmitterState& state);
uint32_t TypeF32(EmitterState& state); uint32_t TypeF32(EmitterState& state);
@@ -542,6 +546,11 @@ void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state); void DefineGetBdaPointer(EmitterState& state);
void DefineIndirectBufferLoads(ValueEmitContext& ctx); void DefineIndirectBufferLoads(ValueEmitContext& ctx);
void DefineBvhIntersect(EmitterState& state); void DefineBvhIntersect(EmitterState& state);
void DefineTrinityBvhIntersect(EmitterState& state);
uint32_t EmitBvhTriangle(EmitterState& state, const std::array<std::array<uint32_t, 3>, 3>& positions,
std::array<uint32_t, 3> origin, std::array<uint32_t, 3> direction,
uint32_t remap, uint32_t triangle_id, uint32_t bary);
void DefineTrinityFunctions(EmitterState& state);
uint32_t GetBdaPointer(EmitterState& state, uint32_t address); uint32_t GetBdaPointer(EmitterState& state, uint32_t address);
// These templates accept local lambdas from several emitter translation units. // These templates accept local lambdas from several emitter translation units.
@@ -414,6 +414,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type) {
case IR::Type::U32: case IR::Type::U32:
case IR::Type::F16: return TypeU32(state); case IR::Type::F16: return TypeU32(state);
case IR::Type::U64: return TypeU64(state); case IR::Type::U64: return TypeU64(state);
case IR::Type::Bvh8Result: return TypeBvh8Result(state);
case IR::Type::U32x2: return TypeU32Pair(state); case IR::Type::U32x2: return TypeU32Pair(state);
case IR::Type::F32: return TypeF32(state); case IR::Type::F32: return TypeF32(state);
case IR::Type::F64: return TypeF64(state); case IR::Type::F64: return TypeF64(state);
@@ -676,6 +677,7 @@ void EmitProgram(EmitterState& state) {
DefineGetBdaPointer(state); DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx); DefineIndirectBufferLoads(ctx);
DefineBvhIntersect(state); DefineBvhIntersect(state);
DefineTrinityFunctions(state);
if (program.info.uses_swizzle) { if (program.info.uses_swizzle) {
ctx.scratch_u32_variable = state.builder.AllocateId(); ctx.scratch_u32_variable = state.builder.AllocateId();
if (state.lane_count == 2) { if (state.lane_count == 2) {
@@ -0,0 +1,208 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t Bits(EmitterState& s, uint32_t value, uint32_t offset, uint32_t count) {
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(s), result, value,
ConstantU32(s, offset), ConstantU32(s, count));
return result;
}
uint32_t SetBits(EmitterState& s, uint32_t value, uint32_t field, uint32_t offset, uint32_t count) {
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpBitFieldInsert, TypeU32(s), result, value, field,
ConstantU32(s, offset), ConstantU32(s, count));
return result;
}
uint32_t Pair(EmitterState& s, uint32_t first, uint32_t second) {
const auto result = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Pair(s), result, first, second);
return result;
}
uint32_t StackIndex(EmitterState& s, uint32_t control, uint32_t ring) {
return EmitAddU32(s, Bits(s, control, 15, 14),
Binary(s, spv::OpShiftLeftLogical, TypeU32(s), ring, ConstantU32(s, 5)));
}
uint32_t StackPush(EmitterState& s, const MemoryResourceAccess& memory,
uint32_t control, uint32_t node, uint32_t capacity) {
const auto u = TypeU32(s), b = TypeBool(s), zero = ConstantU32(s, 0), one = ConstantU32(s, 1);
const auto count = Bits(s, control, 0, 5), parent = Bits(s, control, 5, 5), ring = Bits(s, control, 10, 5);
const auto index = StackIndex(s, control, ring);
EmitIfCondition(s, EmitMemoryElementInBounds(s, memory, index), [&] {
s.builder.AddFunction(spv::OpStore, EmitMemoryElementPointer(s, memory, index), node);
});
control = Binary(s, spv::OpBitwiseOr, u, control,
Select(s, u, Binary(s, spv::OpIEqual, b, count, capacity), ConstantU32(s, 1u << 30), zero));
control = Select(s, u, Binary(s, spv::OpIEqual, b, parent, capacity),
Binary(s, spv::OpBitwiseAnd, u, control, ConstantU32(s, ~(1u << 29))), control);
control = SetBits(s, control, EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
EmitAddU32(s, count, one), capacity), 0, 5);
control = SetBits(s, control, EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
EmitAddU32(s, parent, one), capacity), 5, 5);
const auto next = EmitAddU32(s, ring, one);
return SetBits(s, control, Select(s, u, Binary(s, spv::OpIEqual, b, next, capacity), zero, next), 10, 5);
}
uint32_t StackPop(EmitterState& s, const MemoryResourceAccess& memory,
uint32_t control, uint32_t capacity) {
const auto u = TypeU32(s), b = TypeBool(s), zero = ConstantU32(s, 0), one = ConstantU32(s, 1);
const auto count = Bits(s, control, 0, 5), parent = Bits(s, control, 5, 5), ring = Bits(s, control, 10, 5);
const auto has_parent = Binary(s, spv::OpINotEqual, b, Bits(s, control, 29, 1), zero);
const auto returning = Binary(s, spv::OpLogicalAnd, b, has_parent,
Binary(s, spv::OpIEqual, b, parent, zero));
control = SetBits(s, control, Select(s, u, returning, one, zero), 31, 1);
control = Select(s, u, returning,
Binary(s, spv::OpBitwiseAnd, u, control, ConstantU32(s, ~(1u << 29))), control);
const auto overflow = Binary(s, spv::OpINotEqual, b, Bits(s, control, 30, 1), zero);
const auto empty = Pair(s,
Binary(s, spv::OpBitwiseAnd, u, control, ConstantU32(s, 0x5fffffff)),
Select(s, u, overflow, ConstantU32(s, 0xffffffff), ConstantU32(s, 0xfffffffe)));
return EmitValueOrDefaultIfCondition(s, Binary(s, spv::OpINotEqual, b, count, zero),
TypeU32Pair(s), empty, [&] {
control = SetBits(s, control, Binary(s, spv::OpISub, u, count, one), 0, 5);
control = SetBits(s, control, Binary(s, spv::OpISub, u, parent, one), 5, 5);
const auto previous = Select(s, u, Binary(s, spv::OpIEqual, b, ring, zero),
Binary(s, spv::OpISub, u, capacity, one), Binary(s, spv::OpISub, u, ring, one));
control = SetBits(s, control, previous, 10, 5);
const auto index = StackIndex(s, control, previous);
const auto node = EmitValueOrDefaultIfCondition(s,
EmitMemoryElementInBounds(s, memory, index), u, zero, [&] {
const auto value = s.builder.AllocateId();
s.builder.AddFunction(spv::OpLoad, u, value, EmitMemoryElementPointer(s, memory, index));
return value;
});
const auto instance = Binary(s, spv::OpIEqual, b, Bits(s, node, 0, 3), ConstantU32(s, 6));
control = EmitValueOrDefaultIfCondition(s, instance, u, control, [&] {
const auto nested = Binary(s, spv::OpINotEqual, b, Bits(s, control, 29, 1), zero);
const auto discarded = Binary(s, spv::OpBitwiseOr, u,
SetBits(s, control, Bits(s, control, 5, 5), 0, 5), ConstantU32(s, 1u << 30));
return Binary(s, spv::OpBitwiseOr, u,
SetBits(s, Select(s, u, nested, discarded, control), zero, 5, 5), ConstantU32(s, 1u << 29));
});
return Pair(s, control, node);
});
}
void DefineBvhStack(EmitterState& s) {
if (!s.program.info.bvh_stack) return;
const auto u = TypeU32(s), b = TypeBool(s), vec4 = TypeU32Vector(s, 4), pair = TypeU32Pair(s);
const auto zero = ConstantU32(s, 0), one = ConstantU32(s, 1);
const bool local = s.lds_storage_class == spv::StorageClassFunction;
const bool buffer = s.lds_storage_class == spv::StorageClassStorageBuffer;
std::vector<uint32_t> types {u, u, vec4, vec4, u};
if (local) types.push_back(TypeU32ArrayPointer(s, spv::StorageClassFunction, LdsDwordCount(s)));
if (buffer) types.push_back(u);
const auto signature = s.builder.Type(spv::OpTypeFunction, pair, types);
s.bvh_stack_function = s.builder.AllocateId();
s.builder.AddName(s.bvh_stack_function, "trinity_bvh_stack");
s.builder.AddFunction(spv::OpFunction, pair, s.bvh_stack_function, spv::FunctionControlMaskNone, signature);
std::vector<uint32_t> args;
for (const auto type: types) {
args.push_back(s.builder.AllocateId());
s.builder.AddFunction(spv::OpFunctionParameter, type, args.back());
}
const auto old_lds = s.lds_variable, old_base = s.lds_base_dwords;
if (local) s.lds_variable = args.back();
if (buffer) s.lds_base_dwords = args.back();
const auto memory = PrepareMemoryResourceAccess(s, {.kind = IR::ResourceKind::Lds});
const auto entry = s.builder.AllocateId();
EmitLabel(s, entry);
const auto last = args[1], capacity = args[4];
const auto walkback = Binary(s, spv::OpINotEqual, b, Bits(s, last, 3, 29), ConstantU32(s, 0x1fffffff));
const auto last4 = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeConstruct, vec4, last4, last, last, last, last);
const auto found = [&](uint32_t values) {
return Unary(s, spv::OpAny, b, Binary(s, spv::OpIEqual, TypeBoolVector(s, 4), values, last4));
};
const auto initial_valid = Binary(s, spv::OpLogicalOr, b, Unary(s, spv::OpLogicalNot, b, walkback),
Binary(s, spv::OpLogicalOr, b, found(args[2]), found(args[3])));
const auto header = s.builder.AllocateId(), body = s.builder.AllocateId();
const auto cont = s.builder.AllocateId(), merge = s.builder.AllocateId();
s.builder.AddFunction(spv::OpBranch, header);
EmitLabel(s, header);
const auto index = s.builder.AllocateId(), control = s.builder.AllocateId();
const auto valid = s.builder.AllocateId(), skip_low = s.builder.AllocateId(), skip_high = s.builder.AllocateId();
const auto index_phi = s.builder.AddDeferredPhi(u, index, 2);
const auto control_phi = s.builder.AddDeferredPhi(u, control, 2);
const auto valid_phi = s.builder.AddDeferredPhi(b, valid, 2);
const auto low_phi = s.builder.AddDeferredPhi(b, skip_low, 2);
const auto high_phi = s.builder.AddDeferredPhi(b, skip_high, 2);
const auto more = Binary(s, spv::OpLogicalAnd, b, valid,
Binary(s, spv::OpULessThan, b, index, ConstantU32(s, 8)));
s.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlDontUnrollMask);
s.builder.AddFunction(spv::OpBranchConditional, more, body, merge);
EmitLabel(s, body);
const auto high = Binary(s, spv::OpUGreaterThanEqual, b, index, ConstantU32(s, 4));
const auto high4 = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeConstruct, TypeBoolVector(s, 4), high4, high, high, high, high);
const auto values = Select(s, vec4, high4, args[3], args[2]);
const auto node = s.builder.AllocateId();
s.builder.AddFunction(spv::OpVectorExtractDynamic, u, node, values, Bits(s, index, 0, 2));
const auto sentinel = Binary(s, spv::OpIEqual, b, Bits(s, node, 3, 29), ConstantU32(s, 0x1fffffff));
const auto enabled = Unary(s, spv::OpLogicalNot, b, Select(s, b, high, skip_high, skip_low));
const auto marker = Binary(s, spv::OpLogicalAnd, b, sentinel, enabled);
const auto next_low = Binary(s, spv::OpLogicalOr, b, skip_low,
Binary(s, spv::OpLogicalAnd, b, marker, Binary(s, spv::OpIEqual, b, Bits(s, node, 1, 1), zero)));
const auto next_high = Binary(s, spv::OpLogicalOr, b, skip_high,
Binary(s, spv::OpLogicalAnd, b, high,
Binary(s, spv::OpLogicalAnd, b, marker, Binary(s, spv::OpIEqual, b, Bits(s, node, 2, 1), zero))));
const auto next_valid = Unary(s, spv::OpLogicalNot, b,
Binary(s, spv::OpLogicalAnd, b, walkback, Binary(s, spv::OpIEqual, b, node, last)));
const auto push = Binary(s, spv::OpLogicalAnd, b, next_valid,
Unary(s, spv::OpLogicalNot, b,
Binary(s, spv::OpLogicalOr, b, sentinel, Select(s, b, high, next_high, next_low))));
const auto next_control = EmitValueOrDefaultIfCondition(s, push, u, control, [&] {
return StackPush(s, memory, control, node, capacity);
});
s.builder.AddFunction(spv::OpBranch, cont);
EmitLabel(s, cont);
const auto next_index = Binary(s, spv::OpISub, u, index, one);
s.builder.AddFunction(spv::OpBranch, header);
s.builder.PatchDeferredPhi(index_phi, 0, ConstantU32(s, 7), entry);
s.builder.PatchDeferredPhi(index_phi, 1, next_index, cont);
s.builder.PatchDeferredPhi(control_phi, 0, args[0], entry);
s.builder.PatchDeferredPhi(control_phi, 1, next_control, cont);
s.builder.PatchDeferredPhi(valid_phi, 0, initial_valid, entry);
s.builder.PatchDeferredPhi(valid_phi, 1, next_valid, cont);
s.builder.PatchDeferredPhi(low_phi, 0, ConstantBool(s, false), entry);
s.builder.PatchDeferredPhi(low_phi, 1, next_low, cont);
s.builder.PatchDeferredPhi(high_phi, 0, ConstantBool(s, false), entry);
s.builder.PatchDeferredPhi(high_phi, 1, next_high, cont);
EmitLabel(s, merge);
s.builder.AddFunction(spv::OpReturnValue, StackPop(s, memory, control, capacity));
s.builder.AddFunction(spv::OpFunctionEnd);
if (local) s.lds_variable = old_lds;
if (buffer) s.lds_base_dwords = old_base;
}
} // namespace
void DefineTrinityFunctions(EmitterState& s) {
DefineBvhStack(s);
DefineTrinityBvhIntersect(s);
}
uint32_t EmitBvhStack(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& s = ctx.state;
const auto capacity = ctx.Memory(inst).offset;
if (s.bvh_stack_function == 0 || capacity < 8 || capacity > 31)
ctx.Fail(inst, "invalid BVH stack function or capacity");
const std::array values {ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)};
return EmitValueOrDefaultIfCondition(s, ctx.Arg(inst, 4), TypeU32Pair(s),
ConstantU32CompositeZero(s, 2), [&] {
const auto result = s.builder.AllocateId();
std::vector<uint32_t> call {spv::OpFunctionCall, TypeU32Pair(s), result,
s.bvh_stack_function, values[0], values[1], values[2], values[3], ConstantU32(s, capacity)};
if (s.lds_storage_class == spv::StorageClassFunction) call.push_back(s.lds_variable);
if (s.lds_storage_class == spv::StorageClassStorageBuffer) call.push_back(s.lds_base_dwords);
s.builder.AddFunction(call);
return result;
});
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,511 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t TypeBvh8Result(EmitterState& s) {
if (s.bvh8_result_type == 0) {
const auto v = TypeU32Vector(s, 4);
s.bvh8_result_type = s.builder.Type(spv::OpTypeStruct, v, v, v, v, TypeU32(s));
}
return s.bvh8_result_type;
}
namespace {
using Vec3 = std::array<uint32_t, 3>;
struct Bvh8Emitter {
EmitterState& s;
const uint32_t u = TypeU32(s), f = TypeF32(s), b = TypeBool(s), w = TypeU64(s);
const uint32_t v4 = TypeU32Vector(s, 4), v3 = TypeF32Vector(s, 3);
uint32_t triangle_function = 0, boxes_function = 0, decompress_function = 0;
uint32_t C(uint32_t x) { return ConstantU32(s, x); }
uint32_t And(uint32_t x, uint32_t y) { return Binary(s, spv::OpBitwiseAnd, u, x, y); }
uint32_t Or(uint32_t x, uint32_t y) { return Binary(s, spv::OpBitwiseOr, u, x, y); }
uint32_t Add(uint32_t x, uint32_t y) { return EmitAddU32(s, x, y); }
uint32_t Sub(uint32_t x, uint32_t y) { return Binary(s, spv::OpISub, u, x, y); }
uint32_t Shl(uint32_t x, uint32_t n) { return Binary(s, spv::OpShiftLeftLogical, u, x, C(n)); }
uint32_t Shr(uint32_t x, uint32_t n) { return Binary(s, spv::OpShiftRightLogical, u, x, C(n)); }
uint32_t Eq(uint32_t x, uint32_t y) { return Binary(s, spv::OpIEqual, b, x, y); }
uint32_t Both(uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalAnd, b, x, y); }
uint32_t Either(uint32_t x, uint32_t y) { return Binary(s, spv::OpLogicalOr, b, x, y); }
uint32_t Not(uint32_t x) { return Unary(s, spv::OpLogicalNot, b, x); }
uint32_t Bit(uint32_t x, uint32_t n) { return Not(Eq(And(x, C(1u << n)), C(0))); }
uint32_t Choose(uint32_t condition, uint32_t x, uint32_t y) { return Select(s, u, condition, x, y); }
uint32_t Float(uint32_t x) { return Unary(s, spv::OpBitcast, f, x); }
uint32_t Raw(uint32_t x) { return Unary(s, spv::OpBitcast, u, x); }
uint32_t Fop(spv::Op op, uint32_t x, uint32_t y) { return Binary(s, op, f, x, y); }
uint32_t Fcmp(spv::Op op, uint32_t x, uint32_t y) { return Binary(s, op, b, x, y); }
uint32_t Min(uint32_t x, uint32_t y) { return EmitGlsl<GLSLstd450FMin, IR::Type::F32>(s, x, y); }
uint32_t Max(uint32_t x, uint32_t y) { return EmitGlsl<GLSLstd450FMax, IR::Type::F32>(s, x, y); }
uint32_t Extract(uint32_t type, uint32_t value, uint32_t index) {
const auto id = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeExtract, type, id, value, index);
return id;
}
uint32_t Make(uint32_t type, std::span<const uint32_t> values) {
const auto id = s.builder.AllocateId();
s.builder.AddFunction(spv::OpCompositeConstruct, type, id, values);
return id;
}
uint32_t Make4(uint32_t x, uint32_t y, uint32_t z, uint32_t t) {
return Make(v4, std::array{x, y, z, t});
}
Vec3 Unpack3(uint32_t value) {
return {Extract(f, value, 0), Extract(f, value, 1), Extract(f, value, 2)};
}
uint32_t Word(uint32_t block, uint32_t index) {
const auto offset = Unary(s, spv::OpUConvert, w, Shl(index, 2));
const auto address = Binary(s, spv::OpIAdd, w, block, offset);
const auto pointer = Unary(s, spv::OpConvertUToPtr, TypePhysicalU32Pointer(s), address);
const auto id = s.builder.AllocateId();
s.builder.AddFunction(spv::OpLoad, u, id, pointer, spv::MemoryAccessAlignedMask, 4u);
return id;
}
uint32_t Call(uint32_t type, uint32_t function, std::span<const uint32_t> args) {
const auto id = s.builder.AllocateId();
std::vector<uint32_t> call {spv::OpFunctionCall, type, id, function};
call.insert(call.end(), args.begin(), args.end());
s.builder.AddFunction(call);
return id;
}
std::vector<uint32_t> Begin(uint32_t& function, const char* name, uint32_t result,
std::span<const uint32_t> types) {
function = s.builder.AllocateId();
s.builder.AddName(function, name);
s.builder.AddFunction(spv::OpFunction, result, function, spv::FunctionControlMaskNone,
s.builder.Type(spv::OpTypeFunction, result, types));
std::vector<uint32_t> args;
for (auto type: types) {
args.push_back(s.builder.AllocateId());
s.builder.AddFunction(spv::OpFunctionParameter, type, args.back());
}
EmitLabel(s, s.builder.AllocateId());
return args;
}
void End(uint32_t result) {
s.builder.AddFunction(spv::OpReturnValue, result);
s.builder.AddFunction(spv::OpFunctionEnd);
}
uint32_t Result(const std::array<uint32_t, 10>& dst, const Vec3& org, const Vec3& dir,
uint32_t instance) {
const std::array groups {Make4(dst[0], dst[1], dst[2], dst[3]),
Make4(dst[4], dst[5], dst[6], dst[7]), Make4(dst[8], dst[9], Raw(org[0]), Raw(org[1])),
Make4(Raw(org[2]), Raw(dir[0]), Raw(dir[1]), Raw(dir[2])), instance};
return Make(TypeBvh8Result(s), groups);
}
uint32_t NormalizeFlags(uint32_t flags) {
flags = Choose(Bit(flags, 2), And(flags, C(~0x30u)), flags);
return Or(flags, Choose(Eq(And(flags, C(0x30)), C(0x30)), C(0x100), C(0)));
}
void DefineTriangle() {
const auto a = Begin(triangle_function, "trinity_triangle", v4,
std::array{w, u, u, u, v3, v3});
const auto block = a[0], slot = a[1], flags = a[2], descriptor = a[3];
const auto origin = Unpack3(a[4]), direction = Unpack3(a[5]);
const auto flag_word = Word(block, C(15));
const auto remap = Binary(s, spv::OpShiftRightLogical, u, flag_word, Shl(slot, 3));
const auto bary = Bit(descriptor, 24);
const auto pointer_flags = Both(bary, Bit(descriptor, 23));
const auto tri_flags = Choose(pointer_flags, remap, C(0));
const auto procedural = Bit(tri_flags, 6);
const auto raw = EmitValueOrDefaultIfCondition(s, Not(procedural), v4,
Make4(C(0), C(0x3f800000), C(0x3f800000), C(0x3f800000)), [&] {
std::array<Vec3, 3> positions;
const std::array vertices {slot, Choose(Eq(slot, C(0)), C(1), C(3)), C(2)};
for (uint32_t vertex = 0; vertex < 3; ++vertex)
for (uint32_t axis = 0; axis < 3; ++axis)
positions[vertex][axis] = Float(Word(block,
Add(Binary(s, spv::OpIMul, u, vertices[vertex], C(3)), C(axis))));
return EmitBvhTriangle(s, positions, origin, direction, remap, Add(flag_word, slot), bary);
});
std::array<uint32_t, 4> result;
for (uint32_t i = 0; i < 4; ++i) result[i] = Extract(u, raw, i);
auto missed = Eq(result[0], C(0x7f800000));
const auto flip = Both(Both(Not(procedural), Not(missed)),
Binary(s, spv::OpLogicalNotEqual, b, Bit(flags, 3), Bit(tri_flags, 5)));
for (uint32_t i = 0; i < 4; ++i)
result[i] = Binary(s, spv::OpBitwiseXor, u, result[i],
Choose(i < 2 ? flip : Both(flip, bary), C(0x80000000), C(0)));
const auto front = Fcmp(spv::OpFOrdGreaterThan, Float(result[1]), ConstantF32Value(s, 0.0f));
const auto opaque = Both(Either(Bit(tri_flags, 7), Bit(flags, 0)), Not(Bit(flags, 1)));
missed = Either(missed, Either(Both(opaque, Bit(flags, 6)), Both(Not(opaque), Bit(flags, 7))));
missed = Either(missed, Both(Both(Not(Bit(tri_flags, 4)), Not(procedural)),
Select(s, b, front, Bit(flags, 5), Bit(flags, 4))));
missed = Either(missed, Select(s, b, procedural, Bit(flags, 9), Bit(flags, 8)));
const auto sign = [&](uint32_t value, uint32_t enabled) {
return Choose(pointer_flags, Or(And(value, C(0x7fffffff)),
Choose(enabled, C(0x80000000), C(0))), value);
};
result[2] = sign(result[2], Either(Not(opaque), procedural));
result[3] = sign(result[3], Not(opaque));
const std::array miss {C(0x7f800000), C(0x3f800000), Choose(bary, C(0), C(~0u)), C(0)};
for (uint32_t i = 0; i < 4; ++i) result[i] = Choose(missed, miss[i], result[i]);
End(Make(v4, result));
}
uint32_t Instance(uint32_t block, uint32_t flags, uint32_t mask, Vec3 org, Vec3 dir) {
const auto lo = Word(block, C(12)), hi = Word(block, C(13));
const auto inst_flags = Shr(hi, 22);
auto merged = Or(And(Or(flags, inst_flags), C(0x3f0)), And(inst_flags, C(4)));
merged = Or(merged, And(Binary(s, spv::OpBitwiseXor, u, flags, inst_flags), C(8)));
merged = Or(merged, Choose(Either(Bit(flags, 0), Both(Bit(inst_flags, 0), Not(Bit(flags, 1)))), C(1), C(0)));
merged = Or(merged, Choose(Either(Bit(flags, 1), Both(Bit(inst_flags, 1), Not(Bit(flags, 0)))), C(2), C(0)));
const auto user = Word(block, C(15));
std::array<uint32_t, 10> dst;
dst.fill(C(~0u));
dst[2] = lo;
dst[3] = Or(And(hi, C(0x3fffff)), Shl(merged, 22));
dst[6] = And(user, C(0xffffff));
dst[7] = Choose(Eq(And(mask, Shr(user, 24)), C(0)), C(~0u), Word(block, C(14)));
Vec3 transformed_org, transformed_dir;
for (uint32_t axis = 0; axis < 3; ++axis) {
Vec3 row;
for (uint32_t i = 0; i < 3; ++i) row[i] = Float(Word(block, C(axis * 4 + i)));
const auto dot = [&](const Vec3& value) {
return Fop(spv::OpFAdd, Fop(spv::OpFAdd, Fop(spv::OpFMul, row[0], value[0]),
Fop(spv::OpFMul, row[1], value[1])), Fop(spv::OpFMul, row[2], value[2]));
};
transformed_org[axis] = Fop(spv::OpFAdd, dot(org), Float(Word(block, C(axis * 4 + 3))));
transformed_dir[axis] = dot(dir);
}
return Result(dst, transformed_org, transformed_dir, C(1));
}
void DefineDecompress();
void DefineBoxes();
uint32_t Boxes(uint32_t first, uint32_t second, uint32_t full, uint32_t compressed,
uint32_t flags, uint32_t descriptor, uint32_t extent, uint32_t org, uint32_t dir);
void DefineIntersect();
};
void Bvh8Emitter::DefineDecompress() {
const auto a = Begin(decompress_function, "trinity_shared_exponent", f, std::array{u, u, b});
const auto exponent = a[0], packed = a[1], minimum = a[2];
const auto negative = Bit(packed, 17), mantissa = And(packed, C(0x1ffff));
const auto outward = Eq(Choose(negative, C(1), C(0)), Choose(minimum, C(1), C(0)));
const auto nonzero = Not(Eq(mantissa, C(0)));
const auto highest = EmitGlsl<GLSLstd450FindUMsb, IR::Type::U32>(s, mantissa);
const auto shift = EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s, Sub(C(16), highest), exponent);
const auto decoded_exp = Choose(nonzero, Sub(exponent, shift),
Choose(Both(outward, Binary(s, spv::OpUGreaterThan, b, exponent, C(17))), Sub(exponent, C(17)), C(0)));
// Zero mantissas take a separate result, but their unused shifts must still be defined.
const auto amount = Add(C(7), Choose(nonzero, shift, C(0)));
const auto trail = Choose(Both(outward, Not(Eq(decoded_exp, C(255)))),
Sub(Binary(s, spv::OpShiftLeftLogical, u, C(1), amount), C(1)), C(0));
const auto fraction = Choose(nonzero,
And(Or(Binary(s, spv::OpShiftLeftLogical, u, mantissa, amount), trail), C(0x7fffff)),
Choose(outward, C(0x7fffff), C(0)));
End(Float(Or(Or(fraction, Shl(decoded_exp, 23)), Choose(negative, C(0x80000000), C(0)))));
}
void Bvh8Emitter::DefineBoxes() {
const auto result_type = s.builder.Type(spv::OpTypeStruct, v4, v4);
const auto a = Begin(boxes_function, "trinity_box_slot", result_type,
std::array{w, w, b, b, u, u, f, v3, v3});
const auto first = a[0], second = a[1], full = a[2], compressed = a[3];
const auto flags = a[4], descriptor = a[5], extent = a[6];
const auto org = Unpack3(a[7]), dir = Unpack3(a[8]);
const auto zero = ConstantF32Value(s, 0.0f), infinity = ConstantF32(s, 0x7f800000);
Vec3 inverse;
for (uint32_t i = 0; i < 3; ++i) inverse[i] = Fop(spv::OpFDiv, ConstantF32Value(s, 1.0f), dir[i]);
const auto packed = [&](uint32_t offset, uint32_t width) {
const auto word = Shr(offset, 5), shift = And(offset, C(31));
const auto lo = Binary(s, spv::OpShiftRightLogical, u, Word(first, word), shift);
const auto next = EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s, Add(word, C(1)), C(15));
const auto hi = Binary(s, spv::OpShiftLeftLogical, u, Word(first, next), And(Sub(C(32), shift), C(31)));
return And(Or(lo, Choose(Eq(shift, C(0)), C(0), hi)), C((1u << width) - 1));
};
const auto format = Choose(compressed, C(0), Choose(full, C(1), C(2)));
const auto entry_label = s.current_label;
const auto header = s.builder.AllocateId(), body = s.builder.AllocateId();
const auto cont = s.builder.AllocateId(), merge = s.builder.AllocateId();
const auto initial_nodes = Make4(C(~0u), C(~0u), C(~0u), C(~0u));
const auto initial_keys = Make4(C(0x7f800000), C(0x7f800000), C(0x7f800000), C(0x7f800000));
const auto base = And(Word(first, C(0)), C(0x1fffffff));
s.builder.AddFunction(spv::OpBranch, header);
EmitLabel(s, header);
const auto child = s.builder.AllocateId(), prefix = s.builder.AllocateId();
const auto nodes = s.builder.AllocateId(), keys = s.builder.AllocateId();
const auto child_phi = s.builder.AddDeferredPhi(u, child, 2);
const auto prefix_phi = s.builder.AddDeferredPhi(u, prefix, 2);
const auto nodes_phi = s.builder.AddDeferredPhi(v4, nodes, 2);
const auto keys_phi = s.builder.AddDeferredPhi(v4, keys, 2);
const auto more = Binary(s, spv::OpULessThan, b, child, C(4));
s.builder.AddFunction(spv::OpLoopMerge, merge, cont, spv::LoopControlDontUnrollMask);
s.builder.AddFunction(spv::OpBranchConditional, more, body, merge);
EmitLabel(s, body);
const auto kind = packed(Add(C(29), Binary(s, spv::OpIMul, u, child, C(3))), 3);
const auto direct_node = Word(first, child);
const auto node = Choose(compressed, Or(Shl(prefix, 3), kind), direct_node);
const auto child_flags = Choose(compressed,
And(packed(Add(C(41), Shl(child, 2)), 4), Choose(Eq(child, C(3)), C(7), C(15))), C(0));
const auto step = Choose(Eq(kind, C(2)), C(4),
Choose(Either(Either(Eq(kind, C(3)), Eq(kind, C(5))), Eq(kind, C(6))), C(2), C(1)));
const auto next_prefix = Add(prefix, step);
const auto bounds_type = s.builder.Type(spv::OpTypeStruct, v3, v3);
const auto box = EmitIndexSwitch(s, format, 3, bounds_type, [&](uint32_t encoding) {
std::array<uint32_t, 6> bounds;
if (encoding == 0) {
const auto yz = Word(first, C(2));
const std::array exponents {Shr(Word(first, C(1)), 24), And(yz, C(255)), And(Shr(yz, 8), C(255))};
for (uint32_t component = 0; component < 6; ++component) {
const auto offset = Add(C(80 + component * 18), Binary(s, spv::OpIMul, u, child, C(108)));
bounds[component] = Call(f, decompress_function,
std::array{exponents[component % 3], packed(offset, 18), ConstantBool(s, component < 3)});
}
} else if (encoding == 1) {
for (uint32_t component = 0; component < 6; ++component) {
const auto word = Add(C(4 + component), Binary(s, spv::OpIMul, u, child, C(6)));
const auto block = Select(s, w, Binary(s, spv::OpULessThan, b, word, C(16)), first, second);
bounds[component] = Float(Word(block, And(word, C(15))));
}
} else {
for (uint32_t pair = 0; pair < 3; ++pair) {
const auto word = Add(C(4 + pair), Binary(s, spv::OpIMul, u, child, C(3)));
const auto value = EmitGlsl<GLSLstd450UnpackHalf2x16, IR::Type::F32x2>(s, Word(first, word));
bounds[pair * 2] = Extract(f, value, 0);
bounds[pair * 2 + 1] = Extract(f, value, 1);
}
}
return Make(bounds_type, std::array{Make(v3, std::span(bounds).first(3)),
Make(v3, std::span(bounds).last(3))});
});
const auto lo_bound = Unpack3(Extract(v3, box, 0)), hi_bound = Unpack3(Extract(v3, box, 1));
Vec3 near, far;
auto valid = Not(Eq(And(child_flags, C(3)), C(3)));
for (uint32_t axis = 0; axis < 3; ++axis) {
valid = Both(valid, Fcmp(spv::OpFOrdLessThanEqual, lo_bound[axis], hi_bound[axis]));
const auto lo = Fop(spv::OpFMul, Fop(spv::OpFSub, lo_bound[axis], org[axis]), inverse[axis]);
const auto hi = Fop(spv::OpFMul, Fop(spv::OpFSub, hi_bound[axis], org[axis]), inverse[axis]);
const auto forward = Fcmp(spv::OpFOrdGreaterThanEqual, inverse[axis], zero);
near[axis] = Select(s, f, forward, lo, hi);
far[axis] = Select(s, f, forward, hi, lo);
}
const auto entry = Max(Max(near[0], near[1]), near[2]);
const auto exit = Min(Min(far[0], far[1]), far[2]);
const auto exit_bits = Raw(Select(s, f, Fcmp(spv::OpFOrdEqual, exit, zero), zero, exit));
const auto grown = Float(EmitGlsl<GLSLstd450UMin, IR::Type::U32>(s,
Add(exit_bits, And(Shr(descriptor, 23), C(255))), C(0x7f800000)));
valid = Both(valid, Both(Fcmp(spv::OpFOrdGreaterThanEqual, exit, zero),
Both(Fcmp(spv::OpFOrdLessThan, entry, extent), Fcmp(spv::OpFOrdLessThanEqual, entry, grown))));
const auto opaque = Both(Either(Bit(child_flags, 0), Bit(flags, 0)), Not(Bit(flags, 1)));
const auto nonopaque = Both(Either(Bit(child_flags, 1), Bit(flags, 1)), Not(Bit(flags, 0)));
const auto culled = Either(Either(Both(opaque, Bit(flags, 6)), Both(nonopaque, Bit(flags, 7))),
Either(Both(Bit(child_flags, 2), Bit(flags, 8)), Both(Bit(child_flags, 3), Bit(flags, 9))));
valid = Both(valid, Not(culled));
const auto heuristic = And(Shr(descriptor, 21), C(3));
auto key = Max(entry, zero);
key = Select(s, f, Eq(heuristic, C(1)), Unary(s, spv::OpFNegate, f, Fop(spv::OpFSub, exit, entry)), key);
key = Select(s, f, Eq(heuristic, C(2)), Fop(spv::OpFAdd, entry, exit), key);
const auto next_nodes = s.builder.AllocateId(), next_keys = s.builder.AllocateId();
s.builder.AddFunction(spv::OpVectorInsertDynamic, v4, next_nodes, nodes, Choose(valid, node, C(~0u)), child);
s.builder.AddFunction(spv::OpVectorInsertDynamic, v4, next_keys, keys, Raw(Select(s, f, valid, key, infinity)), child);
s.builder.AddFunction(spv::OpBranch, cont);
EmitLabel(s, cont);
const auto next_child = Add(child, C(1));
s.builder.AddFunction(spv::OpBranch, header);
s.builder.PatchDeferredPhi(child_phi, 0, C(0), entry_label);
s.builder.PatchDeferredPhi(child_phi, 1, next_child, cont);
s.builder.PatchDeferredPhi(prefix_phi, 0, base, entry_label);
s.builder.PatchDeferredPhi(prefix_phi, 1, next_prefix, cont);
s.builder.PatchDeferredPhi(nodes_phi, 0, initial_nodes, entry_label);
s.builder.PatchDeferredPhi(nodes_phi, 1, next_nodes, cont);
s.builder.PatchDeferredPhi(keys_phi, 0, initial_keys, entry_label);
s.builder.PatchDeferredPhi(keys_phi, 1, next_keys, cont);
EmitLabel(s, merge);
End(Make(result_type, std::array{nodes, keys}));
}
uint32_t Bvh8Emitter::Boxes(uint32_t first, uint32_t second, uint32_t full, uint32_t compressed,
uint32_t flags, uint32_t descriptor, uint32_t extent,
uint32_t org, uint32_t dir) {
return Call(s.builder.Type(spv::OpTypeStruct, v4, v4), boxes_function,
std::array{first, second, full, compressed, flags, descriptor, extent, org, dir});
}
void Bvh8Emitter::DefineIntersect() {
const auto result_type = TypeBvh8Result(s);
const auto a = Begin(s.bvh8_intersect_function, "trinity_bvh_intersect", result_type,
std::array{v4, w, u, f, u, v3, v3});
const auto descriptor = a[0], encoded_base = a[1], node = a[2], extent = a[3], mask = a[4];
const auto org = Unpack3(a[5]), dir = Unpack3(a[6]);
std::array<uint32_t, 4> d;
for (uint32_t i = 0; i < 4; ++i) d[i] = Extract(u, descriptor, i);
const auto kind = And(node, C(7));
const auto triangle = Binary(s, spv::OpULessThan, b, kind, C(2));
const auto instance = Eq(kind, C(6));
const auto full = Either(Eq(kind, C(2)), Eq(kind, C(5)));
const auto big = Either(Eq(kind, C(2)), Eq(kind, C(3)));
const auto compressed = Both(Not(full), Bit(d[3], 20));
const auto flags = NormalizeFlags(Choose(Bit(d[3], 23),
Unary(s, spv::OpUConvert, u, Binary(s, spv::OpShiftRightLogical, w, encoded_base, C(54))), C(0)));
const auto base = Binary(s, spv::OpShiftLeftLogical, w, PackU64(s, d[0], And(d[1], C(255))), C(8));
const auto lane_base = Binary(s, spv::OpShiftLeftLogical, w,
Binary(s, spv::OpBitwiseAnd, w, encoded_base, ConstantU64(s, (uint64_t{1} << 54) - 1)), C(3));
const auto offset = Binary(s, spv::OpShiftLeftLogical, w, Unary(s, spv::OpUConvert, w, Shr(node, 3)), C(6));
const auto address = Binary(s, spv::OpIAdd, w, base, Binary(s, spv::OpIAdd, w, lane_base, offset));
const auto capacity = Binary(s, spv::OpIAdd, w,
PackU64(s, d[2], And(d[3], C(0x3ff))), ConstantU64(s, 1));
const auto limit = Binary(s, spv::OpIAdd, w, base,
Binary(s, spv::OpShiftLeftLogical, w, capacity, C(6)));
auto valid = Both(Eq(Shr(d[3], 28), C(8)),
Eq(And(Unary(s, spv::OpUConvert, u, encoded_base), C(7)), C(0)));
valid = Both(valid, Both(Not(Eq(kind, C(7))), Either(Not(instance), Bit(d[3], 22))));
valid = Both(valid, Not(Unary(s, spv::OpIsNan, b, extent)));
for (uint32_t i = 0; i < 3; ++i) {
valid = Both(valid, Not(Either(Unary(s, spv::OpIsNan, b, org[i]), Unary(s, spv::OpIsInf, b, org[i]))));
valid = Both(valid, Not(Unary(s, spv::OpIsNan, b, dir[i])));
}
const auto opaque = Bit(flags, 0), nonopaque = Bit(flags, 1);
valid = Both(valid, Not(Either(Either(Both(opaque, Bit(flags, 6)), Both(nonopaque, Bit(flags, 7))),
Both(Bit(flags, 6), Bit(flags, 7)))));
const auto fetch_size = Select(s, w, full, ConstantU64(s, 128), ConstantU64(s, 64));
const auto fetch = [&](uint32_t at, uint32_t enabled) {
const auto end = Binary(s, spv::OpIAdd, w, at, fetch_size);
const auto in_bounds = Both(Binary(s, spv::OpULessThanEqual, b, end, limit),
Binary(s, spv::OpULessThanEqual, b, end, ConstantU64(s, (uint64_t{1} << 48) - 1)));
return EmitValueOrDefaultIfCondition(s, Both(Both(valid, enabled), in_bounds), w,
ConstantU64(s, 0), [&] { return GetBdaPointer(s, at); });
};
const auto first = fetch(address, ConstantBool(s, true));
const auto present = [&](uint32_t pointer) { return Not(Binary(s, spv::OpIEqual, b, pointer, ConstantU64(s, 0))); };
const auto tail = [&](uint32_t at, uint32_t pointer) {
return EmitValueOrDefaultIfCondition(s, Both(full, present(pointer)), w, ConstantU64(s, 0), [&] {
return GetBdaPointer(s, Binary(s, spv::OpIAdd, w, at, ConstantU64(s, 64)));
});
};
const auto second = tail(address, first);
const auto available = Both(present(first), Either(Not(full), present(second)));
std::array<uint32_t, 10> invalid;
invalid.fill(C(~0u));
for (uint32_t slot = 0; slot < 2; ++slot) {
invalid[slot * 4] = Choose(triangle, C(0x7f800000), C(~0u));
invalid[slot * 4 + 1] = Choose(triangle, C(0x3f800000), C(~0u));
invalid[slot * 4 + 3] = Choose(triangle, C(0), C(~0u));
}
const auto default_result = Result(invalid, org, dir, C(0));
const auto value = EmitValueOrDefaultIfCondition(s, Both(instance, available), result_type, default_result, [&] {
return Instance(first, flags, mask, org, dir);
});
const auto result = EmitValueOrDefaultIfCondition(s, Both(Not(instance), valid), result_type, value, [&] {
const auto second_address = Binary(s, spv::OpIAdd, w, address, fetch_size);
const auto next_first = fetch(second_address, big);
const auto next_second = tail(second_address, next_first);
const auto next_available = Both(present(next_first), Either(Not(full), present(next_second)));
const auto triangles = EmitValueOrDefaultIfCondition(s, triangle, result_type, default_result, [&] {
auto dst = invalid;
for (uint32_t slot = 0; slot < 2; ++slot) {
const auto active = Both(available, slot == 0 ? ConstantBool(s, true) : Eq(kind, C(1)));
const auto absent = Both(available, Both(Eq(kind, C(0)), Bit(d[3], 24)));
const auto miss = Make4(C(0x7f800000), C(0x3f800000),
slot == 1 ? Choose(absent, C(0), C(~0u)) : C(~0u), C(0));
const auto hit = EmitValueOrDefaultIfCondition(s, active, v4, miss, [&] {
return Call(v4, triangle_function, std::array{first, C(slot), flags, d[3], a[5], a[6]});
});
for (uint32_t i = 0; i < 4; ++i) dst[slot * 4 + i] = Extract(u, hit, i);
dst[8 + slot] = EmitValueOrDefaultIfCondition(s, active, u, C(~0u), [&] { return Word(first, C(14)); });
}
return Result(dst, org, dir, C(0));
});
return EmitValueOrDefaultIfCondition(s, Not(triangle), result_type, triangles, [&] {
auto dst = invalid;
const auto slot_type = s.builder.Type(spv::OpTypeStruct, v4, v4);
const auto miss = Make(slot_type, std::array{Make4(C(~0u), C(~0u), C(~0u), C(~0u)),
Make4(C(0x7f800000), C(0x7f800000), C(0x7f800000), C(0x7f800000))});
std::array<uint32_t, 8> keys;
for (uint32_t slot = 0; slot < 2; ++slot) {
const auto hit = EmitValueOrDefaultIfCondition(s, slot == 0 ? available : next_available,
slot_type, miss, [&] {
return Boxes(slot == 0 ? first : next_first, slot == 0 ? second : next_second,
full, compressed, flags, d[1], extent, a[5], a[6]);
});
const auto indices = Extract(v4, hit, 0), depths = Extract(v4, hit, 1);
for (uint32_t i = 0; i < 4; ++i) {
dst[slot * 4 + i] = Extract(u, indices, i);
keys[slot * 4 + i] = Extract(u, depths, i);
}
}
const auto sort = Bit(d[1], 31), wide = Both(sort, Bit(d[3], 21)), tri_first = Bit(d[1], 20);
constexpr std::array<std::pair<uint32_t, uint32_t>, 19> network {{{0,1}, {2,3}, {0,2}, {1,3}, {1,2},
{4,5}, {6,7}, {4,6}, {5,7}, {5,6}, {0,4}, {1,5}, {1,4}, {2,6}, {3,7}, {3,6}, {2,4}, {3,5}, {3,4}}};
const auto unsorted = Make(slot_type, std::array{Make(v4, std::span(dst).first(4)),
Make(v4, std::span(dst).subspan(4, 4))});
const auto sorted_result = EmitValueOrDefaultIfCondition(s, sort, slot_type, unsorted, [&] {
auto sorted_dst = dst;
for (size_t step = 0; step < network.size(); ++step) {
const auto [x, y] = network[step];
const auto is_tri = [&](uint32_t index) { return Binary(s, spv::OpULessThan, b, And(index, C(7)), C(2)); };
const auto xt = is_tri(sorted_dst[x]), yt = is_tri(sorted_dst[y]);
auto swap = Fcmp(spv::OpFOrdGreaterThan, Float(keys[x]), Float(keys[y]));
swap = Select(s, b, Both(tri_first, Binary(s, spv::OpLogicalNotEqual, b, xt, yt)), yt, swap);
if (step >= 10) swap = Both(wide, swap);
const auto key = keys[x], index = sorted_dst[x];
const auto later = [&](uint32_t position) {
return std::ranges::any_of(std::span(network).subspan(step + 1), [=](auto pair) {
return pair.first == position || pair.second == position;
});
};
if (later(x)) keys[x] = Choose(swap, keys[y], key);
if (later(y)) keys[y] = Choose(swap, key, keys[y]);
sorted_dst[x] = Choose(swap, sorted_dst[y], index);
sorted_dst[y] = Choose(swap, index, sorted_dst[y]);
}
return Make(slot_type, std::array{Make(v4, std::span(sorted_dst).first(4)),
Make(v4, std::span(sorted_dst).subspan(4, 4))});
});
for (uint32_t slot = 0; slot < 2; ++slot) {
const auto indices = Extract(v4, sorted_result, slot);
for (uint32_t i = 0; i < 4; ++i) dst[slot * 4 + i] = Extract(u, indices, i);
}
return Result(dst, org, dir, C(0));
});
});
End(result);
}
} // namespace
void DefineTrinityBvhIntersect(EmitterState& s) {
if (!s.program.info.bvh8) return;
Bvh8Emitter e {s};
e.DefineDecompress();
e.DefineTriangle();
e.DefineBoxes();
e.DefineIntersect();
}
uint32_t EmitBvh8Extract(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto index = inst.Arg(1).U32();
if (index > 16) ctx.Fail(inst, "invalid BVH8 result component");
const auto result = ctx.state.builder.AllocateId();
if (index == 16)
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), result,
ctx.Arg(inst, 0), 4u);
else
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state), result,
ctx.Arg(inst, 0), index / 4, index % 4);
return result;
}
uint32_t EmitBvh8Intersect(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& s = ctx.state;
const auto* ray = ctx.ImageAddress(inst.Arg(1));
if (ray == nullptr || ray->NumArgs() < 11 || s.bvh8_intersect_function == 0)
ctx.Fail(inst, "BVH8 intersection requires the complete ray and shared function");
Bvh8Emitter e {s};
const auto vector = [&](uint32_t first) {
return e.Make(e.v3, Vec3{e.Float(ctx.Arg(*ray, first)), e.Float(ctx.Arg(*ray, first + 1)),
e.Float(ctx.Arg(*ray, first + 2))});
};
const std::array args {ctx.Arg(inst, 0), PackU64(s, ctx.Arg(*ray, 0), ctx.Arg(*ray, 1)),
ctx.Arg(*ray, 10), e.Float(ctx.Arg(*ray, 2)), ctx.Arg(*ray, 3), vector(4), vector(7)};
const auto type = TypeBvh8Result(s);
return EmitValueOrDefaultIfCondition(s, ctx.Arg(inst, 2), type,
s.builder.Constant(spv::OpConstantNull, type), [&] {
return e.Call(type, s.bvh8_intersect_function, args);
});
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -281,6 +281,7 @@ Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const Mim
case 0x09u: return Opcode::IMAGE_STORE_MIP; case 0x09u: return Opcode::IMAGE_STORE_MIP;
case 0x0eu: return Opcode::IMAGE_GET_RESINFO; case 0x0eu: return Opcode::IMAGE_GET_RESINFO;
case 0x60u: return Opcode::IMAGE_GET_LOD; case 0x60u: return Opcode::IMAGE_GET_LOD;
case 0xe5u: return Opcode::IMAGE_BVH8_INTERSECT_RAY;
case 0xe6u: case 0xe6u:
case 0xe7u: return Opcode::IMAGE_BVH_INTERSECT_RAY; case 0xe7u: return Opcode::IMAGE_BVH_INTERSECT_RAY;
default: return Opcode::UNSUPPORTED; default: return Opcode::UNSUPPORTED;
@@ -297,6 +298,7 @@ uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
} }
switch (opcode) { switch (opcode) {
case 0xe5u:
case 0xe6u: return 11u; case 0xe6u: return 11u;
case 0xe7u: return 12u; case 0xe7u: return 12u;
case 0x0eu: return 1u; case 0x0eu: return 1u;
@@ -427,6 +429,36 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
"BVH intersection requires full-float ray DWORDs and R128/dmask:0xf"); "BVH intersection requires full-float ray DWORDs and R128/dmask:0xf");
} }
if (opcode == 0xe5u) {
inst.data_components = inst.data_dwords = 10;
if (a16 || d16 || !r128 || inst.dmask != 0xfu ||
(nsa_dwords != 0u && nsa_dwords != 2u) || vdata > 246u ||
(nsa_dwords == 0u && vaddr > 245u)) {
SetUnsupported(inst, Family::MIMG, opcode, "invalid BVH8 ray or destination encoding");
}
if (nsa_dwords == 2u) {
const auto origin = inst.image_nsa_addr[3], direction = inst.image_nsa_addr[4];
const auto node = inst.image_nsa_addr[5];
if (origin > 253u || direction > 253u)
SetUnsupported(inst, Family::MIMG, opcode, "BVH8 ray registers exceed VGPR range");
for (uint32_t i = 0; i < 3; ++i) {
inst.image_nsa_addr[3 + i] = origin + i;
inst.image_nsa_addr[6 + i] = direction + i;
}
inst.image_nsa_addr[9] = node;
}
const auto origin = nsa_dwords != 0 ? inst.image_nsa_addr[3] : vaddr + 4;
const auto direction = nsa_dwords != 0 ? inst.image_nsa_addr[6] : vaddr + 7;
bool overlapping = origin < direction + 3 && direction < origin + 3;
for (const auto output : {2u, 3u, 6u, 7u}) {
const auto reg = vdata + output;
overlapping |= (reg >= origin && reg < origin + 3) ||
(reg >= direction && reg < direction + 3);
}
if (overlapping)
SetUnsupported(inst, Family::MIMG, opcode, "overlapping BVH8 instance outputs are unsupported");
}
DecodeVectorGpr(vdata, inst.dst); DecodeVectorGpr(vdata, inst.dst);
DecodeVectorGpr(vaddr, inst.src0); DecodeVectorGpr(vaddr, inst.src0);
DecodeScalarSource(srsrc * 4u, pc, inst.src1); DecodeScalarSource(srsrc * 4u, pc, inst.src1);
@@ -142,6 +142,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0xb0u, Opcode::DS_WRITE_ADDTID_B32, 1, 32}, {0xb1u, Opcode::DS_READ_ADDTID_B32, 1, 32}, {0xb0u, Opcode::DS_WRITE_ADDTID_B32, 1, 32}, {0xb1u, Opcode::DS_READ_ADDTID_B32, 1, 32},
{0xb2u, Opcode::DS_PERMUTE_B32, 1, 32}, {0xb3u, Opcode::DS_BPERMUTE_B32, 1, 32}, {0xb2u, Opcode::DS_PERMUTE_B32, 1, 32}, {0xb3u, Opcode::DS_BPERMUTE_B32, 1, 32},
{0xdeu, Opcode::DS_WRITE_B96, 3, 32}, {0xdfu, Opcode::DS_WRITE_B128, 4, 32}, {0xdeu, Opcode::DS_WRITE_B96, 3, 32}, {0xdfu, Opcode::DS_WRITE_B128, 4, 32},
{0xe1u, Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32, 1, 32},
{0xfeu, Opcode::DS_READ_B96, 3, 32}, {0xffu, Opcode::DS_READ_B128, 4, 32}, {0xfeu, Opcode::DS_READ_B96, 3, 32}, {0xffu, Opcode::DS_READ_B128, 4, 32},
}; };
@@ -232,6 +233,7 @@ bool IsDsAtomicOpcode(Opcode opcode) {
uint32_t DsSourceCount(Opcode opcode) { uint32_t DsSourceCount(Opcode opcode) {
switch (opcode) { switch (opcode) {
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32: return 3u;
case Opcode::DS_WRITE2_B32: case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32: case Opcode::DS_WRITE2ST64_B32:
case Opcode::DS_WRITE2_B64: case Opcode::DS_WRITE2_B64:
@@ -473,6 +475,13 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) { if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) {
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented"); SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
} }
if (inst.opcode == Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 &&
(inst.gds || inst.offset < 8u || inst.offset > 31u || data1 > 248u)) {
SetUnsupported(inst, Family::DS, opcode, "invalid BVH stack operands or capacity");
}
if (inst.opcode == Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 && vdst == addr) {
SetUnsupported(inst, Family::DS, opcode, "overlapping BVH stack outputs are not implemented");
}
if (inst.gds && inst.opcode == Opcode::DS_CMPST_RTN_B32) { if (inst.gds && inst.opcode == Opcode::DS_CMPST_RTN_B32) {
SetUnsupported(inst, Family::DS, opcode, "DS compare-store return B32 GDS variant is not implemented"); SetUnsupported(inst, Family::DS, opcode, "DS compare-store return B32 GDS variant is not implemented");
} }
@@ -740,6 +740,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_READ2ST64_B64: case Opcode::DS_READ2ST64_B64:
case Opcode::DS_READ_B96: case Opcode::DS_READ_B96:
case Opcode::DS_READ_B128: case Opcode::DS_READ_B128:
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32:
case Opcode::DS_WRITE_B8: case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16: case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B8_D16_HI: case Opcode::DS_WRITE_B8_D16_HI:
@@ -685,6 +685,7 @@ enum class Opcode {
DS_READ2ST64_B64, DS_READ2ST64_B64,
DS_READ_B96, DS_READ_B96,
DS_READ_B128, DS_READ_B128,
DS_BVH_STACK_PUSH8_POP1_RTN_B32,
DS_WRITE_B8, DS_WRITE_B8,
DS_WRITE_B16, DS_WRITE_B16,
DS_WRITE_B8_D16_HI, DS_WRITE_B8_D16_HI,
@@ -700,6 +701,7 @@ enum class Opcode {
DS_WRITE_ADDTID_B32, DS_WRITE_ADDTID_B32,
DS_READ_ADDTID_B32, DS_READ_ADDTID_B32,
IMAGE_BVH_INTERSECT_RAY, IMAGE_BVH_INTERSECT_RAY,
IMAGE_BVH8_INTERSECT_RAY,
IMAGE_GET_RESINFO, IMAGE_GET_RESINFO,
IMAGE_GET_LOD, IMAGE_GET_LOD,
IMAGE_LOAD, IMAGE_LOAD,
@@ -326,7 +326,8 @@ IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
inst.image_sample_flags, inst.image_dimension, inst.image_address_components); inst.image_sample_flags, inst.image_dimension, inst.image_address_components);
EXIT_IF(count > components.size()); EXIT_IF(count > components.size());
const auto nsa_components = const auto nsa_components =
std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents); inst.opcode == Decoder::Opcode::IMAGE_BVH8_INTERSECT_RAY && inst.image_nsa_dwords != 0u
? 10u : std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
for (uint32_t index = 0; index < count; index++) { for (uint32_t index = 0; index < count; index++) {
if (index != 0u && index - 1u < nsa_components) { if (index != 0u && index - 1u < nsa_components) {
components[index] = components[index] =
@@ -781,6 +782,15 @@ void Translator::WriteSharedU32(uint32_t width, IR::U32 address,
} }
} }
void Translator::DS_BVH_STACK(const Decoder::Instruction& inst) {
const auto result = ir.Emit(IR::ValueOpcode::BvhStack,
{ReadU32(inst.src0), ReadU32(inst.src1), ConstructU32x4(inst.src2, 4),
ConstructU32x4(OffsetOperand(inst.src2, 4), 4), ir.GetExec()},
AddMemoryInfo(MemoryInfoFromDecoded(inst), inst.pc));
WriteOperand(inst.src0, ir.Emit(IR::ValueOpcode::CompositeExtractU32x2, {result, IR::Value(0u)}));
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::CompositeExtractU32x2, {result, IR::Value(1u)}));
}
void Translator::DS_READ(const Decoder::Instruction& inst) { void Translator::DS_READ(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst); const auto memory = MemoryInfoFromDecoded(inst);
const auto address = ReadU32(MemorySourceAt(inst, 0)); const auto address = ReadU32(MemorySourceAt(inst, 0));
@@ -939,6 +949,31 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
{ReadU32(inst.src1), address, ir.GetExec()})); {ReadU32(inst.src1), address, ir.GetExec()}));
} }
void Translator::IMAGE_BVH8(const Decoder::Instruction& inst) {
std::array<IR::U32, 10> previous;
for (uint32_t i = 0; i < previous.size(); ++i)
previous[i] = ReadU32(OffsetOperand(inst.dst, i));
const auto result = ir.Emit(IR::ValueOpcode::Bvh8Intersect,
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()});
const auto get = [&](uint32_t i) {
return IR::U32(ir.Emit(IR::ValueOpcode::Bvh8Extract, {result, IR::Value(i)}));
};
const auto instance = ir.INotEqual(get(16), IR::U32(IR::Value(0u)));
for (uint32_t i = 0; i < 10; ++i) {
auto value = get(i);
if (i == 0 || i == 1 || i == 4 || i == 5 || i == 8 || i == 9)
value = ir.Select(instance, previous[i], value);
WriteOperand(OffsetOperand(inst.dst, i), value);
}
for (uint32_t i = 0; i < 6; ++i) {
auto reg = inst.image_nsa_dwords != 0 ? inst.image_nsa_addr[3 + i] : inst.src0.reg + 4 + i;
auto target = inst.src0;
target.reg = reg;
// Non-instance results may alias the ray inputs.
WriteOperand(target, ir.Select(instance, get(10 + i), ReadU32(target)));
}
}
void Translator::EmitMemory(const Decoder::Instruction& inst) { void Translator::EmitMemory(const Decoder::Instruction& inst) {
switch (inst.opcode) { switch (inst.opcode) {
// Translated scalar loads have no cache of their own to invalidate. // Translated scalar loads have no cache of their own to invalidate.
@@ -947,6 +982,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_GL1_INV: case Decoder::Opcode::BUFFER_GL1_INV:
ir.Emit(IR::ValueOpcode::DataCacheInvalidate); ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
return; return;
case Decoder::Opcode::IMAGE_BVH8_INTERSECT_RAY: return IMAGE_BVH8(inst);
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: { case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect, const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
{ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()}, {ConstructU32x4(inst.src1, 4), MakeImageAddress(inst, inst.src0), ir.GetExec()},
@@ -1186,6 +1222,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
return DS_APPEND_CONSUME(inst, IR::ValueOpcode::DataAppend); return DS_APPEND_CONSUME(inst, IR::ValueOpcode::DataAppend);
case Decoder::Opcode::DS_WRITE_ADDTID_B32: return DS_ADDTID(inst, true); case Decoder::Opcode::DS_WRITE_ADDTID_B32: return DS_ADDTID(inst, true);
case Decoder::Opcode::DS_READ_ADDTID_B32: return DS_ADDTID(inst, false); case Decoder::Opcode::DS_READ_ADDTID_B32: return DS_ADDTID(inst, false);
case Decoder::Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32: return DS_BVH_STACK(inst);
case Decoder::Opcode::DS_READ2_B32: case Decoder::Opcode::DS_READ2_B32:
case Decoder::Opcode::DS_READ2ST64_B32: case Decoder::Opcode::DS_READ2ST64_B32:
case Decoder::Opcode::DS_READ2_B64: case Decoder::Opcode::DS_READ2_B64:
@@ -105,6 +105,8 @@ private:
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values, void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
const IR::MemoryInfo& memory, uint32_t pc); const IR::MemoryInfo& memory, uint32_t pc);
void DS_READ(const Decoder::Instruction& inst); void DS_READ(const Decoder::Instruction& inst);
void IMAGE_BVH8(const Decoder::Instruction& inst);
void DS_BVH_STACK(const Decoder::Instruction& inst);
void DS_READ2(const Decoder::Instruction& inst); void DS_READ2(const Decoder::Instruction& inst);
void DS_WRITE(const Decoder::Instruction& inst); void DS_WRITE(const Decoder::Instruction& inst);
void DS_WRITE2(const Decoder::Instruction& inst); void DS_WRITE2(const Decoder::Instruction& inst);
@@ -155,7 +155,7 @@ KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
KYTY_IO_FIELDS(StageInput, a, b, c, d, e) KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
KYTY_IO_FIELDS(StageOutput, a, b, c, d) KYTY_IO_FIELDS(StageOutput, a, b, c, d)
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
A, B, C, D, E, F, G, H, I, J, K) A, B, C, D, E, F, G, H, I, J, K, L, M)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b) KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c) KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c)
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k, KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k,
@@ -543,6 +543,8 @@ struct ShaderInfo {
bool uses_image_heap = false; bool uses_image_heap = false;
bool uses_dispatch_threads = false; bool uses_dispatch_threads = false;
bool bvh = false; bool bvh = false;
bool bvh8 = false;
bool bvh_stack = false;
bool subgroup_ballot = false; bool subgroup_ballot = false;
bool subgroup_vote = false; bool subgroup_vote = false;
bool subgroup_barrier = false; bool subgroup_barrier = false;
@@ -31,6 +31,7 @@ std::string TypeName(Type type) {
"SamplerResource", "SamplerResource",
"ImageAddress", "ImageAddress",
"F64", "F64",
"Bvh8Result",
}; };
const auto bits = static_cast<uint32_t>(type); const auto bits = static_cast<uint32_t>(type);
if (bits == 0) { if (bits == 0) {
+1
View File
@@ -28,6 +28,7 @@ enum class Type : uint32_t {
SamplerResource = 1u << 18u, SamplerResource = 1u << 18u,
ImageAddress = 1u << 19u, ImageAddress = 1u << 19u,
F64 = 1u << 20u, F64 = 1u << 20u,
Bvh8Result = 1u << 21u,
}; };
constexpr Type operator|(Type lhs, Type rhs) { constexpr Type operator|(Type lhs, Type rhs) {
@@ -44,6 +44,7 @@ constexpr Type AddressResource = Type::AddressResource;
constexpr Type ImageResource = Type::ImageResource; constexpr Type ImageResource = Type::ImageResource;
constexpr Type SamplerResource = Type::SamplerResource; constexpr Type SamplerResource = Type::SamplerResource;
constexpr Type ImageAddress = Type::ImageAddress; constexpr Type ImageAddress = Type::ImageAddress;
constexpr Type Bvh8Result = Type::Bvh8Result;
constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{ constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{
#define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__), #define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__),
@@ -76,7 +77,8 @@ bool HasSideEffects(ValueOpcode opcode) {
return true; return true;
} }
const auto shared_access = SharedAccessOf(opcode); const auto shared_access = SharedAccessOf(opcode);
if (shared_access == SharedAccess::Write || shared_access == SharedAccess::Atomic || if (shared_access == SharedAccess::Write || shared_access == SharedAccess::ReadWrite ||
shared_access == SharedAccess::Atomic ||
shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) { shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
return true; return true;
} }
@@ -156,6 +158,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
SharedAccess SharedAccessOf(ValueOpcode opcode) { SharedAccess SharedAccessOf(ValueOpcode opcode) {
switch (opcode) { switch (opcode) {
case ValueOpcode::BvhStack: return SharedAccess::ReadWrite;
case ValueOpcode::LoadSharedU8: case ValueOpcode::LoadSharedU8:
case ValueOpcode::LoadSharedU16: case ValueOpcode::LoadSharedU16:
case ValueOpcode::LoadSharedU32: case ValueOpcode::LoadSharedU32:
@@ -17,7 +17,7 @@ enum class ValueOpcode : uint16_t {
}; };
enum class BufferAccess { None, Read, Write, Atomic }; enum class BufferAccess { None, Read, Write, Atomic };
enum class SharedAccess { None, Read, Write, Atomic, Append, Consume }; enum class SharedAccess { None, Read, Write, ReadWrite, Atomic, Append, Consume };
enum class AddressAccess { None, Read, Write }; enum class AddressAccess { None, Read, Write };
enum class ImageAccess { None, Read, Write, Atomic }; enum class ImageAccess { None, Read, Write, Atomic };
enum class ImageResourceClass { None, Sampled, Storage }; enum class ImageResourceClass { None, Sampled, Storage };
@@ -318,6 +318,9 @@ VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress) VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1) VALUE_OPCODE(BvhIntersect, U32x4, U32x4, ImageAddress, U1)
VALUE_OPCODE(Bvh8Intersect, Bvh8Result, U32x4, ImageAddress, U1)
VALUE_OPCODE(Bvh8Extract, U32, Bvh8Result, U32)
VALUE_OPCODE(BvhStack, U32x2, U32, U32, U32x4, U32x4, U1)
VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress) VALUE_OPCODE(ImageQueryLod, U32x4, ImageResource, SamplerResource, ImageAddress)
VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1) VALUE_OPCODE(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1) VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
@@ -4182,7 +4182,7 @@ private:
void Collect(Inst& inst) { void Collect(Inst& inst) {
const auto op = inst.GetOpcode(); const auto op = inst.GetOpcode();
if (op == ValueOpcode::BvhIntersect) { if (op == ValueOpcode::BvhIntersect || op == ValueOpcode::Bvh8Intersect) {
m_info.uses_dma = true; m_info.uses_dma = true;
return; return;
} }
@@ -515,6 +515,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break; case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break; case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = true; break;
case ValueOpcode::BvhIntersect: info.bvh = true; break; case ValueOpcode::BvhIntersect: info.bvh = true; break;
case ValueOpcode::Bvh8Intersect: info.bvh8 = true; break;
case ValueOpcode::BvhStack: info.bvh_stack = true; break;
case ValueOpcode::ConditionRef: case ValueOpcode::ConditionRef:
info.subgroup_vote |= info.subgroup_vote |=
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction; inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
+4 -2
View File
@@ -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));
+4 -1
View File
@@ -2277,7 +2277,10 @@ int KYTY_SYSV_ABI PthreadRwlockDestroy(PthreadRwlock* rwlock) {
return KERNEL_ERROR_EINVAL; return KERNEL_ERROR_EINVAL;
} }
EXIT_NOT_IMPLEMENTED(*rwlock == nullptr); // SCE_PTHREAD_RWLOCK_INITIALIZER has no allocated resources until first use.
if (*rwlock == nullptr) {
return OK;
}
{ {
std::lock_guard lock((*rwlock)->m); std::lock_guard lock((*rwlock)->m);
@@ -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>
@@ -406,6 +412,11 @@
<property name="text"><string>Enable network connection</string></property> <property name="text"><string>Enable network connection</string></property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBox_psn_emulation">
<property name="text"><string>Emulate PSN (compatibility)</string></property>
</widget>
</item>
<item> <item>
<spacer name="networkSpacer"> <spacer name="networkSpacer">
<property name="orientation"><enum>Qt::Vertical</enum></property> <property name="orientation"><enum>Qt::Vertical</enum></property>
+4
View File
@@ -128,6 +128,7 @@ public:
bool perf_overlay_enabled = false; bool perf_overlay_enabled = false;
bool skip_notice_screen = false; bool skip_notice_screen = false;
bool network_connected = false; bool network_connected = false;
bool psn_emulation_enabled = false;
int vblank_frequency = 60; int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE; int console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false; bool vulkan_validation_enabled = false;
@@ -141,6 +142,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;
@@ -171,6 +173,7 @@ public:
perf_overlay_enabled = other.perf_overlay_enabled; perf_overlay_enabled = other.perf_overlay_enabled;
skip_notice_screen = other.skip_notice_screen; skip_notice_screen = other.skip_notice_screen;
network_connected = other.network_connected; network_connected = other.network_connected;
psn_emulation_enabled = other.psn_emulation_enabled;
vblank_frequency = other.vblank_frequency; vblank_frequency = other.vblank_frequency;
console_language = other.console_language; console_language = other.console_language;
vulkan_validation_enabled = other.vulkan_validation_enabled; vulkan_validation_enabled = other.vulkan_validation_enabled;
@@ -184,6 +187,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;
+4
View File
@@ -42,6 +42,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(perf_overlay_enabled); KYTY_CFG_SET(perf_overlay_enabled);
KYTY_CFG_SET(skip_notice_screen); KYTY_CFG_SET(skip_notice_screen);
KYTY_CFG_SET(network_connected); KYTY_CFG_SET(network_connected);
KYTY_CFG_SET(psn_emulation_enabled);
KYTY_CFG_SET(vblank_frequency); KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language); KYTY_CFG_SET(console_language);
KYTY_CFG_SET(vulkan_validation_enabled); KYTY_CFG_SET(vulkan_validation_enabled);
@@ -55,6 +56,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);
@@ -96,6 +98,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(perf_overlay_enabled); KYTY_CFG_GET(perf_overlay_enabled);
KYTY_CFG_GET(skip_notice_screen); KYTY_CFG_GET(skip_notice_screen);
KYTY_CFG_GET(network_connected); KYTY_CFG_GET(network_connected);
KYTY_CFG_GET(psn_emulation_enabled);
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt(); vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
console_language = s.value("console_language", console_language).toInt(); console_language = s.value("console_language", console_language).toInt();
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) { if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
@@ -112,6 +115,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 =
@@ -298,6 +298,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_shader_cache->setChecked(info.shader_cache_enabled); m_ui->checkBox_shader_cache->setChecked(info.shader_cache_enabled);
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen); m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
m_ui->checkBox_network_connected->setChecked(info.network_connected); m_ui->checkBox_network_connected->setChecked(info.network_connected);
m_ui->checkBox_psn_emulation->setChecked(info.psn_emulation_enabled);
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled); m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds); m_ui->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
m_ui->checkBox_perf_overlay->setChecked(info.perf_overlay_enabled); m_ui->checkBox_perf_overlay->setChecked(info.perf_overlay_enabled);
@@ -311,6 +312,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);
@@ -533,6 +535,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.shader_cache_enabled = ui.checkBox_shader_cache->isChecked(); info.shader_cache_enabled = ui.checkBox_shader_cache->isChecked();
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked(); info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
info.network_connected = ui.checkBox_network_connected->isChecked(); info.network_connected = ui.checkBox_network_connected->isChecked();
info.psn_emulation_enabled = ui.checkBox_psn_emulation->isChecked();
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked(); info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
info.trophy_duration_seconds = ui.spinBox_trophy_duration->value(); info.trophy_duration_seconds = ui.spinBox_trophy_duration->value();
info.perf_overlay_enabled = ui.checkBox_perf_overlay->isChecked(); info.perf_overlay_enabled = ui.checkBox_perf_overlay->isChecked();
@@ -541,6 +544,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();
+2
View File
@@ -276,6 +276,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
AddFlag(args, info.perf_overlay_enabled, "--perf-overlay"); AddFlag(args, info.perf_overlay_enabled, "--perf-overlay");
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen"); AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
AddFlag(args, info.network_connected, "--network-connected"); AddFlag(args, info.network_connected, "--network-connected");
AddFlag(args, info.psn_emulation_enabled, "--emulate-psn");
AddFlag(args, info.tessellation_enabled, "--tessellation"); AddFlag(args, info.tessellation_enabled, "--tessellation");
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries"); AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
AddFlag(args, info.shader_cache_enabled, "--shader-cache"); AddFlag(args, info.shader_cache_enabled, "--shader-cache");
@@ -291,6 +292,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
View File
@@ -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 -4
View File
@@ -32,6 +32,7 @@ namespace AppContent {
constexpr uint32_t APP_CONTENT_APPPARAM_SKU_FLAG_FULL = 3; constexpr uint32_t APP_CONTENT_APPPARAM_SKU_FLAG_FULL = 3;
constexpr int APP_CONTENT_ERROR_PARAMETER = -2133262334; /* 0x80D90002 */ constexpr int APP_CONTENT_ERROR_PARAMETER = -2133262334; /* 0x80D90002 */
constexpr int APP_CONTENT_ERROR_BUSY = -2133262333; /* 0x80D90003 */
constexpr int APP_CONTENT_ERROR_NOT_MOUNTED = -2133262332; /* 0x80D90004 */ constexpr int APP_CONTENT_ERROR_NOT_MOUNTED = -2133262332; /* 0x80D90004 */
constexpr int APP_CONTENT_ERROR_NOT_FOUND = -2133262331; /* 0x80D90005 */ constexpr int APP_CONTENT_ERROR_NOT_FOUND = -2133262331; /* 0x80D90005 */
constexpr int APP_CONTENT_ERROR_MOUNT_FULL = -2133262330; /* 0x80D90006 */ constexpr int APP_CONTENT_ERROR_MOUNT_FULL = -2133262330; /* 0x80D90006 */
@@ -210,10 +211,8 @@ int KYTY_SYSV_ABI AppContentAddcontMount(uint32_t servic
for (const auto& m: g_addcont_mounts) { for (const auto& m: g_addcont_mounts) {
if (m.label == entry->entitlement_label) { if (m.label == entry->entitlement_label) {
const auto guest_point = m.point.substr(0, m.point.size() - 1); LOGF("\t already mounted %s\n", m.point.c_str());
std::strncpy(mount_point->data, guest_point.c_str(), sizeof(mount_point->data) - 1); return APP_CONTENT_ERROR_BUSY;
LOGF("\t already mounted %s\n", guest_point.c_str());
return OK;
} }
} }
+2
View File
@@ -1162,6 +1162,8 @@ LIB_DEFINE(InitAudio_1_Ngs2) {
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands); LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState); LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags); LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
LIB_FUNC("9eic4AmjGVI", Ngs2::Ngs2VoiceQueryInfo);
LIB_FUNC("WCayTgob7-o", Ngs2::Ngs2VoiceGetPortInfo);
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender); LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples); LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData); LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
+3 -13
View File
@@ -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;
} }
+54 -32
View File
@@ -1685,12 +1685,34 @@ LIB_VERSION("NpAuth", 1, "NpAuth", 1, 1);
constexpr int NP_AUTH_MAX_REQUEST_NUM = 16; constexpr int NP_AUTH_MAX_REQUEST_NUM = 16;
constexpr int NP_AUTH_REQUEST_ID_OFFSET = 0x10000000; constexpr int NP_AUTH_REQUEST_ID_OFFSET = 0x10000000;
constexpr int NP_AUTH_ERROR_INVALID_ARGUMENT = -2141912319; /* 0x80550301 */ constexpr int NP_AUTH_ERROR_INVALID_ARGUMENT = -2141912319; /* 0x80550301 */
constexpr int NP_AUTH_ERROR_INVALID_SIZE = -2141912318; /* 0x80550302 */
constexpr int NP_AUTH_ERROR_ABORTED = -2141912316; /* 0x80550304 */ constexpr int NP_AUTH_ERROR_ABORTED = -2141912316; /* 0x80550304 */
constexpr int NP_AUTH_ERROR_REQUEST_MAX = -2141912315; /* 0x80550305 */ constexpr int NP_AUTH_ERROR_REQUEST_MAX = -2141912315; /* 0x80550305 */
constexpr int NP_AUTH_ERROR_REQUEST_NOT_FOUND = -2141912314; /* 0x80550306 */ constexpr int NP_AUTH_ERROR_REQUEST_NOT_FOUND = -2141912314; /* 0x80550306 */
constexpr int NP_AUTH_ERROR_INVALID_ID = -2141912313; /* 0x80550307 */ constexpr int NP_AUTH_ERROR_INVALID_ID = -2141912313; /* 0x80550307 */
constexpr int NP_AUTH_ERROR_SERVICE_DOWN = -2141912063; /* 0x80550401 */
constexpr int NP_ERROR_SIGNED_OUT = -2141913082; /* 0x80550006 */ constexpr int NP_ERROR_SIGNED_OUT = -2141913082; /* 0x80550006 */
struct NpAuthGetAuthorizationCodeParameterV3 {
size_t size;
int user_id;
uint8_t padding[4];
const void* client_id;
const char* scope;
};
struct NpAuthGetIdTokenParameterV3 {
size_t size;
int user_id;
uint8_t padding[4];
const void* client_id;
const void* client_secret;
const char* scope;
};
static_assert(sizeof(NpAuthGetAuthorizationCodeParameterV3) == 32);
static_assert(sizeof(NpAuthGetIdTokenParameterV3) == 40);
enum class NpAuthRequestState { enum class NpAuthRequestState {
None = 0, None = 0,
Ready, Ready,
@@ -1822,7 +1844,7 @@ static int np_auth_wait_or_poll_async(int req_id, int* result) {
return 0; return 0;
} }
static int np_auth_complete_signed_out(NpAuthRequest* request) { static int np_auth_complete_unavailable(NpAuthRequest* request, int user_id) {
if (request->state == NpAuthRequestState::Complete) { if (request->state == NpAuthRequestState::Complete) {
request->result = NP_AUTH_ERROR_INVALID_ARGUMENT; request->result = NP_AUTH_ERROR_INVALID_ARGUMENT;
return NP_AUTH_ERROR_INVALID_ARGUMENT; return NP_AUTH_ERROR_INVALID_ARGUMENT;
@@ -1833,9 +1855,11 @@ static int np_auth_complete_signed_out(NpAuthRequest* request) {
} }
request->state = NpAuthRequestState::Complete; request->state = NpAuthRequestState::Complete;
request->result = NP_ERROR_SIGNED_OUT; // Emulating sign-in cannot provide a real authorization code or ID token.
request->result = Network::NpManager::IsSignedIn(user_id) ? NP_AUTH_ERROR_SERVICE_DOWN
: NP_ERROR_SIGNED_OUT;
return request->async ? 0 : NP_ERROR_SIGNED_OUT; return request->async ? 0 : request->result;
} }
static int KYTY_SYSV_ABI NpAuthWaitAsync(int req_id, int* result) { static int KYTY_SYSV_ABI NpAuthWaitAsync(int req_id, int* result) {
@@ -1856,8 +1880,9 @@ static int KYTY_SYSV_ABI NpAuthPollAsync(int req_id, int* result) {
return np_auth_wait_or_poll_async(req_id, result); return np_auth_wait_or_poll_async(req_id, result);
} }
static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* param, static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(
void* auth_code, int* issuer_id) { int req_id, const NpAuthGetAuthorizationCodeParameterV3* param, void* auth_code,
int* issuer_id) {
PRINT_NAME(); PRINT_NAME();
LOGF("\t req_id = %d\n", req_id); LOGF("\t req_id = %d\n", req_id);
@@ -1865,9 +1890,17 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
LOGF("\t auth_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(auth_code)); LOGF("\t auth_code = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(auth_code));
LOGF("\t issuer_id = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(issuer_id)); LOGF("\t issuer_id = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(issuer_id));
if (auth_code != nullptr) { if (req_id <= 0 || param == nullptr || auth_code == nullptr) {
memset(auth_code, 0, 136); return NP_AUTH_ERROR_INVALID_ARGUMENT;
} }
if (param->size < sizeof(*param)) {
return NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->client_id == nullptr || param->scope == nullptr) {
return NP_AUTH_ERROR_INVALID_ARGUMENT;
}
memset(auth_code, 0, 136);
if (issuer_id != nullptr) { if (issuer_id != nullptr) {
*issuer_id = 0; *issuer_id = 0;
} }
@@ -1877,46 +1910,35 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
if (request == nullptr) { if (request == nullptr) {
return NP_AUTH_ERROR_REQUEST_NOT_FOUND; return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
} }
if (request->state == NpAuthRequestState::Aborted) { return np_auth_complete_unavailable(request, param->user_id);
// request->result = 0;
// return 0;
request->result = NP_AUTH_ERROR_ABORTED;
return NP_AUTH_ERROR_ABORTED;
}
// request->state = NpAuthRequestState::Complete;
// request->result = 0;
// return 0;
return np_auth_complete_signed_out(request);
} }
static int KYTY_SYSV_ABI NpAuthGetIdTokenV3(int req_id, const void* param, void* id_token) { static int KYTY_SYSV_ABI NpAuthGetIdTokenV3(int req_id, const NpAuthGetIdTokenParameterV3* param,
void* id_token) {
PRINT_NAME(); PRINT_NAME();
LOGF("\t req_id = %d\n", req_id); LOGF("\t req_id = %d\n", req_id);
LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param)); LOGF("\t param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(param));
LOGF("\t id_token = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(id_token)); LOGF("\t id_token = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(id_token));
if (id_token != nullptr) { if (req_id <= 0 || param == nullptr || id_token == nullptr) {
memset(id_token, 0, 4104); return NP_AUTH_ERROR_INVALID_ARGUMENT;
} }
if (param->size < sizeof(*param)) {
return NP_AUTH_ERROR_INVALID_SIZE;
}
if (param->client_id == nullptr || param->client_secret == nullptr || param->scope == nullptr) {
return NP_AUTH_ERROR_INVALID_ARGUMENT;
}
memset(id_token, 0, 4104);
std::scoped_lock lock(g_np_auth_request_mutex); std::scoped_lock lock(g_np_auth_request_mutex);
auto* request = np_auth_find_request(req_id); auto* request = np_auth_find_request(req_id);
if (request == nullptr) { if (request == nullptr) {
return NP_AUTH_ERROR_REQUEST_NOT_FOUND; return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
} }
if (request->state == NpAuthRequestState::Aborted) { return np_auth_complete_unavailable(request, param->user_id);
// request->result = 0;
// return 0;
request->result = NP_AUTH_ERROR_ABORTED;
return NP_AUTH_ERROR_ABORTED;
}
// request->state = NpAuthRequestState::Complete;
// request->result = 0;
// return 0;
return np_auth_complete_signed_out(request);
} }
LIB_DEFINE(InitNet_1_NpAuth) { LIB_DEFINE(InitNet_1_NpAuth) {
+282 -255
View File
@@ -1,349 +1,376 @@
#include "graphics/guest_gpu/pm4.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/shader/shaderBindings.h"
#include "libs/errno.h" #include "libs/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;
}
int CheckContext(const void* context) {
if (!g_initialized) {
return NOT_INITIALIZED;
} }
return g_contexts.contains(context) ? OK : INVALID_CONTEXT;
} }
static bool IsSupportedType(uint32_t type) { int KYTY_SYSV_ABI Initialize() {
switch (type) { std::lock_guard lock(g_mutex);
case 0: return true; if (!Loader::SystemContentIsTrinityMode()) {
case 1: return (g_supported_modes & 0x1u) != 0; return NOT_TRINITY_MODE;
case 2: return (g_supported_modes & 0x2u) != 0;
default: return false;
} }
} if (g_initialized) {
return ALREADY_INITIALIZED;
static uint64_t RequiredBlockCount(uint32_t type) {
const auto base = BaseVaBytes(type);
if (base == 0) {
return 0;
} }
return (base + EXTRA_VA_BYTES) / MAIN_MEMORY_BLOCK_SIZE;
}
static int CheckInitialized() {
return (g_initialized ? OK : PSML_ERROR_NOT_INITIALIZED);
}
static void MarkSharedResources(void* resources, const SharedResourcesInitParameters& params) {
auto* bytes = static_cast<uint8_t*>(resources);
*reinterpret_cast<uint32_t*>(bytes) = SHARED_RESOURCES_MAGIC;
*reinterpret_cast<const DirectMemoryBlock**>(bytes + 0x08) = params.blocks;
*reinterpret_cast<uint64_t*>(bytes + 0x18) = params.block_count;
*reinterpret_cast<uint32_t*>(bytes + 0x20) = params.type;
*reinterpret_cast<uint64_t*>(bytes + 0x28) = params.virtual_address_start;
}
static bool HasKnownObjectMagic(const void* object) {
if (object == nullptr) {
return false;
}
const auto magic = *static_cast<const uint32_t*>(object);
return magic == SHARED_RESOURCES_MAGIC || magic == CONTEXT_MAGIC;
}
static int KYTY_SYSV_ABI PsmlInitialize() {
PRINT_NAME();
g_initialized = true; g_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(); int KYTY_SYSV_ABI CreatePrivateContext(void** out, const ContextParameters* params) {
if (init_error != OK) { return CreateContext(out, params == nullptr ? nullptr : &params->memory,
return init_error; params == nullptr ? 0 : params->width,
params == nullptr ? 0 : params->height, nullptr, false);
}
int KYTY_SYSV_ABI CreateSharedContext(void** out, const ContextParameters* params) {
return CreateContext(
out, params == nullptr ? nullptr : &params->memory, params == nullptr ? 0 : params->width,
params == nullptr ? 0 : params->height, params == nullptr ? nullptr : params->shared, true);
}
int KYTY_SYSV_ABI ReleaseContext(void* context) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
} }
if (!HasKnownObjectMagic(context)) { auto& record = g_contexts.at(context);
return PSML_ERROR_INVALID_OBJECT; if (record->shared != nullptr) {
--record->shared->contexts;
} }
g_contexts.erase(context);
*static_cast<uint32_t*>(context) = 0;
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;
} }
static int KYTY_SYSV_ABI PsmlValidateObject(const void* object) { } // namespace
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
+70 -46
View File
@@ -4002,6 +4002,11 @@ struct NpCountryCode {
char padding[1]; char padding[1];
}; };
struct NpLanguageCode {
char code[36];
char padding[12];
};
struct NpAgeRestriction { struct NpAgeRestriction {
NpCountryCode country_code; NpCountryCode country_code;
int8_t age; int8_t age;
@@ -4056,8 +4061,23 @@ constexpr int np_error_aborted = -2141913070; /* 0x8055
constexpr int np_error_request_max = -2141913069; /* 0x80550013 */ constexpr int np_error_request_max = -2141913069; /* 0x80550013 */
constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */ constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */
constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */ constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */
constexpr int np_error_network_not_available = -2141872086; /* 0x8055a02a */
constexpr int np_request_max = 128; constexpr int np_request_max = 128;
constexpr uint32_t np_state_signed_out = 1; constexpr uint32_t np_state_signed_out = 1;
constexpr uint32_t np_state_signed_in = 2;
// A simulated account belongs only to the primary user and survives network loss.
bool IsSignedIn(int user_id) {
return Config::PsnEmulationEnabled() && user_id == Config::GetUserId();
}
static int np_reachability_result(int user_id) {
if (!IsSignedIn(user_id)) {
return np_error_signed_out;
}
// This is an explicit compatibility simulation, not a connection to PSN.
return NetCtl::NetCtlConnected() ? OK : np_error_network_not_available;
}
struct NpStateCallback { struct NpStateCallback {
NpStateCallbackA callback = nullptr; NpStateCallbackA callback = nullptr;
@@ -4117,7 +4137,7 @@ static NpRequest* np_get_request_locked(int req_id) {
return &g_np_requests[index]; return &g_np_requests[index];
} }
static int np_complete_signed_out_locked(NpRequest* request) { static int np_complete_locked(NpRequest* request, int result) {
if (request->state == NpRequestState::Complete) { if (request->state == NpRequestState::Complete) {
request->result = np_error_invalid_argument; request->result = np_error_invalid_argument;
return np_error_invalid_argument; return np_error_invalid_argument;
@@ -4128,9 +4148,9 @@ static int np_complete_signed_out_locked(NpRequest* request) {
} }
request->state = NpRequestState::Complete; request->state = NpRequestState::Complete;
request->result = np_error_signed_out; request->result = result;
return (request->async ? OK : np_error_signed_out); return (request->async ? OK : result);
} }
int KYTY_SYSV_ABI NpCheckCallback() { int KYTY_SYSV_ABI NpCheckCallback() {
@@ -4144,7 +4164,9 @@ int KYTY_SYSV_ABI NpCheckCallback() {
} }
} }
for (const auto& entry: pending) { for (const auto& entry: pending) {
entry.callback(Config::GetUserId(), np_state_signed_out, entry.userdata); entry.callback(Config::GetUserId(),
IsSignedIn(Config::GetUserId()) ? np_state_signed_in : np_state_signed_out,
entry.userdata);
} }
return OK; return OK;
@@ -4239,12 +4261,13 @@ int KYTY_SYSV_ABI NpGetNpId(int user_id, NpId* np_id) {
EXIT_NOT_IMPLEMENTED(np_id == nullptr); EXIT_NOT_IMPLEMENTED(np_id == nullptr);
// int s = snprintf(np_id->handle.data, 16, "Kyty");
// EXIT_NOT_IMPLEMENTED(s >= 16);
// np_id->handle.term = 0;
std::memset(np_id, 0, sizeof(*np_id)); std::memset(np_id, 0, sizeof(*np_id));
// return OK; if (IsSignedIn(user_id)) {
std::memcpy(np_id->handle.data, "Kyty", 4);
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4255,12 +4278,13 @@ int KYTY_SYSV_ABI NpGetOnlineId(int user_id, NpOnlineId* online_id) {
EXIT_NOT_IMPLEMENTED(online_id == nullptr); EXIT_NOT_IMPLEMENTED(online_id == nullptr);
// int s = snprintf(online_id->data, 16, "Kyty");
// EXIT_NOT_IMPLEMENTED(s >= 16);
// online_id->term = 0;
std::memset(online_id, 0, sizeof(*online_id)); std::memset(online_id, 0, sizeof(*online_id));
// return OK; if (IsSignedIn(user_id)) {
std::memcpy(online_id->data, "Kyty", 4);
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4271,10 +4295,13 @@ int KYTY_SYSV_ABI NpGetAccountIdA(int user_id, uint64_t* account_id) {
EXIT_NOT_IMPLEMENTED(account_id == nullptr); EXIT_NOT_IMPLEMENTED(account_id == nullptr);
// *account_id = 0x00000000feedfaceull;
*account_id = 0; *account_id = 0;
// return OK; if (IsSignedIn(user_id)) {
*account_id = 0x00000000feedfaceull;
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4287,10 +4314,12 @@ int KYTY_SYSV_ABI NpGetAccountCountryA(int user_id, void* country_code) {
auto* code = static_cast<NpCountryCode*>(country_code); auto* code = static_cast<NpCountryCode*>(country_code);
std::memset(code, 0, sizeof(*code)); std::memset(code, 0, sizeof(*code));
// code->data[0] = '';
// code->data[1] = 'S';
// return OK; if (IsSignedIn(user_id)) {
std::memcpy(code->data, "US", 2);
return OK;
}
return np_error_signed_out; return np_error_signed_out;
} }
@@ -4305,11 +4334,14 @@ int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age) {
return np_error_invalid_argument; return np_error_invalid_argument;
} }
// *age = 21; std::lock_guard lock(g_np_request_mutex);
*age = 0; auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
// return OK; *age = IsSignedIn(user_id) ? 21 : 0;
return np_error_signed_out; return np_complete_locked(request, IsSignedIn(user_id) ? OK : np_error_signed_out);
} }
int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) { int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_code) {
@@ -4325,11 +4357,17 @@ int KYTY_SYSV_ABI NpGetAccountLanguage2(int req_id, int user_id, void* language_
std::lock_guard lock(g_np_request_mutex); std::lock_guard lock(g_np_request_mutex);
if (np_get_request_locked(req_id) == nullptr) { auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found; return np_error_request_not_found;
} }
return np_error_signed_out; auto* code = static_cast<NpLanguageCode*>(language_code);
std::memset(code, 0, sizeof(*code));
if (IsSignedIn(user_id)) {
std::memcpy(code->code, "en-US", 5);
}
return np_complete_locked(request, IsSignedIn(user_id) ? OK : np_error_signed_out);
} }
int KYTY_SYSV_ABI NpCreateRequest() { int KYTY_SYSV_ABI NpCreateRequest() {
@@ -4428,8 +4466,9 @@ int KYTY_SYSV_ABI NpCheckNpAvailability(int req_id, const char* user, void* resu
return np_error_request_not_found; return np_error_request_not_found;
} }
// return OK; return np_complete_locked(request, std::strcmp(user, "Kyty") == 0
return np_complete_signed_out_locked(request); ? np_reachability_result(Config::GetUserId())
: np_error_signed_out);
} }
int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) { int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) {
@@ -4451,8 +4490,7 @@ int KYTY_SYSV_ABI NpCheckNpReachability(int req_id, int user_id) {
return np_error_request_not_found; return np_error_request_not_found;
} }
// return OK; return np_complete_locked(request, np_reachability_result(user_id));
return np_complete_signed_out_locked(request);
} }
int KYTY_SYSV_ABI NpPollAsync(int req_id, int* result) { int KYTY_SYSV_ABI NpPollAsync(int req_id, int* result) {
@@ -4509,23 +4547,10 @@ int KYTY_SYSV_ABI NpCheckPremium(int req_id, const NpCheckPremiumParameter* para
return np_error_request_not_found; return np_error_request_not_found;
} }
if (request->state == NpRequestState::Complete) {
request->result = np_error_invalid_argument;
return np_error_invalid_argument;
}
if (request->state == NpRequestState::Aborted) {
return np_error_aborted;
}
std::memset(result, 0, sizeof(*result)); std::memset(result, 0, sizeof(*result));
// result->authorized = true;
// request->state = NpRequestState::Complete; // The simulated account has no premium eligibility.
// request->result = OK; return np_complete_locked(request, np_reachability_result(param->user_id));
// return OK;
return np_complete_signed_out_locked(request);
} }
int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) { int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) {
@@ -4535,7 +4560,7 @@ int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) {
LOGF("\t user_id = %d\n", user_id); LOGF("\t user_id = %d\n", user_id);
*state = np_state_signed_out; *state = IsSignedIn(user_id) ? np_state_signed_in : np_state_signed_out;
return OK; return OK;
} }
@@ -4549,8 +4574,7 @@ int KYTY_SYSV_ABI NpGetNpReachabilityState(int user_id, uint32_t* state) {
LOGF("\t user_id = %d\n", user_id); LOGF("\t user_id = %d\n", user_id);
// *state = 2; // SCE_NP_REACHABILITY_STATE_REACHABLE *state = np_reachability_result(user_id) == OK ? 2 : 0; // REACHABLE / UNAVAILABLE
*state = 0; // SCE_NP_REACHABILITY_STATE_UNAVAILABLE
return OK; return OK;
} }
@@ -4564,7 +4588,7 @@ int KYTY_SYSV_ABI NpHasSignedUp(int user_id, bool* has_signed_up) {
LOGF("\t user_id = %d\n", user_id); LOGF("\t user_id = %d\n", user_id);
*has_signed_up = false; *has_signed_up = IsSignedIn(user_id);
return OK; return OK;
} }
+2
View File
@@ -182,6 +182,8 @@ struct NpCheckPremiumResult;
using NpStateCallbackA = KYTY_SYSV_ABI void (*)(int user_id, uint32_t state, void* userdata); using NpStateCallbackA = KYTY_SYSV_ABI void (*)(int user_id, uint32_t state, void* userdata);
bool IsSignedIn(int user_id);
int KYTY_SYSV_ABI NpCheckCallback(); int KYTY_SYSV_ABI NpCheckCallback();
int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret); int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret);
int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction); int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction);
+121 -7
View File
@@ -568,6 +568,9 @@ struct Ngs2VoiceInternal {
std::vector<float> output_matrix; std::vector<float> output_matrix;
uint32_t channels = 0; uint32_t channels = 0;
uint32_t output_id = 0; uint32_t output_id = 0;
float fbw_level = 1.0f;
float lfe_level = 1.0f;
float dry_level = 0.0f;
bool rendering = false; bool rendering = false;
bool rendered = false; bool rendered = false;
bool has_samples = false; bool has_samples = false;
@@ -710,10 +713,11 @@ struct Ngs2VoiceInternal {
if (count > capacity) { if (count > capacity) {
return NGS2_ERROR_INVALID_NUM_WAVEFORM_BLOCKS; return NGS2_ERROR_INVALID_NUM_WAVEFORM_BLOCKS;
} }
EXIT_NOT_IMPLEMENTED(!accepts_blocks || (flags & ~0x17u) != 0);
const bool clear = (flags & 4u) != 0; const bool clear = (flags & 4u) != 0;
const bool only_data = (flags & 2u) != 0; const bool only_data = (flags & 2u) != 0;
const bool join = only_data && (flags & 0x10u) == 0; const bool join = only_data && (flags & 0x10u) == 0;
// CLEAR reopens a list that the previous addition closed.
EXIT_NOT_IMPLEMENTED((!accepts_blocks && !clear) || (flags & ~0x17u) != 0);
const auto retained = clear ? 0 : blocks.size(); const auto retained = clear ? 0 : blocks.size();
Block tail = retained == 0 ? Block {} : blocks.back(); Block tail = retained == 0 ? Block {} : blocks.back();
std::vector<Block> pending; std::vector<Block> pending;
@@ -1800,6 +1804,18 @@ static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) {
voice.has_samples = output != 0; voice.has_samples = output != 0;
} }
// Mono and stereo convert at -3 dB, mono feeds the surround front center, others map 1:1.
static float Ngs2DefaultMatrixLevel(uint32_t src_channels, uint32_t src, uint32_t dst_channels,
size_t dst) {
if ((src_channels == 1 && dst_channels == 2) || (src_channels == 2 && dst_channels == 1)) {
return std::numbers::sqrt2_v<float> / 2;
}
if (src_channels == 1 && dst_channels > 2) {
return dst == 2 ? 1.0f : 0.0f;
}
return src == dst ? 1.0f : 0.0f;
}
static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2VoiceInternal*>& voices, static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2VoiceInternal*>& voices,
uint32_t grain) { uint32_t grain) {
if (voice.rendered) { if (voice.rendered) {
@@ -1848,7 +1864,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
EXIT_NOT_IMPLEMENTED(port.input >= voice.rack->NumBuffers()); EXIT_NOT_IMPLEMENTED(port.input >= voice.rack->NumBuffers());
const auto* input = source->Buffer(source->rack->PortBufferId(p), grain); const auto* input = source->Buffer(source->rack->PortBufferId(p), grain);
auto* output = voice.Buffer(port.input, grain); auto* output = voice.Buffer(port.input, grain);
// A port without a matrix, or whose matrix has no levels, uses the default mix.
const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix); const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix);
if (matrix != nullptr && matrix->empty()) {
matrix = nullptr;
}
const size_t output_channels = const size_t output_channels =
matrix == nullptr matrix == nullptr
? voice.channels ? voice.channels
@@ -1857,7 +1877,8 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
for (uint32_t src = 0; src < source->channels; ++src) { for (uint32_t src = 0; src < source->channels; ++src) {
const float level = const float level =
port.volume * (matrix == nullptr port.volume * (matrix == nullptr
? (src == dst ? 1.0f : 0.0f) ? Ngs2DefaultMatrixLevel(source->channels, src,
voice.channels, dst)
: (*matrix)[dst * source->channels + src]); : (*matrix)[dst * source->channels + src]);
for (uint32_t i = 0; i < grain; ++i) { for (uint32_t i = 0; i < grain; ++i) {
output[dst * grain + i] += input[src * grain + i] * level; output[dst * grain + i] += input[src * grain + i] * level;
@@ -1867,6 +1888,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
voice.has_samples = true; voice.has_samples = true;
} }
} }
if (voice.has_samples && voice.rack->type == Ngs2RackType::Reverb) {
for (auto& sample: voice.samples) {
sample *= voice.dry_level;
}
}
if (voice.has_samples) { if (voice.has_samples) {
const auto& custom = voice.rack->option.custom_sampler.custom_rack_option; const auto& custom = voice.rack->option.custom_sampler.custom_rack_option;
const auto rate = voice.rack->ngs->option.sample_rate; const auto rate = voice.rack->ngs->option.sample_rate;
@@ -1978,10 +2004,13 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
pcm = static_cast<float*>(output.buffer); pcm = static_cast<float*>(output.buffer);
} }
for (uint32_t dst = 0; dst < output.num_channels; ++dst) { for (uint32_t dst = 0; dst < output.num_channels; ++dst) {
// Output channel 3 is LFE; every other channel is full band.
const float gain = dst == 3 ? voice->lfe_level : voice->fbw_level;
for (uint32_t src = 0; src < voice->channels; ++src) { for (uint32_t src = 0; src < voice->channels; ++src) {
const float level = voice->output_matrix.empty() const float level =
? (src == dst ? 1.0f : 0.0f) gain * (voice->output_matrix.empty()
: voice->output_matrix.at(src * output.num_channels + dst); ? static_cast<float>(src == dst)
: voice->output_matrix.at(src * output.num_channels + dst));
if (level == 0.0f) { if (level == 0.0f) {
continue; continue;
} }
@@ -2339,6 +2368,17 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i
return OK; return OK;
} }
// A patch to a rack handle connects to voice #0 of that rack.
static Ngs2VoiceInternal* Ngs2FindPatchDestination(uintptr_t handle) {
Common::LockGuard racks_lock(g_racks_mutex);
for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) {
if (reinterpret_cast<uintptr_t>(rack) == handle) {
return reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
}
}
return reinterpret_cast<Ngs2VoiceInternal*>(handle);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) {
PRINT_NAME(); PRINT_NAME();
@@ -2408,7 +2448,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param); const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size()); EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size());
voice->ports[patch->port].dest = voice->ports[patch->port].dest =
reinterpret_cast<Ngs2VoiceInternal*>(patch->dest_handle); Ngs2FindPatchDestination(patch->dest_handle);
voice->ports[patch->port].input = patch->dest_input_id; voice->ports[patch->port].input = patch->dest_input_id;
LOGF("\t connect->port = %u\n" LOGF("\t connect->port = %u\n"
"\t connect->dest_input_id = %u\n" "\t connect->dest_input_id = %u\n"
@@ -2456,7 +2496,31 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
} }
break; break;
} }
case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break; case 0x2001: {
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb);
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
switch (param->id & 0xffffu) {
case 0:
// Input and output channels, flags; channel conversion stays silent.
EXIT_NOT_IMPLEMENTED(values[0] == 0 || values[2] != 0);
voice->ResetSetupState();
voice->channels = values[1] == values[0] ? values[0] : 0;
break;
case 1: {
// The reverb parameters start with the wet and dry levels.
const auto* levels = reinterpret_cast<const float*>(values);
voice->dry_level = std::clamp(levels[1], 0.0f, 1.0f);
static std::atomic_bool wet_warned = false;
if (levels[0] != 0.0f && !wet_warned.exchange(true)) {
Log::WriteToConsoleAndLog("warning: NGS2 reverb wet signal is muted\n");
}
break;
}
default: EXIT("unsupported reverb control: 0x%08" PRIx32 "\n", param->id);
}
break;
}
case 0x3000: { case 0x3000: {
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering);
const auto* values = reinterpret_cast<const uint32_t*>(param + 1); const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
@@ -2470,6 +2534,12 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
matrix->levels + matrix->num_levels); matrix->levels + matrix->num_levels);
break; break;
} }
case 4: {
const auto* levels = reinterpret_cast<const float*>(values);
voice->fbw_level = std::clamp(levels[0], 0.0f, 4.0f);
voice->lfe_level = std::clamp(levels[1], 0.0f, 4.0f);
break;
}
case 5: voice->output_id = values[0]; break; case 5: voice->output_id = values[0]; break;
default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id); default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id);
} }
@@ -2761,6 +2831,50 @@ int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state
return OK; return OK;
} }
int KYTY_SYSV_ABI Ngs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t query_id, void* info,
size_t info_size) {
PRINT_NAME();
struct SetupInfo {
uint32_t channels, flags;
};
EXIT_NOT_IMPLEMENTED(voice_handle == 0 || info == nullptr);
EXIT_NOT_IMPLEMENTED(query_id != 0x4001 || info_size != sizeof(SetupInfo));
auto* voice = reinterpret_cast<Ngs2VoiceInternal*>(voice_handle);
Common::LockGuard lock(voice->rack->ngs->mutex);
*static_cast<SetupInfo*>(info) = {voice->channels, 0};
return OK;
}
int KYTY_SYSV_ABI Ngs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, void* info,
size_t info_size) {
PRINT_NAME();
struct PortInfo {
int32_t matrix;
float volume;
uint32_t delay_samples, input;
uintptr_t dest;
};
EXIT_NOT_IMPLEMENTED(voice_handle == 0 || info == nullptr || info_size != sizeof(PortInfo));
auto* voice = reinterpret_cast<Ngs2VoiceInternal*>(voice_handle);
Common::LockGuard lock(voice->rack->ngs->mutex);
EXIT_NOT_IMPLEMENTED(port >= voice->ports.size());
const auto& p = voice->ports[port];
*static_cast<PortInfo*>(info) = {p.matrix, p.volume, 0, p.input,
reinterpret_cast<uintptr_t>(p.dest)};
return OK;
}
int KYTY_SYSV_ABI Ngs2SystemLock(uintptr_t system_handle) { int KYTY_SYSV_ABI Ngs2SystemLock(uintptr_t system_handle) {
PRINT_NAME(); PRINT_NAME();
+4
View File
@@ -60,6 +60,10 @@ int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* comma
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
size_t state_size); size_t state_size);
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags); int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
int KYTY_SYSV_ABI Ngs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t query_id, void* info,
size_t info_size);
int KYTY_SYSV_ABI Ngs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, void* info,
size_t info_size);
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
uint32_t num_buffer_info); uint32_t num_buffer_info);
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info); int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info);
+14
View File
@@ -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;
+1
View File
@@ -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);
+5
View File
@@ -74,11 +74,14 @@ 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");
::printf(" --vulkan-validation Enable Vulkan validation.\n"); ::printf(" --vulkan-validation Enable Vulkan validation.\n");
::printf(" --network-connected Use the host local network.\n"); ::printf(" --network-connected Use the host local network.\n");
::printf(" --emulate-psn Simulate PSN sign-in for compatibility.\n");
::printf(" --gpu-assisted-validation Bounds-check shader accesses on the GPU.\n" ::printf(" --gpu-assisted-validation Bounds-check shader accesses on the GPU.\n"
" Implies --vulkan-validation; very slow.\n"); " Implies --vulkan-validation; very slow.\n");
::printf(" --shader-validation Enable shader validation.\n"); ::printf(" --shader-validation Enable shader validation.\n");
@@ -204,8 +207,10 @@ 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},
{"--emulate-psn", &ConfigOptions::psn_emulation_enabled},
{"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled}, {"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled},
{"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled}, {"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled},
{"--shader-validation", &ConfigOptions::shader_validation_enabled}, {"--shader-validation", &ConfigOptions::shader_validation_enabled},
+283 -5
View File
@@ -73,6 +73,10 @@ namespace Libs::LibNpCommerce {
void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols); void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols);
} }
namespace Libs::LibNpAuth {
void InitNet_1_NpAuth(Loader::SymbolDatabase *symbols);
}
namespace Libs::LibNpEntitlementAccess { namespace Libs::LibNpEntitlementAccess {
void InitNet_1_NpEntitlementAccess(Loader::SymbolDatabase *symbols); void InitNet_1_NpEntitlementAccess(Loader::SymbolDatabase *symbols);
} }
@@ -97,6 +101,17 @@ void WaitForMutexTest(Predicate ready) {
} }
} }
void TestStaticRwlockDestroy() {
using namespace Libs::LibKernel;
PthreadRwlock rwlock = nullptr;
Check(PthreadRwlockDestroy(nullptr) == KERNEL_ERROR_EINVAL &&
PthreadRwlockDestroy(&rwlock) == OK && rwlock == nullptr,
"destroy an untouched static rwlock without allocating it");
Check(PthreadRwlockRdlock(&rwlock) == OK && PthreadRwlockUnlock(&rwlock) == OK &&
PthreadRwlockDestroy(&rwlock) == OK,
"static rwlock remains usable after destroying its untouched initializer");
}
struct MutexWaiter { struct MutexWaiter {
Libs::LibKernel::PthreadMutex *mutex; Libs::LibKernel::PthreadMutex *mutex;
Libs::LibKernel::KernelUseconds timeout = 0; Libs::LibKernel::KernelUseconds timeout = 0;
@@ -573,17 +588,16 @@ void TestAddContent(const std::filesystem::path& root) {
Check(mount(0, &info[3].label, &point) == OK && Check(mount(0, &info[3].label, &point) == OK &&
!std::string_view(point.data).ends_with('/'), !std::string_view(point.data).ends_with('/'),
"mount owned add-on without trailing slash"); "mount owned add-on without trailing slash");
MountPoint repeated {};
Check(mount(0, &info[3].label, &repeated) == static_cast<int>(0x80d90003u),
"duplicate mount reports BUSY");
const std::string mounted_file = std::string(point.data) + "/payload.txt"; const std::string mounted_file = std::string(point.data) + "/payload.txt";
const int fd = FileSystem::KernelOpen(mounted_file.c_str(), 0, 0); const int fd = FileSystem::KernelOpen(mounted_file.c_str(), 0, 0);
std::array<char, 11> payload {}; std::array<char, 11> payload {};
Check(fd >= 3 && FileSystem::KernelRead(fd, payload.data(), payload.size()) == payload.size() && Check(fd >= 3 && FileSystem::KernelRead(fd, payload.data(), payload.size()) == payload.size() &&
std::string_view(payload.data(), payload.size()) == "DLC payload" && std::string_view(payload.data(), payload.size()) == "DLC payload" &&
FileSystem::KernelClose(fd) == OK, FileSystem::KernelClose(fd) == OK,
"guest reads files from the selected title's add-on"); "guest reads the original mount after a rejected duplicate mount");
MountPoint repeated {};
Check(mount(0, &info[3].label, &repeated) == OK &&
std::strcmp(repeated.data, point.data) == 0,
"duplicate mount returns the same mount point");
Check(unmount(&point) == OK && FileSystem::GetRealFilename(mounted_file).empty() && Check(unmount(&point) == OK && FileSystem::GetRealFilename(mounted_file).empty() &&
unmount(&point) == static_cast<int>(0x80d90004u), unmount(&point) == static_cast<int>(0x80d90004u),
"unmount removes mapping and repeated unmount reports NOT_MOUNTED"); "unmount removes mapping and repeated unmount reports NOT_MOUNTED");
@@ -2082,6 +2096,256 @@ void TestNetCtlHostIpv4() {
} }
#endif #endif
template <int Mode>
void KYTY_SYSV_ABI RecordNpState(int user_id, uint32_t state, void* arg) {
auto& event = *static_cast<std::array<int, 3>*>(arg);
++event[0];
event[1] = user_id;
event[2] = static_cast<int>(state);
}
void TestNpEmulation() {
using namespace Libs::Network::NpManager;
using namespace Libs::Network::NetCtl;
constexpr int SignedOut = static_cast<int>(0x80550006u);
constexpr int NetworkUnavailable = static_cast<int>(0x8055a02au);
constexpr int Aborted = static_cast<int>(0x80550012u);
struct AsyncParameters {
size_t size = sizeof(AsyncParameters);
uint64_t affinity = 0;
int priority = 0;
uint8_t padding[4] {};
} async_parameters;
struct PremiumParameters {
size_t size = sizeof(PremiumParameters);
int user_id = 0;
uint8_t padding[4] {};
uint64_t features = 1;
uint8_t reserved[32] {};
} premium_parameters;
struct PremiumResult {
bool authorized = true;
uint8_t reserved[32] {};
} premium_result;
const auto create_request = [&] (bool async) {
const int request = async
? NpCreateAsyncRequest(reinterpret_cast<const NpCreateAsyncRequestParameter*>(
&async_parameters))
: NpCreateRequest();
Check(request > 0, "create NP request");
return request;
};
const auto check_request = [&] (bool async, int expected, auto operation) {
const int request = create_request(async);
Check(operation(request) == (async ? OK : expected),
"NP call separates asynchronous submission from its execution result");
if (async) {
int result = 123;
Check(NpPollAsync(request, &result) == OK && result == expected,
"NP polling reports the completed operation result");
}
Check(NpDeleteRequest(request) == OK, "release completed NP request");
};
Config::ConfigOptions options;
options.printf_direction = Config::LogDirection::Silent;
Check(!options.network_connected && !options.psn_emulation_enabled,
"host networking and PSN emulation both require explicit opt-in");
static std::array<std::array<int, 3>, 4> events {};
const std::array<NpStateCallbackA, 4> callbacks {
RecordNpState<0>, RecordNpState<1>, RecordNpState<2>, RecordNpState<3>};
int mode = 0;
for (bool network : {false, true}) {
int previous_network_state = -1;
for (bool emulate : {false, true}) {
options.network_connected = network;
options.psn_emulation_enabled = emulate;
Config::Load(options);
Check(Config::IsNetworkConnected() == network && Config::PsnEmulationEnabled() == emulate,
"loading PSN emulation does not change the independent network option");
Check(NetCtlInit() == OK, "initialize network state for NP mode");
int network_state = -1;
Check(NetCtlGetState(&network_state) == OK && (network || network_state == 0),
"PSN emulation cannot connect disabled host networking");
Check(previous_network_state == -1 || previous_network_state == network_state,
"PSN emulation does not change host network connectivity");
previous_network_state = network_state;
const bool connected = network_state == 3;
Check(NpRegisterStateCallbackA(callbacks[mode], &events[mode]) > 0 &&
NpCheckCallback() == OK && events[mode][0] == 1 &&
events[mode][1] == Config::GetUserId() &&
events[mode][2] == (emulate ? 2 : 1),
"initial NP callback matches the selected emulated sign-in state");
++mode;
for (int player = 0; player < Config::MAX_PLAYERS; ++player) {
const int user_id = Config::GetPlayerUserId(player);
const bool signed_in = emulate && player == 0;
const int identity_result = signed_in ? OK : SignedOut;
const int online_result = !signed_in ? SignedOut : connected ? OK : NetworkUnavailable;
uint32_t state = 99;
bool signed_up = false;
Check(NpGetState(user_id, &state) == OK && state == (signed_in ? 2u : 1u) &&
NpHasSignedUp(user_id, &signed_up) == OK && signed_up == signed_in,
"only the primary emulated user has a persistent signed-in account");
Check(NpGetNpReachabilityState(user_id, &state) == OK &&
state == (signed_in && connected ? 2u : 0u),
"NP reachability requires both the emulated account and host connectivity");
uint64_t account_id = 0;
std::array<char, 20> online_id {};
std::array<char, 36> np_id {};
std::array<char, 4> country {};
Check(NpGetAccountIdA(user_id, &account_id) == identity_result &&
NpGetOnlineId(user_id, reinterpret_cast<NpOnlineId*>(online_id.data())) ==
identity_result &&
NpGetNpId(user_id, reinterpret_cast<NpId*>(np_id.data())) == identity_result &&
NpGetAccountCountryA(user_id, country.data()) == identity_result,
"NP identity and account-country getters agree on primary and guest sign-in");
if (signed_in) {
Check(account_id != 0 && online_id[0] != 0 && online_id[16] == 0 &&
std::strcmp(online_id.data(), np_id.data()) == 0 &&
country[0] != 0 && country[1] != 0 && country[2] == 0,
"emulated account identity and country remain usable while disconnected");
}
for (bool async : {false, true}) {
check_request(async, online_result, [&] (int request) {
return NpCheckNpReachability(request, user_id);
});
uint8_t age = 0;
check_request(async, identity_result, [&] (int request) {
return NpGetAccountAge(request, user_id, &age);
});
std::array<char, 48> language {};
check_request(async, identity_result, [&] (int request) {
return NpGetAccountLanguage2(request, user_id, language.data());
});
Check(!signed_in || (age > 0 && language[0] != 0 && language[35] == 0),
"signed-in account metadata is available independently of host networking");
premium_parameters.user_id = user_id;
premium_result.authorized = true;
check_request(async, online_result, [&] (int request) {
return NpCheckPremium(request,
reinterpret_cast<const NpCheckPremiumParameter*>(&premium_parameters),
reinterpret_cast<NpCheckPremiumResult*>(&premium_result));
});
Check(!premium_result.authorized, "PSN emulation does not grant premium eligibility");
if (player == 0) {
check_request(async, online_result, [&] (int request) {
return NpCheckNpAvailability(request, "Kyty", nullptr);
});
}
}
}
const int aborted = create_request(true);
int result = 123;
Check(NpAbortRequest(aborted) == OK &&
NpCheckNpReachability(aborted, Config::GetUserId()) == Aborted &&
NpPollAsync(aborted, &result) == OK && result == Aborted &&
NpDeleteRequest(aborted) == OK,
"NP emulation does not turn an aborted request into a successful one");
NetCtlTerm();
}
}
Config::Load(Config::ConfigOptions {});
}
void TestNpAuthEmulation() {
Loader::SymbolDatabase symbols;
Libs::LibNpAuth::InitNet_1_NpAuth(&symbols);
const auto resolve = [&] (const char* nid) {
const auto* symbol = symbols.FindByNid(nid, Loader::SymbolType::Func);
Check(symbol != nullptr, "resolve NP authentication exports");
return symbol->vaddr;
};
const auto create = reinterpret_cast<int(KYTY_SYSV_ABI*)()>(resolve("6bwFkosYRQg"));
const auto create_async = reinterpret_cast<int(KYTY_SYSV_ABI*)(const void*)>(
resolve("N+mr7GjTvr8"));
const auto destroy = reinterpret_cast<int(KYTY_SYSV_ABI*)(int)>(resolve("H8wG9Bk-nPc"));
const auto poll = reinterpret_cast<int(KYTY_SYSV_ABI*)(int, int*)>(resolve("gjSyfzSsDcE"));
const auto get_code = reinterpret_cast<int(KYTY_SYSV_ABI*)(int, const void*, void*, int*)>(
resolve("KI4dHLlTNl0"));
const auto get_token = reinterpret_cast<int(KYTY_SYSV_ABI*)(int, const void*, void*)>(
resolve("RdsFVsgSpZY"));
constexpr int InvalidArgument = static_cast<int>(0x80550301u);
constexpr int InvalidSize = static_cast<int>(0x80550302u);
constexpr int ServiceUnavailable = static_cast<int>(0x80550401u);
constexpr int SignedOut = static_cast<int>(0x80550006u);
struct AuthorizationCodeParameters {
size_t size = sizeof(AuthorizationCodeParameters);
int user_id = 0;
uint8_t padding[4] {};
const void* client_id = "test-client";
const char* scope = "test-scope";
} code_parameters;
struct IdTokenParameters {
size_t size = sizeof(IdTokenParameters);
int user_id = 0;
uint8_t padding[4] {};
const void* client_id = "test-client";
const void* client_secret = "test-secret";
const char* scope = "test-scope";
} token_parameters;
std::array<uint8_t, 136> code {};
std::array<uint8_t, 4104> token {};
const std::array<uint64_t, 3> async_parameters {24, 0, 0};
int issuer = 0;
const int invalid_request = create();
code_parameters.size = 0;
token_parameters.size = 0;
Check(invalid_request > 0 &&
get_code(invalid_request, nullptr, code.data(), &issuer) == InvalidArgument &&
get_token(invalid_request, nullptr, token.data()) == InvalidArgument &&
get_code(invalid_request, &code_parameters, code.data(), &issuer) == InvalidSize &&
get_token(invalid_request, &token_parameters, token.data()) == InvalidSize &&
destroy(invalid_request) == OK,
"NP authentication rejects missing and undersized user parameters");
code_parameters.size = sizeof(code_parameters);
token_parameters.size = sizeof(token_parameters);
Config::ConfigOptions options;
options.printf_direction = Config::LogDirection::Silent;
for (bool network : {false, true}) {
for (bool emulate : {false, true}) {
options.network_connected = network;
options.psn_emulation_enabled = emulate;
Config::Load(options);
for (int player = 0; player < Config::MAX_PLAYERS; ++player) {
code_parameters.user_id = Config::GetPlayerUserId(player);
token_parameters.user_id = code_parameters.user_id;
const int expected = emulate && player == 0 ? ServiceUnavailable : SignedOut;
for (bool async : {false, true}) {
for (bool id_token : {false, true}) {
const int request = async ? create_async(async_parameters.data()) : create();
Check(request > 0, "create NP authentication request");
code.fill(0xff);
token.fill(0xff);
issuer = -1;
const int result = id_token
? get_token(request, &token_parameters, token.data())
: get_code(request, &code_parameters, code.data(), &issuer);
Check(result == (async ? OK : expected),
"NP authentication distinguishes an unavailable service from sign-out");
if (async) {
int completion = 123;
Check(poll(request, &completion) == OK && completion == expected,
"asynchronous NP authentication delivers the actual failure");
}
const auto zero = [] (uint8_t value) { return value == 0; };
Check(id_token ? std::all_of(token.begin(), token.end(), zero)
: std::all_of(code.begin(), code.end(), zero) && issuer == 0,
"PSN emulation does not fabricate authorization codes or ID tokens");
Check(destroy(request) == OK, "release NP authentication request");
}
}
}
}
}
Config::Load(Config::ConfigOptions {});
}
void CheckEtherAddressFormatting() { void CheckEtherAddressFormatting() {
Loader::SymbolDatabase symbols; Loader::SymbolDatabase symbols;
Libs::LibNet::InitNet_1_Net(&symbols); Libs::LibNet::InitNet_1_Net(&symbols);
@@ -2458,7 +2722,19 @@ int main(int argc, char** argv) {
Config::Load(options); Config::Load(options);
subsystems.Initialize<Log::Lifecycle>(); subsystems.Initialize<Log::Lifecycle>();
if (argc == 2 && std::string_view(argv[1]) == "--network-only") {
#if defined(__linux__)
TestNetCtlHostIpv4();
#endif
TestNpEmulation();
TestNpAuthEmulation();
subsystems.Destroy();
std::printf("KernelFileSystemTests: network cases passed\n");
return 0;
}
TestMutexHandoff(); TestMutexHandoff();
TestStaticRwlockDestroy();
if (argc == 2 && std::string_view(argv[1]) == "--mutex-only") { if (argc == 2 && std::string_view(argv[1]) == "--mutex-only") {
subsystems.Destroy(); subsystems.Destroy();
std::printf("KernelFileSystemTests: mutex cases passed\n"); std::printf("KernelFileSystemTests: mutex cases passed\n");
@@ -2503,6 +2779,8 @@ int main(int argc, char** argv) {
#if defined(__linux__) #if defined(__linux__)
TestNetCtlHostIpv4(); TestNetCtlHostIpv4();
#endif #endif
TestNpEmulation();
TestNpAuthEmulation();
TestNpWebApi2Memory(); TestNpWebApi2Memory();
TestNpCommerceDialog(); TestNpCommerceDialog();
graphics.reset(); graphics.reset();
+138
View File
@@ -807,6 +807,76 @@ void TestPcm16RenderOutput() {
"PCM16 output retained samples from the previous render"); "PCM16 output retained samples from the previous render");
} }
void TestPatchToRackHandle() {
Ngs2Internal system;
Ngs2RackOptionUnion sampler_option {}, mastering_option {};
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
sampler_option.common.max_voices = 1;
RenderRack sampler(system, 0x1000, sampler_option.common);
RenderRack master(system, 0x3000, mastering_option.common);
auto& source = sampler.Voice();
Ngs2VoicePatchParam patch {{24, 0, 5}, 0, 0, master.handle};
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&source), &patch.header) == OK &&
source.ports[0].dest == &master.Voice(),
"patch to a rack handle did not connect to its voice #0");
}
void TestMasteringGain() {
Ngs2Internal system;
system.option = Ngs2DefaultSystemOption();
system.option.num_grain_samples = 1;
Ngs2RackOptionUnion sampler_option {}, mastering_option {};
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
sampler_option.common.max_voices = 1;
RenderRack sampler(system, 0x1000, sampler_option.common);
RenderRack master(system, 0x3000, mastering_option.common);
auto& source = sampler.Voice();
auto& mastering = master.Voice();
mastering.channels = 8;
mastering.state = Ngs2VoicePlayState::Playing;
const int16_t frame[8] = {8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192};
source.SetupSampler({0x12, 8, 48000});
source.blocks.push_back({reinterpret_cast<const uint8_t*>(frame), {0, sizeof(frame), 0, 0, 1}});
source.ports[0].dest = &mastering;
source.SetEvent(1);
struct {
Ngs2VoiceParamHeader header {16, 0, 0x30000004};
float fbw = 5.0f, lfe = 0.5f;
} gain;
float pcm[8] = {};
Ngs2RenderBufferInfo output {pcm, sizeof(pcm), 0x18, 8};
const float expected[8] = {1.0f, 1.0f, 1.0f, 0.125f, 1.0f, 1.0f, 1.0f, 1.0f};
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&mastering), &gain.header) == OK &&
Ngs2SystemRender(reinterpret_cast<uintptr_t>(&system), &output, 1) == OK &&
std::ranges::equal(pcm, expected),
"mastering gain was not clamped or applied per full band and LFE channel");
}
void TestReverbDryLevel() {
Fixture f(48000);
Ngs2RackInternal reverb_rack {};
reverb_rack.ngs = &f.system;
reverb_rack.type = Ngs2RackType::Reverb;
Ngs2VoiceInternal reverb;
reverb.rack = &reverb_rack;
f.voice.ports.push_back({&reverb, 0, 1.0f, -1});
const int16_t pcm = 8192;
f.Queue(&pcm, 1, 0);
struct {
Ngs2VoiceParamHeader header {24, 24, 0x20010000};
uint32_t channels[4] = {1, 1, 0, 0};
Ngs2VoiceParamHeader levels_header {96, 0, 0x20010001};
float levels[22] = {0.6f, 0.4f};
} setup;
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&reverb), &setup.header);
reverb.SetEvent(1);
Ngs2RenderVoice(reverb, {&f.voice, &reverb}, 1);
Check(reverb.has_samples && reverb.samples[0] == 0.25f * 0.4f,
"reverb did not pass its dry signal");
}
void TestCustomMasteringChannelMixer() { void TestCustomMasteringChannelMixer() {
Ngs2Internal system; Ngs2Internal system;
system.option = Ngs2DefaultSystemOption(); system.option = Ngs2DefaultSystemOption();
@@ -1026,6 +1096,35 @@ void TestMonoRateAndRouting() {
"22.05 kHz effect duration was incorrect"); "22.05 kHz effect duration was incorrect");
} }
void TestDefaultMatrix() {
struct Case {
uint32_t src_channels;
int32_t matrix;
uint32_t dst_channels;
std::vector<float> expected;
};
constexpr float minus_3db = std::numbers::sqrt2_v<float> / 2;
const int16_t pcm[] = {8192, -16384};
for (const auto& c: {Case {1, -1, 2, {0.25f * minus_3db, 0.25f * minus_3db}},
Case {1, 0, 2, {0.25f * minus_3db, 0.25f * minus_3db}},
Case {2, -1, 1, {0.25f * minus_3db + -0.5f * minus_3db}},
Case {1, -1, 6, {0, 0, 0.25f, 0, 0, 0}}}) {
Fixture f(48000, c.src_channels);
Ngs2RackInternal rack {};
rack.ngs = &f.system;
rack.type = Ngs2RackType::Mastering;
Ngs2VoiceInternal dest;
dest.rack = &rack;
dest.channels = c.dst_channels;
dest.state = Ngs2VoicePlayState::Playing;
f.voice.matrices.resize(1);
f.voice.ports.push_back({&dest, 0, 1.0f, c.matrix});
f.Queue(pcm, 1, 0);
Ngs2RenderVoice(dest, {&f.voice, &dest}, 1);
Check(dest.samples == c.expected, "default port mix produced the wrong channel levels");
}
}
void TestMatrixOutputChannels() { void TestMatrixOutputChannels() {
Fixture f(48000, 2); Fixture f(48000, 2);
Ngs2RackInternal mastering_rack {}; Ngs2RackInternal mastering_rack {};
@@ -1111,6 +1210,38 @@ void TestDeferredSamplerSetup() {
"sampler did not play after deferred stream setup"); "sampler did not play after deferred stream setup");
} }
void TestClearReopensClosedList() {
Fixture f(48000);
const int16_t first = 8192, second = -16384;
f.Queue(&first, 1, 0);
f.Queue(&second, 1, 4);
f.Render(1);
Check(f.voice.samples[0] == -0.5f, "CLEAR did not reopen a closed waveform list");
}
void TestSetupQuery() {
Fixture f(48000, 2);
uint32_t info[2] = {99, 99};
Ngs2VoiceQueryInfo(reinterpret_cast<uintptr_t>(&f.voice), 0x4001, info, sizeof(info));
Check(info[0] == 2 && info[1] == 0, "setup query did not report the voice channels");
}
void TestPortInfo() {
Fixture f(48000);
Ngs2VoiceInternal dest;
f.voice.ports.push_back({&dest, 1, 0.5f, 0});
struct {
int32_t matrix;
float volume;
uint32_t delay_samples, input;
uintptr_t dest;
} info {99, 99, 99, 99, 99};
Ngs2VoiceGetPortInfo(reinterpret_cast<uintptr_t>(&f.voice), 0, &info, sizeof(info));
Check(info.matrix == 0 && info.volume == 0.5f && info.delay_samples == 0 && info.input == 1 &&
info.dest == reinterpret_cast<uintptr_t>(&dest),
"port info did not report the port routing and volume");
}
void TestPlayStateBeforeRender() { void TestPlayStateBeforeRender() {
Fixture f(48000); Fixture f(48000);
const int16_t pcm[] = {8192, -16384}; const int16_t pcm[] = {8192, -16384};
@@ -1803,6 +1934,9 @@ int main() {
TestAtrac9Vibration(); TestAtrac9Vibration();
TestAtrac9Discrete(); TestAtrac9Discrete();
TestPcm16RenderOutput(); TestPcm16RenderOutput();
TestPatchToRackHandle();
TestMasteringGain();
TestReverbDryLevel();
TestCustomMasteringChannelMixer(); TestCustomMasteringChannelMixer();
TestCustomVoiceStateCapacity(); TestCustomVoiceStateCapacity();
TestSubmixerSetupAndRouting(); TestSubmixerSetupAndRouting();
@@ -1837,8 +1971,12 @@ int main() {
TestPcmSkipAcrossBlocks(); TestPcmSkipAcrossBlocks();
TestPcmBlocksEndWithTheirData(); TestPcmBlocksEndWithTheirData();
TestMonoRateAndRouting(); TestMonoRateAndRouting();
TestDefaultMatrix();
TestMatrixOutputChannels(); TestMatrixOutputChannels();
TestCustomPcmStillPlays(); TestCustomPcmStillPlays();
TestClearReopensClosedList();
TestSetupQuery();
TestPortInfo();
TestPlayStateBeforeRender(); TestPlayStateBeforeRender();
TestStatePublication(); TestStatePublication();
TestStopThenPlayBeforeRender(); TestStopThenPlayBeforeRender();
+346
View File
@@ -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, &params), 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, &params), 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, &params), 0);
std::fill(command.begin(), command.end(), 0xdeadbeef);
space.dwords = dwords - 1;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_NO_SPACE);
space.dwords = 0x4002;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_BAD_COMMAND_BUFFER);
space.dwords = dwords;
params.width = 961;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_RENDER_SIZE);
params.width = 960;
output = Texture(960, 539, 0x10000);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_OUTPUT_TEXTURE);
params.input = nullptr;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
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, &params), 0);
command.fill(0xdeadbeef);
params.width = 961;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_RENDER_SIZE);
params.width = 960;
input[1] &= ~(0x1ffu << 20);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_INVALID_TEXTURE);
input = Texture(960, 540, 0x20000);
input[3] = (input[3] & ~(0xfu << 28)) | (14u << 28);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_INVALID_TEXTURE);
input = Texture(960, 540, 0x20000);
output[3] &= ~(0x1fu << 20);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_OUTPUT_TEXTURE);
output = Texture(960, 540, 0x20000);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
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();
}
+228
View File
@@ -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);
}
+432
View File
@@ -1,3 +1,435 @@
TestCase TrinityBvhIntersections(bool compressed, bool bary, bool sorted,
bool wide_sort, u32 heuristic = 0, bool triangles_first = false,
u32 wave_size = 32, bool edges = false) {
TestCase test;
test.name = compressed ? (sorted ? "TrinityCompressedSorted" : "TrinityCompressedUnsorted")
: (sorted ? "TrinityFp32Sorted" : "TrinityFp32Unsorted");
if (!bary) test.name = "TrinityTriangleIds";
if (heuristic == 1) test.name = "TrinityLargestInterval";
if (heuristic == 2) test.name = "TrinityClosestMidpoint";
if (triangles_first) test.name = "TrinityTrianglesFirst";
if (wave_size == 64) test.name = "TrinityBvhWave64";
if (edges) test.name = compressed ? "TrinityCompressedEdges" : "TrinityFp32Edges";
constexpr u32 NodeStart = 4096, OutputStart = 2048, GuestBase = 0x10000;
constexpr u32 Invalid = ~0u, Infinity = 0x7f800000, Flag = 0x8484, Shape = 0x12345678;
const auto bits = [](float x) { return std::bit_cast<u32>(x); };
test.initial.resize(NodeStart + 256 * 16);
auto& data = test.initial;
const std::array<float, 12> vertices {-1,-1,2, 1,-1,2, 0,1,2, 2,1,2};
for (u32 i = 0; i < vertices.size(); ++i) data[NodeStart + i] = bits(vertices[i]);
data[NodeStart + 14] = Shape;
data[NodeStart + 15] = Flag;
const std::array<float, 12> matrix {2,0,0,1, 0,3,0,-2, 0,0,4,3};
for (u32 i = 0; i < matrix.size(); ++i) data[NodeStart + 16 + i] = bits(matrix[i]);
data[NodeStart + 28] = 0x1000;
data[NodeStart + 29] = 9u << 22; // Force opaque and flip facing.
data[NodeStart + 30] = 0x98;
data[NodeStart + 31] = 0x04123456;
const std::array<float, 8> nearDist {5,1,3,7,4,2,6,8};
const std::array<float, 8> length {1,8,3,5,7,2,4,6};
const std::array<u32, 8> children {128,138,171,190,256,266,299,318};
const auto set_bits = [&](u32 base, u32 offset, u32 count, u32 value) {
for (u32 bit = 0; bit < count; ++bit)
data[base + (offset + bit) / 32] |= ((value >> bit) & 1) << ((offset + bit) % 32);
};
for (u32 slot = 0; slot < 2; ++slot) {
const u32 base = NodeStart + (3 + slot * (compressed ? 1 : 2)) * 16;
if (compressed) {
set_bits(base, 0, 29, slot ? 32 : 16);
for (u32 i = 0; i < 4; ++i) set_bits(base, 29 + i * 3, 3, children[slot * 4 + i] & 7);
set_bits(base, 56, 8, 127); set_bits(base, 64, 8, 127); set_bits(base, 72, 8, 130);
}
for (u32 i = 0; i < 4; ++i) {
const u32 index = slot * 4 + i;
if (compressed) {
// Exact integer minima; positive maxima round outward within one quantization step.
const std::array<u32, 6> planes {0x30000, 0x30000, u32(nearDist[index]) * 8192,
0x10000, 0x10000, u32(nearDist[index] + length[index]) * 8192};
for (u32 axis = 0; axis < 6; ++axis) set_bits(base, 80 + i * 108 + axis * 18, 18, planes[axis]);
} else {
data[base + i] = children[index];
const std::array<float, 6> bounds {-1,-1,nearDist[index], 1,1,nearDist[index] + length[index]};
for (u32 axis = 0; axis < 6; ++axis) data[base + 4 + i * 6 + axis] = bits(bounds[axis]);
}
}
}
if (compressed) {
std::copy_n(data.begin() + NodeStart + 3 * 16, 16, data.begin() + NodeStart + 10 * 16);
} else {
constexpr std::array<u32, 4> lo {0x4500, 0x3c00, 0x4200, 0x4700};
constexpr std::array<u32, 4> hi {0x4600, 0x4880, 0x4600, 0x4a00};
for (u32 i = 0; i < 4; ++i) {
data[NodeStart + 160 + i] = children[i];
data[NodeStart + 164 + i * 3] = 0xbc00bc00;
data[NodeStart + 165 + i * 3] = 0x3c000000 | lo[i];
data[NodeStart + 166 + i * 3] = (hi[i] << 16) | 0x3c00;
}
}
if (edges) {
// Slot zero ends exactly at the descriptor limit; slot one lies beyond it.
std::copy_n(data.begin() + NodeStart + 3 * 16, compressed ? 16 : 32,
data.begin() + NodeStart + (compressed ? 253 : 252) * 16);
for (u32 index : {12u, 13u})
std::copy_n(data.begin() + NodeStart, 16, data.begin() + NodeStart + index * 16);
data[NodeStart + 12 * 16 + 15] = 0xc4c4; // Opaque procedural pair.
data[NodeStart + 13 * 16 + 15] = 0x9494; // Opaque, double-sided pair.
if (compressed) {
std::copy_n(data.begin() + NodeStart + 3 * 16, 16, data.begin() + NodeStart + 11 * 16);
set_bits(NodeStart + 11 * 16, 41, 4, 1);
set_bits(NodeStart + 11 * 16, 45, 4, 2);
set_bits(NodeStart + 11 * 16, 49, 4, 3);
}
}
std::vector<std::array<u32, 16>> expected(wave_size);
for (u32 lane = 0; lane < wave_size; ++lane) {
const u32 which = lane % 16, input = lane * 16;
auto& out = expected[lane];
out.fill(Invalid);
data[input + 2] = bits(20); data[input + 3] = 4; data[input + 9] = bits(1);
const auto miss = [&](u32 slot, bool invalid = false) {
out[slot * 4] = Infinity; out[slot * 4 + 1] = bits(1);
out[slot * 4 + 2] = !invalid && bary ? 0 : Invalid; out[slot * 4 + 3] = 0;
};
const auto hit = [&](u32 slot, float u, float v) {
out[slot * 4] = bits(-8); out[slot * 4 + 1] = bits(-4);
out[slot * 4 + 2] = bary ? bits(u) : Flag + slot;
out[slot * 4 + 3] = bary ? bits(v) : 1;
};
if (which <= 2 || which >= 9 && which <= 14) {
data[input + 10] = which == 1 || which == 2 ? 1 : 0;
miss(0); miss(1);
out[8] = Shape;
if (which == 1) {
data[input + 4] = bits(1); hit(1, 2, 1); out[9] = Shape;
} else {
hit(0, 1, 1);
if (which == 2) { miss(1); out[9] = Shape; }
}
if (which == 9 || which == 10 || which == 11) {
if (which == 9) data[input + 4] = 0x7fc00000;
if (which == 10) data[input + 10] = 0xfff8;
if (which == 11) data[input + 1] = 0x41u << 22;
miss(0, true); miss(1, true); out[8] = out[9] = Invalid;
} else if (which == 12) {
data[input + 1] = 0x100u << 22; miss(0);
} else if (which == 13) {
data[input + 1] = 8u << 22; out[0] = bits(8); out[1] = bits(4);
} else if (which == 14) {
data[input + 1] = 2u << 22;
if (bary) { out[2] = bits(-1); out[3] = bits(-1); }
}
} else if (which >= 3 && which <= 5) {
data[input + 10] = which == 3 ? (3 * 8 + (compressed ? 3 : 2))
: which == 4 ? (3 * 8 + (compressed ? 4 : 5)) : (10 * 8 + 4);
std::array<u32, 8> order {0,1,2,3,4,5,6,7};
const auto compare = [&](u32 x, u32 y) {
const bool xt = (children[x] & 7) < 2, yt = (children[y] & 7) < 2;
if (triangles_first && xt != yt) return xt;
if (heuristic == 1) return length[x] > length[y];
if (heuristic == 2) return 2 * nearDist[x] + length[x] < 2 * nearDist[y] + length[y];
return nearDist[x] < nearDist[y];
};
if (sorted) {
if (wide_sort && which == 3) std::stable_sort(order.begin(), order.end(), compare);
else {
std::stable_sort(order.begin(), order.begin() + 4, compare);
std::stable_sort(order.begin() + 4, order.end(), compare);
}
}
for (u32 i = 0; i < (which == 3 ? 8u : 4u); ++i) out[i] = children[order[i]];
} else if (which == 6 || which == 7 || which == 15) {
data[input + 10] = 14;
if (which == 15) { data[input] = 8; data[input + 10] = 6; }
data[input + 4] = bits(1); data[input + 5] = bits(2); data[input + 6] = bits(3);
if (which == 7) data[input + 3] = 2;
for (u32 i = 0; i < 10; ++i) out[i] = 0xabc00000 + i;
out[2] = 0x1000; out[3] = 9u << 22; out[6] = 0x123456; out[7] = which == 7 ? Invalid : 0x98;
} else data[input + 10] = 7;
if (edges) {
switch (which) {
case 0:
case 1:
case 2:
case 5:
if (which == 0) data[input] = 1; // Misaligned encoded base must not read across a block.
if (which == 1) data[input + 4] = Infinity;
if (which == 2) data[input + 2] = 0x7fc00000;
if (which == 5) { data[input + 1] = 0x2000; data[input + 10] = 0; }
miss(0, true); miss(1, true); out[8] = out[9] = Invalid;
break;
case 3:
data[input + 10] = compressed ? 253 * 8 + 3 : 252 * 8 + 2;
for (u32 i = 4; i < 8; ++i) out[i] = Invalid;
break;
case 4:
if (compressed) {
data[input + 10] = 11 * 8 + 4; data[input + 1] = 0x40u << 22;
out[0] = out[2] = Invalid;
} else {
data[input + 2] = bits(1);
for (u32 i = 0; i < 4; ++i) out[i] = Invalid;
}
break;
case 6:
data[input + 1] = 8u << 22; out[3] = 1u << 22;
break;
case 7:
data[input + 1] = 2u << 22; out[3] = 10u << 22;
break;
case 9:
case 10:
data[input + 10] = 12 * 8;
data[input + 4] = bits(100);
out[8] = Shape; miss(0); miss(1);
if (which == 9) { out[0] = bits(0); out[1] = bits(1); out[2] = bits(-1); out[3] = bits(1); }
else data[input + 1] = 0x200u << 22;
break;
case 11:
data[input + 1] = 0x10u << 22; miss(0); miss(1); out[8] = Shape;
break;
case 12:
case 13:
data[input + 1] = (which == 12 ? 0x14u : 0x10u) << 22;
if (which == 13) data[input + 10] = 13 * 8;
hit(0, 1, 1);
break;
case 14:
// The compressed +X bound rounds outward beyond exactly one.
if (compressed) {
data[input + 1] = 0; data[input + 4] = bits(1.0f + 0x1p-17f);
data[input + 10] = 3 * 8 + 4;
out.fill(Invalid);
for (u32 i = 0; i < 4; ++i) out[i] = children[i];
}
break;
}
}
for (u32 i = 0; i < 6; ++i) out[10 + i] = data[input + 4 + i];
if (which == 6 || which == 7 || which == 15) {
out[10] = bits(3); out[11] = bits(4); out[12] = bits(15); out[15] = bits(4);
}
if (lane % 32 >= 16) {
for (u32 i = 0; i < 10; ++i) out[i] = 0xabc00000 + i;
for (u32 i = 0; i < 6; ++i) out[10 + i] = data[input + 4 + i];
}
}
test.expected = data;
for (u32 lane = 0; lane < wave_size; ++lane)
std::copy(expected[lane].begin(), expected[lane].end(), test.expected.begin() + OutputStart + lane * 16);
auto& code = test.code;
AppendSMovLiteral(&code, 8, GuestBase >> 8);
AppendSMovLiteral(&code, 9, (sorted ? 1u << 31 : 0) | (triangles_first ? 1u << 20 : 0) | (heuristic << 21));
AppendSMovLiteral(&code, 10, edges ? 253 : 255);
AppendSMovLiteral(&code, 11, 0x80c00000 | (bary ? 1u << 24 : 0) |
(compressed ? 1u << 20 : 0) | (wide_sort ? 1u << 21 : 0));
code.push_back(EncodeVop2(0x1a, 1, InlineU32(6), 0));
for (u32 offset : {0u,4u,8u})
code.insert(code.end(), {EncodeMubuf0(offset == 8 ? 0x0f : 0x0e, offset * 4), EncodeMubuf1(4 + offset, 0, 1)});
for (u32 i = 0; i < 10; ++i) AppendVMovLiteral(&code, 32 + i, 0xabc00000 + i);
AppendSMovLiteral(&code, 126, 0x0000ffff);
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0x0000ffff);
code.insert(code.end(), {EncodeMimg0(0xe5, 15, 2, false, 0, true), EncodeMimg1(32, 4, 2),
0x08070605, 0x00000e0b});
code.push_back(EncodeSop1(0x04, 126, 193));
code.push_back(EncodeVop2(0x1a, 2, InlineU32(4), 0));
for (u32 i = 0; i < 10; ++i) AppendStoreVgprAtLaneDwordOffset(&code, 32 + i, 2, OutputStart + i);
for (u32 i = 0; i < 6; ++i) AppendStoreVgprAtLaneDwordOffset(&code, 8 + i, 2, OutputStart + 10 + i);
AppendEnd(&code);
test.bda_mappings = {{GuestBase, NodeStart * sizeof(u32)}};
test.compute_info.wave_size = wave_size;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
test.decoded_counts = {{"IMAGE_BVH8_INTERSECT_RAY", 1}};
test.required_spirv = {"trinity_bvh_intersect"};
return test;
}
TestCase TrinityBvhAliases() {
auto test = TrinityBvhIntersections(false, true, false, false);
test.name = "TrinityBvhRegisterAliases";
constexpr u32 OutputStart = 2048;
for (u32 lane = 0; lane < 32; ++lane) {
auto* out = test.expected.data() + OutputStart + lane * 16;
const auto* input = test.initial.data() + lane * 16;
const bool active = lane < 16, instance = active && (lane == 6 || lane == 7 || lane == 15);
if (!active) {
for (u32 i = 0; i < 6; ++i) out[4 + i] = input[i];
} else if (instance) {
out[4] = input[0]; out[5] = input[1];
out[8] = out[10]; out[9] = out[11];
} else {
// A non-instance writes its shape IDs into registers formerly holding ray XY.
out[10] = out[8]; out[11] = out[9];
}
}
auto& code = test.code;
code.clear();
AppendSMovLiteral(&code, 8, 0x100);
AppendSMovLiteral(&code, 9, 0);
AppendSMovLiteral(&code, 10, 255);
AppendSMovLiteral(&code, 11, 0x81c00000);
code.push_back(EncodeVop2(0x1a, 50, InlineU32(6), 0));
code.push_back(EncodeVop2(0x1a, 51, InlineU32(4), 0));
for (u32 i = 0; i < 10; ++i) AppendVMovLiteral(&code, i, 0xabc00000 + i);
for (u32 offset : {0u, 4u, 8u})
code.insert(code.end(), {EncodeMubuf0(offset == 8 ? 0x0f : 0x0e, offset * 4),
EncodeMubuf1(4 + offset, 0, 50)});
AppendSMovLiteral(&code, 126, 0x0000ffff);
code.insert(code.end(), {EncodeMimg0(0xe5, 15, 2, false, 0, true), EncodeMimg1(0, 4, 2),
0x08070605, 0x00000e0b});
code.push_back(EncodeSop1(0x04, 126, 193));
for (u32 i = 0; i < 10; ++i) AppendStoreVgprAtLaneDwordOffset(&code, i, 51, OutputStart + i);
for (u32 i = 0; i < 6; ++i) AppendStoreVgprAtLaneDwordOffset(&code, 8 + i, 51, OutputStart + 10 + i);
AppendEnd(&code);
return test;
}
std::vector<TestCase> TrinityBvhCases() {
return {TrinityBvhIntersections(false, true, false, false),
TrinityBvhIntersections(false, true, true, true),
TrinityBvhIntersections(true, true, false, false),
TrinityBvhIntersections(true, true, true, false),
TrinityBvhIntersections(false, false, true, true),
TrinityBvhIntersections(false, true, true, true, 1),
TrinityBvhIntersections(false, true, true, true, 2),
TrinityBvhIntersections(true, true, true, true, 0, true),
TrinityBvhIntersections(true, true, true, true, 0, false, 64),
TrinityBvhIntersections(false, true, false, false, 0, false, 32, true),
TrinityBvhIntersections(true, true, false, false, 0, false, 32, true),
TrinityBvhAliases()};
}
TestCase TrinityStack(u32 wave_size, u32 capacity, bool storage = false) {
TestCase test;
static constexpr const char* names[2][3] = {
{"TrinityStack32Capacity8", "TrinityStack32Capacity16", "TrinityStack32Capacity31"},
{"TrinityStack64Capacity8", "TrinityStack64Capacity16", "TrinityStack64Capacity31"}};
test.name = storage ? (wave_size == 32 ? "TrinityStackStorage32" : "TrinityStackStorage64")
: names[wave_size == 64][capacity == 8 ? 0 : capacity == 16 ? 1 : 2];
constexpr u32 Invalid = 0xffffffffu, Terminal = 0xfffffffeu;
constexpr u32 Parent = 1u << 29, Overflow = 1u << 30, Return = 1u << 31;
const auto state = [](u32 count, u32 parent, u32 ring, u32 flags = 0) {
return count | (parent << 5) | (ring << 10) | flags;
};
test.initial.resize(wave_size * 32);
struct Output { u32 control, node; std::array<u32, 8> lds; };
std::vector<Output> outputs(wave_size);
for (u32 lane = 0; lane < wave_size; ++lane) {
const u32 which = lane % 16;
const u32 base = which == 15 ? 0x3fff : (lane / 32) * capacity * 32 + lane % 32;
auto* input = test.initial.data() + lane * 32;
auto& out = outputs[lane];
input[0] = base << 15;
input[1] = Invalid;
std::fill(input + 2, input + 10, Invalid);
input[10] = base * 4;
for (u32 i = 0; i < 8; ++i) out.lds[i] = 100 + i * 8;
if (capacity == 8) out.lds[7] = 88;
out.node = Terminal;
out.control = 0;
const auto children = [&] {
for (u32 i = 0; i < 8; ++i) input[2 + i] = (i + 1) * 8;
};
switch (which) {
case 0: break;
case 1: input[0] |= Overflow; out.control = Overflow; out.node = Invalid; break;
case 2:
children(); out.control = state(7, 7, 7); out.node = 8;
out.lds = {64, 56, 48, 40, 32, 24, 16, 8}; break;
case 3:
input[2] = 8; input[4] = 24; input[7] = 48; input[9] = 64;
out.control = state(3, 3, 3); out.node = 8;
out.lds[0] = 64; out.lds[1] = 48; out.lds[2] = 24; out.lds[3] = 8; break;
case 4:
case 14:
input[0] |= state(capacity, capacity, 0, which == 14 ? Parent : 0);
input[2] = 8; out.control = state(capacity - 1, capacity - 1, 0, Overflow);
out.node = out.lds[0] = 8; break;
case 5:
input[0] |= state(1, 1, 0); out.control = state(0, 0, capacity - 1); out.node = 88; break;
case 6:
input[0] |= state(1, 0, 1, Parent);
out.control = state(0, 31, 0, Return); out.node = 100; break;
case 7:
input[2] = 14; out.control = Parent; out.node = out.lds[0] = 14; break;
case 8:
input[0] |= state(3, 1, 3, Parent);
out.lds[2] = 14;
out.control = state(0, 0, 2, Parent | Overflow); out.node = 14; break;
case 9:
children(); input[1] = 24; out.control = state(4, 4, 4); out.node = 32;
out.lds[0] = 64; out.lds[1] = 56; out.lds[2] = 48; out.lds[3] = 40; out.lds[4] = 32; break;
case 10:
children(); input[1] = 96; input[0] |= state(1, 1, 1);
out.node = 100; break;
case 11:
children(); input[9] = 0xfffffffbu; out.control = state(3, 3, 3); out.node = 8;
out.lds[0] = 32; out.lds[1] = 24; out.lds[2] = 16; out.lds[3] = 8; break;
case 12: children(); input[9] = 0xfffffff9u; break;
case 13:
children(); input[5] = 0xfffffffdu; out.control = state(3, 3, 3); out.node = 40;
out.lds[0] = 64; out.lds[1] = 56; out.lds[2] = 48; out.lds[3] = 40; break;
case 15:
input[0] |= state(1, 1, 1); out.node = 0; out.lds.fill(0); break;
}
out.control |= base << 15;
if ((lane % 32) >= 16) {
out.control = input[0]; out.node = input[2];
for (u32 i = 0; i < 8; ++i) out.lds[i] = 100 + i * 8;
if (capacity == 8) out.lds[7] = 88;
if (which == 8) out.lds[2] = 14;
if (which == 15) out.lds.fill(0);
}
}
test.expected = test.initial;
for (u32 lane = 0; lane < wave_size; ++lane) {
test.expected[lane * 32 + 20] = outputs[lane].control;
test.expected[lane * 32 + 21] = outputs[lane].node;
std::copy(outputs[lane].lds.begin(), outputs[lane].lds.end(), test.expected.begin() + lane * 32 + 24);
}
auto& code = test.code;
code.push_back(EncodeVop2(0x1a, 1, InlineU32(7), 0));
code.insert(code.end(), {EncodeMubuf0(0x0d), EncodeMubuf1(8, 0, 1),
EncodeMubuf0(0x0e, 8), EncodeMubuf1(16, 0, 1),
EncodeMubuf0(0x0e, 24), EncodeMubuf1(20, 0, 1),
EncodeMubuf0(0x0c, 40), EncodeMubuf1(2, 0, 1)});
for (u32 i = 0; i < 8; ++i) {
AppendVMovU32(&code, 3, 100 + i * 8);
code.insert(code.end(), {EncodeDs0(0x0d, i * 128), EncodeDs1(0, 3, 2)});
}
AppendVMovU32(&code, 3, 88);
code.insert(code.end(), {EncodeDs0(0x0d, (capacity - 1) * 128), EncodeDs1(0, 3, 2)});
// Lane eight starts with a nested instance on top of its stack.
AppendSMovLiteral(&code, 126, 0x01000100u);
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0x01000100u);
AppendVMovU32(&code, 3, 14);
code.insert(code.end(), {EncodeDs0(0x0d, 256), EncodeDs1(0, 3, 2)});
AppendSMovLiteral(&code, 126, 0x0000ffffu);
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0x0000ffffu);
code.insert(code.end(), {EncodeDs0(0xe1, capacity), EncodeDs1Ex(16, 16, 9, 8)});
code.push_back(EncodeSop1(0x04, 126, 193));
code.push_back(EncodeVop2(0x1a, 3, InlineU32(5), 0));
AppendStoreVgprAtLaneDwordOffset(&code, 8, 3, 20);
AppendStoreVgprAtLaneDwordOffset(&code, 16, 3, 21);
for (u32 i = 0; i < 8; ++i) {
code.insert(code.end(), {EncodeDs0(0x36, i * 128), EncodeDs1(24, 0, 2)});
AppendStoreVgprAtLaneDwordOffset(&code, 24, 3, 24 + i);
}
AppendEnd(&code);
test.compute_info.wave_size = wave_size;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.lds_size_dwords = wave_size * capacity;
test.compute_info.lds_storage = storage;
test.has_compute_info = true;
test.decoded_counts = {{"DS_BVH_STACK_PUSH8_POP1_RTN_B32", 1}};
test.forbidden_spirv = {"OpControlBarrier", "OpMemoryBarrier", "get_bda_pointer"};
return test;
}
TestCase FlatStackApertures(u32 wave_size) { TestCase FlatStackApertures(u32 wave_size) {
TestCase test; TestCase test;
test.name = wave_size == 32 ? "FlatStackApertures32" : "FlatStackApertures64"; test.name = wave_size == 32 ? "FlatStackApertures32" : "FlatStackApertures64";
+82
View File
@@ -1,4 +1,86 @@
void TestTrinityStackInstruction() {
namespace D = ShaderRecompiler::Decoder;
const std::array code{0xdb840008u, 0x01010a09u,
0xdb840010u, 0x02010a09u, EncodeSopp(0x01)};
D::Program decoded;
D::DecodeProgram(code, decoded);
const auto& first = decoded.instructions.front();
Check(first.opcode == D::Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 &&
first.src_count == 3 && first.offset == 8 && first.dst.reg == 1 &&
first.src0.reg == 9 && first.src1.reg == 10 && first.src2.reg == 1,
"Trinity stack operands or capacity were not decoded");
ShaderComputeInputInfo compute{};
compute.lds_size_dwords = 1024;
auto options = MakeCompileOptions(ShaderType::Compute);
options.input_info.compute = &compute;
const auto result = RecompileForTest(code, options);
Check(CountSourceOccurrences(result.ir_dump, " = BvhStack ") == 2 &&
result.program.info.uses_lds && result.program.info.bvh_stack &&
!result.program.info.shared_int64_atomics,
"unused stack results discarded LDS writes or selected 64-bit atomics");
Check(SpirvInstructionOpcodeCount(result.spirv, 54u) == 2 &&
SpirvInstructionOpcodeCount(result.spirv, 246u) == 1,
"Trinity stack calls duplicated the function or unrolled its eight pushes");
CheckSpirvBinaryValidates(result.spirv);
compute.lds_storage = true;
CheckSpirvBinaryValidates(RecompileForTest(code, options).spirv);
options = MakeCompileOptions(ShaderType::Pixel);
CheckSpirvBinaryValidates(RecompileForTest(code, options).spirv);
for (const auto words : {std::array{0xdb860008u, 0x01010a09u},
std::array{0xdb840007u, 0x01010a09u},
std::array{0xdb840020u, 0x01010a09u},
std::array{0xdb840008u, 0x01f90a09u},
std::array{0xdb840008u, 0x09010a09u}}) {
const std::array invalid{words[0], words[1], EncodeSopp(0x01)};
D::DecodeProgram(invalid, decoded);
Check(decoded.instructions[0].opcode == D::Opcode::UNSUPPORTED,
"unsupported stack encoding was silently accepted");
}
}
void TestTrinityBvhInstruction() {
namespace D = ShaderRecompiler::Decoder;
const std::array code{0xf1949f05u, 0x00010000u, 0x0a030201u, 0x0000040du,
EncodeMubuf0(0x1e, 0, false), EncodeMubuf1(0, 0, 0),
EncodeMubuf0(0x1e, 16, false), EncodeMubuf1(4, 0, 0),
EncodeMubuf0(0x1d, 32, false), EncodeMubuf1(8, 0, 0),
EncodeMubuf0(0x1e, 40, false), EncodeMubuf1(10, 0, 0),
EncodeMubuf0(0x1d, 56, false), EncodeMubuf1(14, 0, 0), EncodeSopp(0x01)};
D::Program decoded;
D::DecodeProgram(code, decoded);
const auto& inst = decoded.instructions.front();
Check(inst.opcode == D::Opcode::IMAGE_BVH8_INTERSECT_RAY && inst.word_count == 4 &&
inst.data_dwords == 10 && inst.image_address_components == 11 &&
inst.image_nsa_addr[3] == 10 && inst.image_nsa_addr[5] == 12 &&
inst.image_nsa_addr[6] == 13 && inst.image_nsa_addr[8] == 15 &&
inst.image_nsa_addr[9] == 4 && inst.src1.reg == 4,
"Trinity BVH8 grouped ray registers were not decoded");
const auto result = RecompileForTest(code, MakeCompileOptions(ShaderType::Compute));
Check(result.program.info.bvh8 && !result.program.info.bvh &&
result.program.info.uses_dma && result.program.info.images.empty(),
"Trinity BVH8 did not use its separate raw-memory helper");
Check(CountSourceOccurrences(result.ir_dump, " = Bvh8Intersect ") == 1,
"Trinity BVH8 result and ray writes duplicated the intersection");
CheckSpirvBinaryValidates(result.spirv);
Check(SpirvInstructionOpcodeCount(result.spirv, 54u) == 6 &&
SpirvInstructionOpcodeCount(result.spirv, 246u) == 1,
"Trinity BVH8 duplicated helpers or unrolled its four-child loop");
CheckSpirvBinaryValidates(RecompileForTest(code, MakeCompileOptions(ShaderType::Pixel)).spirv);
for (const auto words : {std::array{0xf1949f05u, 0x40010000u, 0x0a030201u, 0x0000040du},
std::array{0xf1949f05u, 0x0001f700u, 0x0a030201u, 0x0000040du},
std::array{0xf1949f05u, 0x00010000u, 0xfe030201u, 0x0000040du},
std::array{0xf1949f05u, 0x00010700u, 0x0a030201u, 0x0000040du},
std::array{0xf1949f05u, 0x00010000u, 0x0a030201u, 0x0000040bu}}) {
const std::array invalid{words[0], words[1], words[2], words[3], EncodeSopp(0x01)};
D::DecodeProgram(invalid, decoded);
Check(decoded.instructions[0].opcode == D::Opcode::UNSUPPORTED,
"Trinity BVH8 accepted truncated ray or destination registers");
}
}
void TestRayTracingInstructions() { void TestRayTracingInstructions() {
TestTrinityBvhInstruction();
TestTrinityStackInstruction();
namespace D = ShaderRecompiler::Decoder; namespace D = ShaderRecompiler::Decoder;
const std::array code{ const std::array code{
0xf1989f01u, 0x00081014u, 0xf1989f01u, 0x00081014u,
+35 -3
View File
@@ -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"
@@ -2185,9 +2186,9 @@ public:
VkMemoryPropertyFlags properties{}; VkMemoryPropertyFlags properties{};
vmaGetAllocationMemoryProperties(graphics.allocator, image.allocation, vmaGetAllocationMemoryProperties(graphics.allocator, image.allocation,
&properties); &properties);
Require(name, "allocation policy", Require(name, "small allocation preference",
(properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0, (properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0,
"native image lost its device-local memory requirement"); "native image did not prefer available device-local memory");
// A reused presentation image may still describe its previous recording. // A reused presentation image may still describe its previous recording.
// No commands use this fixture; seed that bookkeeping before retirement. // No commands use this fixture; seed that bookkeeping before retirement.
@@ -20888,6 +20889,14 @@ public:
std::swap(channels[0], channels[2]); std::swap(channels[0], channels[2]);
std::memcpy(dst->data() + dst_offset, channels.data(), std::memcpy(dst->data() + dst_offset, channels.data(),
sizeof(channels)); sizeof(channels));
} else if (info.GetColorTransform() == ColorTransform::Rotate2_10_10_10) {
uint32_t packed = 0;
std::memcpy(&packed, src.data() + src_offset, sizeof(packed));
// The guest format stores the two-bit channel low; the host format stores it high.
const uint32_t alpha = to_tiled ? packed >> 30u : packed & 3u;
const uint32_t color = to_tiled ? packed & 0x3fffffffu : packed >> 2u;
packed = to_tiled ? (color << 2u) | alpha : color | (alpha << 30u);
std::memcpy(dst->data() + dst_offset, &packed, sizeof(packed));
} else { } else {
std::memcpy(dst->data() + dst_offset, src.data() + src_offset, std::memcpy(dst->data() + dst_offset, src.data() + src_offset,
block.bytes_per_element); block.bytes_per_element);
@@ -21281,6 +21290,8 @@ public:
convert_reference(false, &cpu, tiled, layout); convert_reference(false, &cpu, tiled, layout);
gpu_detile(tiled, &gpu, total.size, total.size, layout); gpu_detile(tiled, &gpu, total.size, total.size, layout);
compare("format bytes", cpu, gpu); compare("format bytes", cpu, gpu);
if (layout.GetColorTransform() == ColorTransform::Rotate2_10_10_10)
check_round_trip("low-alpha packed format", total.size, layout);
++format_cases; ++format_cases;
} }
} }
@@ -21825,6 +21836,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;
@@ -46794,6 +46807,11 @@ std::vector<TestCase> MakeCases() {
AddCase(ScratchIsPrivatePerInvocation); AddCase(ScratchIsPrivatePerInvocation);
cases.push_back(FlatStackApertures(32)); cases.push_back(FlatStackApertures(32));
cases.push_back(FlatStackApertures(64)); cases.push_back(FlatStackApertures(64));
for (auto& test : TrinityBvhCases()) cases.push_back(std::move(test));
for (const u32 wave : {32u, 64u}) {
for (const u32 capacity : {8u, 16u, 31u}) cases.push_back(TrinityStack(wave, capacity));
cases.push_back(TrinityStack(wave, 8, true));
}
cases.push_back(BvhIntersections(true, true, 1)); cases.push_back(BvhIntersections(true, true, 1));
cases.push_back(BvhIntersections(true, true, 0, true)); cases.push_back(BvhIntersections(true, true, 0, true));
for (bool barycentrics : {false, true}) { for (bool barycentrics : {false, true}) {
@@ -51364,13 +51382,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");
} }
@@ -53330,6 +53351,11 @@ int main(int argc, char **argv) {
#endif #endif
if (argc == 2 && std::strcmp(argv[1], "--ray-tracing-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--ray-tracing-only") == 0) {
VulkanHarness vulkan; VulkanHarness vulkan;
for (const auto& test : TrinityBvhCases()) RunCase(&vulkan, test);
for (const u32 wave : {32u, 64u}) {
for (const u32 capacity : {8u, 16u, 31u}) RunCase(&vulkan, TrinityStack(wave, capacity));
RunCase(&vulkan, TrinityStack(wave, 8, true));
}
RunCase(&vulkan, FlatStackApertures(32)); RunCase(&vulkan, FlatStackApertures(32));
RunCase(&vulkan, FlatStackApertures(64)); RunCase(&vulkan, FlatStackApertures(64));
RunCase(&vulkan, BvhIntersections(true, true, 1)); RunCase(&vulkan, BvhIntersections(true, true, 1));
@@ -53835,6 +53861,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());
+129
View File
@@ -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();
}
+5
View File
@@ -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);