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
nmzik 5e837a9bea shader: share formatted indirect buffer load emission 2026-10-29 05:52:04 +01:00
nmzikandFrenzied a0b99a8c21 ngs2: accept larger custom voice state buffers
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
nmzikandFrenzied 8cfe1c4d34 ngs2: apply custom channel matrices before mastering output
Co-authored-by: Frenzied <frenziedsaint@gmail.com>
2026-10-29 05:52:04 +01:00
Çağatay Güçlü f71da54b9b shader: support fractional-even tessellation spacing 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 63207739bc shader: initialize local vertex count from active lanes 2026-10-29 05:52:04 +01:00
Çağatay Güçlü 143f640706 shader: preserve counterclockwise tessellation output 2026-10-29 05:52:04 +01:00
nmzik 54b937c85f shader: support high products of 24-bit integers 2026-10-28 22:49:21 +01:00
nmzik 22610cb93e shader: support unsigned 16-bit multiply-add 2026-10-28 22:41:29 +01:00
nmzik 0386f33bf3 shader: support four 16-bit integer ternary operations 2026-10-28 22:37:24 +01:00
nmzik 138d5b86c4 shader: support S_QUADMASK_B32 2026-10-28 22:31:16 +01:00
nmzik 963aaeb3b6 gpu: preserve low-alpha 2/10/10/10 render-target storage 2026-10-28 22:29:50 +01:00
Çağatay Güçlü 77458260f1 shader: reflect masked hull control-point addresses 2026-10-28 22:19:09 +01:00
nmzik 41de2fd64e shader: support five VOP3 float conversion forms 2026-10-28 22:19:02 +01:00
Çağatay Güçlü 128c938514 shader: support quad tessellation domains and factor routing 2026-10-28 22:09:39 +01:00
Chai T a8e3c29b2e shader: implement V_ASHRREV_I64 (VOP3 0x301) 2026-10-28 21:51:28 +01:00
Devran Cosmo Uenal c528324d27 audio: let the AJM Opus decoder write part of a packet (#1914)
ajm: buffer and resample Opus output
2026-10-28 21:31:16 +01:00
Mr. Big BritchesandClaude Opus 5.5 c67352d5d6 ci: build and run the NGS2 sampler tests (#1957)
ngs2_sampler_tests is registered with ctest but no job built it or listed
it in the test filter, so the NGS2 unit tests never ran in CI. Each
platform now builds the target and includes ngs2_sampler in its ctest
filter.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:58:06 +01:00
Nawid3333 6c01401862 graphics: accept video-out buffers with 128-byte independent DCC blocks (Gran Turismo 7) (#1945)
graphics: accept video-out buffers with 128-byte independent DCC blocks
2026-10-28 04:44:09 +01:00
84 changed files with 5346 additions and 783 deletions

No files matched your search

+6 -6
View File
@@ -113,7 +113,7 @@ jobs:
shell: cmd
run: |
cmake --build _Build/windows --target launcher ^
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
shader_recompiler_compute_tests ^
resource_tracking_tests scalar_provenance_tests ^
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests --parallel
@@ -121,7 +121,7 @@ jobs:
- name: Test
shell: cmd
run: |
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$"
- name: Install
shell: cmd
@@ -202,7 +202,7 @@ jobs:
shell: bash
run: |
cmake --build _Build/macos \
--target launcher audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
--target launcher audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
memory_tracker_tests page_manager_tests shader_recompiler_compute_tests \
resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
@@ -212,7 +212,7 @@ jobs:
shell: bash
run: |
ctest --test-dir _Build/macos --output-on-failure \
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
-R '^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
- name: Install
shell: bash
@@ -388,7 +388,7 @@ jobs:
run: |
cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \
audio_out2_port_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
audio_out2_port_tests ngs2_sampler_tests pad_haptics_tests controller_settings_tests host_input_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests archive_file_tests avplayer_file_tests \
--parallel
@@ -397,7 +397,7 @@ jobs:
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(audio_out2_port|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
-R '^(audio_out2_port|ngs2_sampler|pad_haptics|controller_settings|host_input|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|archive_file|avplayer_file|virtual_memory_allocation)$'
- name: Install
shell: bash
+37
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_video_out_overlay gpu_video_out_overlay.frag)
add_gpu_blit_shader(gpu_imgui_hdr gpu_video_out_overlay.frag -DIMGUI_HDR=1)
add_gpu_blit_shader(gpu_pssr_linear gpu_pssr_linear.comp)
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
list(APPEND kyty_emulator_src
@@ -522,6 +523,13 @@ add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
add_executable(ajm_resample_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AjmResampleTests.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(ajm_resample_tests common fmt::fmt FFmpeg::ffmpeg LibAtrac9 opus)
target_include_directories(ajm_resample_tests PRIVATE ${inc_headers})
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
)
@@ -624,6 +632,19 @@ target_sources(shader_recompiler_compute_tests PRIVATE
"${gpu_test_ms_depth_header}")
add_kyty_full_emulator_test(virtual_memory_allocation_tests "${KYTY_TESTS_DIR}/VirtualMemoryAllocationTests.cpp")
add_library(kyty_test_gtest STATIC EXCLUDE_FROM_ALL
"${KYTY_THIRD_PARTY_DIR}/fmt/test/gtest/gmock-gtest-all.cc")
target_include_directories(kyty_test_gtest PUBLIC "${KYTY_THIRD_PARTY_DIR}/fmt/test/gtest")
target_sources(virtual_memory_allocation_tests PRIVATE "${KYTY_TESTS_DIR}/TrinityTests.cpp")
target_link_libraries(virtual_memory_allocation_tests kyty_test_gtest)
add_executable(psml_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/PsmlTests.cpp"
"${KYTY_SOURCE_DIR}/libs/libPsml.cpp"
"${KYTY_SOURCE_DIR}/loader/symbolDatabase.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp")
target_link_libraries(psml_tests common kyty_test_gtest)
target_include_directories(psml_tests PRIVATE ${inc_headers})
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
@@ -668,6 +689,7 @@ if(BUILD_TESTING)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME audio_out_timing COMMAND $<TARGET_FILE:audio_out_timing_tests>)
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
add_test(NAME ajm_resample COMMAND $<TARGET_FILE:ajm_resample_tests>)
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
add_test(NAME controller_settings COMMAND $<TARGET_FILE:controller_settings_tests>)
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
@@ -684,6 +706,16 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --tessellation-only)
add_test(NAME shader_recompiler_float_min_max
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --float-min-max-only)
add_test(NAME shader_recompiler_vop3_conversions
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --vop3-conversions-only)
add_test(NAME shader_recompiler_quadmask
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --quadmask-only)
add_test(NAME shader_recompiler_integer16_ternary
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-only)
add_test(NAME shader_recompiler_mad_u16
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mad-u16-only)
add_test(NAME shader_recompiler_mul_hi24
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mul-hi24-only)
add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME graphics_pipeline_rasterization
@@ -692,6 +724,10 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --draw-offset-only)
add_test(NAME virtual_memory_allocation
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
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
add_test(NAME command_scheduler_timeline
@@ -762,6 +798,7 @@ if(BUILD_TESTING)
audio_out2_port_tests
audio_out_timing_tests
ngs2_sampler_tests
ajm_resample_tests
pad_haptics_tests
controller_settings_tests
host_input_tests
+8
View File
@@ -113,6 +113,10 @@ bool VrEnabled() {
return g_config->vr_enabled;
}
bool Ps5ProEnabled() {
return g_config->pro_enabled;
}
bool AmdCpuEnabled() {
return g_config->amd_cpu_enabled;
}
@@ -133,6 +137,10 @@ bool IsNetworkConnected() {
return g_config->network_connected;
}
bool PsnEmulationEnabled() {
return g_config->psn_emulation_enabled;
}
bool ShaderValidationEnabled() {
return g_config->shader_validation_enabled;
}
+4
View File
@@ -67,8 +67,10 @@ struct ConfigOptions {
bool stretch_enabled = false;
bool splash_enabled = true;
bool vr_enabled = false;
bool pro_enabled = false;
bool amd_cpu_enabled = false;
bool network_connected = false;
bool psn_emulation_enabled = false;
uint32_t vblank_frequency = 60;
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
@@ -128,8 +130,10 @@ bool HdrEnabled();
bool HideCursorEnabled();
bool StretchEnabled();
bool VrEnabled();
bool Ps5ProEnabled();
bool AmdCpuEnabled();
bool IsNetworkConnected();
bool PsnEmulationEnabled();
uint32_t GetVblankFrequency();
uint32_t GetConsoleLanguage();
bool VulkanValidationEnabled();
@@ -3,6 +3,7 @@
#include "common/assert.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -92,6 +93,7 @@ public:
uint32_t mode);
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
void ProcessPssr(const Pssr::Request& request);
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
void WaitForRewind(bool valid);
@@ -226,6 +226,7 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
g_cp_op_custom_func[Pm4::R_PSSR] = CpOpPssr;
return g_cp_op_custom_func;
}
@@ -151,6 +151,7 @@ uint32_t CpOpPushMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
uint32_t CpOpPopMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpWaitRegMem64(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpFlip(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPssr(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
} // namespace Libs::Graphics
@@ -1706,6 +1706,17 @@ KYTY_CP_OP_PARSER(CpOpFlip) {
return 5;
}
KYTY_CP_OP_PARSER(CpOpPssr) {
KYTY_PROFILER_FUNCTION();
const auto packet_dw = KYTY_PM4_LEN(cmd_id) - 1;
EXIT_NOT_IMPLEMENTED(packet_dw < Pssr::RequestDwords || packet_dw > dw);
Pssr::Request request {};
std::memcpy(&request, buffer, sizeof(request));
cp.ProcessPssr(request);
return packet_dw;
}
KYTY_CP_OP_PARSER(CpOpIndexType) {
KYTY_PROFILER_FUNCTION();
+1
View File
@@ -46,6 +46,7 @@ constexpr FormatInfo kFormatInfo[] = {
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_11_11Float, 4, 0, 4, true, false},
{BufferFormat::k2_10_10_10UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
+5
View File
@@ -929,6 +929,11 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
}
}
void CommandProcessor::ProcessPssr(const Pssr::Request& request) {
EXIT_NOT_IMPLEMENTED(IsAsyncComputeQueue());
m_renderer.GetRenderExecutor().ProcessPssr(CurrentBuffer(), request);
}
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
EXIT_NOT_IMPLEMENTED(args_addr == 0 || (args_addr & 3u) != 0);
if ((mode & Pm4::COMPUTE_DISPATCH_INITIATOR_USE_THREAD_DIMENSIONS) != 0) {
+1
View File
@@ -81,6 +81,7 @@ constexpr uint32_t R_WRITE_DATA = 0x15;
constexpr uint32_t R_FLIP = 0x17;
constexpr uint32_t R_DMA_DATA = 0x19;
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
constexpr uint32_t R_PSSR = 0x1B;
constexpr uint32_t R_NUM = 0x3F + 1;
+20
View File
@@ -0,0 +1,20 @@
#ifndef EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
#define EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
#include <cstdint>
namespace Libs::Graphics::Pssr {
struct Request {
uint32_t output[8];
uint32_t input[8];
uint32_t width;
uint32_t height;
};
constexpr uint32_t RequestDwords = sizeof(Request) / sizeof(uint32_t);
static_assert(sizeof(Request) == 72);
} // namespace Libs::Graphics::Pssr
#endif // EMULATOR_SRC_GRAPHICS_GUEST_GPU_PSSR_H_
@@ -17,9 +17,15 @@
namespace Libs::Graphics {
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11 };
enum class ColorTransform : uint32_t { None, SwapBgra16, Reverse10_11_11, Rotate2_10_10_10 };
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
enum class VideoOutCompression : uint8_t {
Uncompressed,
Dcc256_256_0,
Dcc256_64_64,
Dcc128_128_128,
Unsupported
};
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask };
@@ -99,6 +105,8 @@ struct ImageInfo {
switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float:
return ColorTransform::Reverse10_11_11;
case Prospero::BufferFormat::k2_10_10_10UNorm:
return ColorTransform::Rotate2_10_10_10;
default: return ColorTransform::None;
}
}
@@ -287,8 +295,9 @@ inline bool ImageInfo::IsDepth() const noexcept {
[[nodiscard]] inline VideoOutCompression
ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t dcc_control,
uint64_t dcc_clear_color) noexcept {
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
constexpr uint32_t DCC_256_256_0 = 0x00000048u;
constexpr uint32_t DCC_256_64_64 = 0x00000208u;
constexpr uint32_t DCC_128_128_128 = 0x00100024u;
if (!compressed) {
return metadata_address == 0 && dcc_control == 0 && dcc_clear_color == 0
? VideoOutCompression::Uncompressed
@@ -300,6 +309,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
switch (dcc_control) {
case DCC_256_256_0: return VideoOutCompression::Dcc256_256_0;
case DCC_256_64_64: return VideoOutCompression::Dcc256_64_64;
case DCC_128_128_128: return VideoOutCompression::Dcc128_128_128;
default: return VideoOutCompression::Unsupported;
}
}
@@ -32,8 +32,12 @@ struct HostFormatInfo {
HostFormatInfo ResolveHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
if (guest_format == Prospero::BufferFormat::k10_11_11Float) {
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
switch (guest_format) {
case Prospero::BufferFormat::k10_11_11Float:
return {vk::Format::eB10G11R11UfloatPack32, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k2_10_10_10UNorm:
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingAbgr};
default: break;
}
if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) {
@@ -104,7 +104,8 @@ TileManager::Tiling TileManager::Prepare(bool tile, uint64_t tiled_capacity,
block.block_depth == 0 || block.block_size == 0 || info.tiling.texel_width == 0 ||
info.tiling.texel_height == 0 || info.tiled_slice_stride == 0);
EXIT_IF((transform == ColorTransform::SwapBgra16 && block.bytes_per_element != 8u) ||
(transform == ColorTransform::Reverse10_11_11 && block.bytes_per_element != 4u));
((transform == ColorTransform::Reverse10_11_11 ||
transform == ColorTransform::Rotate2_10_10_10) && block.bytes_per_element != 4u));
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
};
@@ -560,7 +561,8 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
uint32_t element_bytes = 0;
switch (transform) {
case ColorTransform::SwapBgra16: element_bytes = 8; break;
case ColorTransform::Reverse10_11_11: element_bytes = 4; break;
case ColorTransform::Reverse10_11_11:
case ColorTransform::Rotate2_10_10_10: element_bytes = 4; break;
default: EXIT("invalid color transform\n");
}
EXIT_IF(input.size == 0 || input.size % element_bytes != 0 ||
+9
View File
@@ -150,15 +150,21 @@ private:
friend class CommandScheduler;
};
namespace Pssr {
struct Request;
}
class RenderExecutor {
public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
~RenderExecutor();
KYTY_CLASS_NO_COPY(RenderExecutor);
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode, bool async_compute);
void DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer, uint64_t args_addr,
uint32_t mode, bool async_compute);
void ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request);
void PrepareBindings(const ShaderStageRuntime& runtime, PreparedBindings& prepared);
void FindBuffers(std::span<PreparedBindings* const> stages);
@@ -219,6 +225,9 @@ private:
std::vector<ImageId> m_bound_images;
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
vk::DescriptorSetLayout m_pssr_descriptor_layout = nullptr;
vk::PipelineLayout m_pssr_pipeline_layout = nullptr;
vk::Pipeline m_pssr_pipeline = nullptr;
friend class CommandProcessor;
friend struct RenderExecutorTestAccess;
@@ -0,0 +1,184 @@
#include "common/assert.h"
#include "common/threads.h"
#include "gpu_blit_shaders/gpu_pssr_linear_spv.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/shaderBindings.h"
#include <algorithm>
#include <array>
namespace Libs::Graphics {
namespace {
struct PssrParameters {
uint32_t input_size[2];
uint32_t output_size[2];
uint32_t output_channels[4];
};
bool IsInputFormat(Prospero::BufferFormat format) {
return format == Prospero::BufferFormat::k11_11_10Float ||
format == Prospero::BufferFormat::k10_11_11Float ||
format == Prospero::BufferFormat::k16_16_16_16Float;
}
} // namespace
RenderExecutor::~RenderExecutor() {
const auto device = m_context.GetGraphics().device;
device.destroyPipeline(m_pssr_pipeline, nullptr);
device.destroyPipelineLayout(m_pssr_pipeline_layout, nullptr);
device.destroyDescriptorSetLayout(m_pssr_descriptor_layout, nullptr);
}
void RenderExecutor::ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request) {
EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
Common::LockGuard lock(m_context.GetMutex());
ShaderTextureResource input_descriptor {}, output_descriptor {};
std::copy_n(request.input, 8, input_descriptor.fields);
std::copy_n(request.output, 8, output_descriptor.fields);
const auto is_2d = [](const ShaderTextureResource& descriptor) {
return !descriptor.IsNull() &&
(descriptor.Type() == Prospero::ImageType::kColor2D ||
descriptor.Type() == Prospero::ImageType::kColor2DArray) &&
descriptor.TileMode() != Prospero::TileMode::kDepth;
};
EXIT_IF(!is_2d(input_descriptor) || !is_2d(output_descriptor) ||
!IsInputFormat(input_descriptor.Format()) ||
output_descriptor.Format() != Prospero::BufferFormat::k11_11_10Float ||
output_descriptor.TileMode() != Prospero::TileMode::kStandard256B ||
output_descriptor.BaseLevel() != 0 || output_descriptor.LastLevel() != 0 ||
output_descriptor.MaxMip() != 0 || output_descriptor.BaseArray5() != 0 ||
output_descriptor.Depth() != 0 || request.width == 0 || request.height == 0);
const auto input_width = std::max(1u, (input_descriptor.Width5() + 1u) >>
input_descriptor.BaseLevel());
const auto input_height = std::max(1u, (input_descriptor.Height5() + 1u) >>
input_descriptor.BaseLevel());
EXIT_IF(request.width > input_width || request.height > input_height);
PssrParameters parameters {{request.width, request.height},
{output_descriptor.Width5() + 1u, output_descriptor.Height5() + 1u},
{4, 4, 4, 4}};
for (uint32_t component = 0; component < 3; ++component) {
for (uint32_t source = 0; source < 4; ++source) {
if (GetDstSel(output_descriptor.DstSelXYZW(), source) == 4u + component) {
parameters.output_channels[component] = source;
break;
}
}
}
const auto resolve = [&](const uint32_t* words, bool output) {
ShaderRecompiler::IR::ImageResource resource {};
resource.resource_class = output ? ShaderRecompiler::IR::ImageResourceClass::Storage
: ShaderRecompiler::IR::ImageResourceClass::Sampled;
resource.numeric_class = Prospero::TextureNumericClass::Float;
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
resource.read = !output;
resource.written = output;
ShaderRecompiler::IR::DescriptorValue value {};
std::copy_n(words, 8, value.dwords.begin());
value.dword_count = 8;
auto binding = ResolveTexture(resource, value);
binding.desc.view_info.level_count = 1;
binding.desc.view_info.min_lod = 0;
return binding;
};
auto& cache = m_context.GetTextureCache();
auto input = resolve(request.input, false);
BindImage(input.image_id, false);
auto output = resolve(request.output, true);
BindImage(output.image_id, true);
EXIT_IF(input.desc.info.data.address < output.desc.info.data.End() &&
output.desc.info.data.address < input.desc.info.data.End());
const auto acquire = [&](TextureBinding& binding, const uint32_t* words, bool storage) {
auto& previous = cache.GetImage(binding.image_id);
if (!previous.registered || previous.binding.needs_rebind) {
previous.binding = {};
binding = resolve(words, storage);
BindImage(binding.image_id, storage);
}
binding.image_view = cache.FindTexture(binding.image_id, binding.desc);
};
acquire(input, request.input, false);
acquire(output, request.output, true);
auto& source = cache.GetImage(input.image_id);
auto& destination = cache.GetImage(output.image_id);
const auto& source_view = input.desc.view_info;
const auto& destination_view = output.desc.view_info;
EXIT_IF(source.info.IsDepth() || destination.info.IsDepth() ||
source.backing.samples != 1 || destination.backing.samples != 1 ||
(input.image_id == output.image_id &&
source_view.base_level == destination_view.base_level &&
source_view.base_layer == destination_view.base_layer));
auto& graphics = m_context.GetGraphics();
if (m_pssr_pipeline == nullptr) {
const std::array bindings {
vk::DescriptorSetLayoutBinding {0, vk::DescriptorType::eSampledImage, 1,
vk::ShaderStageFlagBits::eCompute},
vk::DescriptorSetLayoutBinding {1, vk::DescriptorType::eStorageImage, 1,
vk::ShaderStageFlagBits::eCompute}};
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
descriptor_info.bindingCount = bindings.size();
descriptor_info.pBindings = bindings.data();
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(
&descriptor_info, nullptr, &m_pssr_descriptor_layout),
"create upscaling descriptor layout");
const vk::PushConstantRange push {vk::ShaderStageFlagBits::eCompute, 0,
sizeof(PssrParameters)};
vk::PipelineLayoutCreateInfo layout_info {};
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &m_pssr_descriptor_layout;
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = &push;
RequireVulkanSuccess(graphics.device.createPipelineLayout(
&layout_info, nullptr, &m_pssr_pipeline_layout),
"create upscaling pipeline layout");
const auto shader = CompileSPV(GPU_PSSR_LINEAR_SPV, graphics.device);
vk::ComputePipelineCreateInfo create {};
create.stage.stage = vk::ShaderStageFlagBits::eCompute;
create.stage.module = shader;
create.stage.pName = "main";
create.layout = m_pssr_pipeline_layout;
const auto result = graphics.device.createComputePipelines(
nullptr, 1, &create, nullptr, &m_pssr_pipeline);
graphics.device.destroyShaderModule(shader, nullptr);
RequireVulkanSuccess(result, "create upscaling pipeline");
}
buffer.EndRendering();
const auto command = buffer.Handle();
source.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead,
ImageSubresourceRange {source_view.base_level, 1, source_view.base_layer, 1},
command);
destination.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite,
ImageSubresourceRange {destination_view.base_level, 1,
destination_view.base_layer, 1}, command);
const std::array images {
vk::DescriptorImageInfo {nullptr, input.image_view, vk::ImageLayout::eGeneral},
vk::DescriptorImageInfo {nullptr, output.image_view, vk::ImageLayout::eGeneral}};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t i = 0; i < writes.size(); ++i) {
writes[i].dstBinding = i;
writes[i].descriptorCount = 1;
writes[i].descriptorType = i == 0 ? vk::DescriptorType::eSampledImage
: vk::DescriptorType::eStorageImage;
writes[i].pImageInfo = &images[i];
}
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pssr_pipeline_layout,
0, writes.size(), writes.data());
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_pssr_pipeline);
command.pushConstants(m_pssr_pipeline_layout, vk::ShaderStageFlagBits::eCompute,
0, sizeof(parameters), &parameters);
command.dispatch((parameters.output_size[0] + 7) / 8,
(parameters.output_size[1] + 7) / 8, 1);
ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader);
ResetBindings();
}
} // namespace Libs::Graphics
@@ -0,0 +1,33 @@
#version 450 core
#extension GL_EXT_samplerless_texture_functions : require
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform texture2D source;
layout(binding = 1) writeonly uniform image2D destination;
layout(push_constant) uniform Parameters {
uvec2 input_size;
uvec2 output_size;
uvec4 output_channels;
} parameters;
vec4 fetch_color(ivec2 coordinate) {
return texelFetch(source, clamp(coordinate, ivec2(0), ivec2(parameters.input_size) - 1), 0);
}
void main() {
uvec2 output_coordinate = gl_GlobalInvocationID.xy;
if (any(greaterThanEqual(output_coordinate, parameters.output_size))) return;
vec2 input_coordinate = (vec2(output_coordinate) + 0.5) *
vec2(parameters.input_size) / vec2(parameters.output_size) - 0.5;
ivec2 lower = ivec2(floor(input_coordinate));
vec2 weight = fract(input_coordinate);
vec4 color = mix(mix(fetch_color(lower), fetch_color(lower + ivec2(1, 0)), weight.x),
mix(fetch_color(lower + ivec2(0, 1)), fetch_color(lower + ivec2(1, 1)), weight.x),
weight.y);
vec4 stored = vec4(0.0);
for (uint channel = 0; channel < 3; ++channel) {
uint selected = parameters.output_channels[channel];
if (selected < 4) stored[channel] = color[selected];
}
imageStore(destination, ivec2(output_coordinate), stored);
}
@@ -1,4 +1,4 @@
// ColorTransform values match imageInfo.h. Preserve the floating-point bit fields exactly.
// ColorTransform values match imageInfo.h. Preserve the component bit fields exactly.
uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
switch (transform) {
case 1u:
@@ -15,6 +15,13 @@ uvec2 transform_color(uvec2 value, uint transform, bool to_host) {
}
break;
}
case 3u:
if (to_host) {
value.x = (value.x >> 2u) | (value.x << 30u);
} else {
value.x = (value.x << 2u) | (value.x >> 30u);
}
break;
}
return value;
}
+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);
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;
const auto result = static_cast<vk::Result>(
@@ -156,11 +156,14 @@ void ReflectTessellationLayout(const Decoder::Program& program, bool local,
if (rhs.constant == 0u && third.constant == 8u) value = constant(0u);
}
break;
case Opcode::V_AND_B32:
if ((lhs.kind == Kind::PackedControlPoint && rhs == constant(0xffu)) ||
(rhs.kind == Kind::PackedControlPoint && lhs == constant(0xffu)))
value = constant(0u);
case Opcode::V_AND_B32: {
const auto mask = lhs.kind == Kind::PackedControlPoint ? rhs
: rhs.kind == Kind::PackedControlPoint ? lhs
: Address {};
if (mask == constant(0xffu)) value = constant(0u);
if (mask == constant(0x1f00u)) value = {Kind::Affine, 256u, 0u};
break;
}
case Opcode::V_ADD_NC_U32: value = add(lhs, rhs); break;
case Opcode::V_MUL_LO_U32: value = multiply(lhs, rhs); break;
case Opcode::V_MUL_U32_U24: value = multiply(low24(lhs), low24(rhs)); break;
@@ -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,
Vec3 origin, Vec3 direction) {
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 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).
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))));
}
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 z_largest = cmp(spv::OpFOrdLessThan,
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);
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 index = Binary(s, spv::OpBitwiseAnd, u,
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 {
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)),
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))) };
return Vector(s, TypeU32Vector(s, 4), result);
}
namespace {
std::array<uint32_t, 6> Box(EmitterState& s, uint32_t first, uint32_t second,
uint32_t half, uint32_t child) {
const auto f = TypeF32(s);
@@ -338,6 +338,9 @@ inline constexpr auto EmitDataConsume = EmitAppendConsume;
uint32_t EmitSwizzleU32(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 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 EmitImageQueryLod = EmitImage;
inline constexpr auto EmitImageRead = EmitImage;
@@ -118,8 +118,11 @@ struct EmitterState {
uint32_t bda_pointer_function = 0;
uint32_t indirect_format_function = 0;
uint32_t indirect_format_geometry_function = 0;
std::map<std::array<uint32_t, 3>, 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 bvh8_result_type = 0;
uint32_t bvh8_intersect_function = 0;
uint32_t bvh_stack_function = 0;
uint32_t gds_variable = 0;
uint32_t gds_length = 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 TypeU64(EmitterState& state);
uint32_t TypeU32Pair(EmitterState& state);
uint32_t TypeBvh8Result(EmitterState& state);
uint32_t TypeI32(EmitterState& state);
uint32_t TypeI32Pair(EmitterState& state);
uint32_t TypeF32(EmitterState& state);
@@ -540,9 +544,13 @@ uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
void EmitProgram(EmitterState& state);
void DefineGetBdaPointer(EmitterState& state);
void DefineIndirectBufferFormats(ValueEmitContext& ctx);
void DefineIndirectBufferLoads(ValueEmitContext& ctx);
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);
// These templates accept local lambdas from several emitter translation units.
@@ -1026,53 +1026,50 @@ uint32_t LoadIndirectBuffer(ValueEmitContext& ctx, const IndirectBufferAccess& b
return ConstructU32Composite(state, components, values);
}
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IndirectBufferAccess& buffer) {
auto& state = ctx.state;
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto buffer = PrepareIndirectBuffer(state, IndirectBufferArgs(ctx, inst), true);
const auto geometry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
state.indirect_format_geometry_function, buffer.format);
const auto field = [&](uint32_t index) {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
return value;
};
const auto bytes = field(0);
const auto component = Binary(
state, spv::OpUMod, TypeU32(state),
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
Unary(state, spv::OpNot, TypeU64(state), alignment));
const auto address = Binary(
state, spv::OpIAdd, TypeU64(state), base,
Unary(state, spv::OpUConvert, TypeU64(state),
Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
const auto memory = AndCondition(
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
ConstantU32(state, 4)));
const auto raw = EmitValueOrZeroIfCondition(
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
[&]() {
// Format alignment and component spacing keep every selected component within one DWORD.
const auto word = LoadBdaDword(
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
ConstantU64(state, ~uint64_t {3})));
const auto byte = Binary(
state, spv::OpBitwiseAnd, TypeU32(state),
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
ConstantU32(state, 3)));
});
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
state.indirect_format_function, buffer.format, buffer.selector,
component, raw);
return result;
});
const auto geometry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, 4), geometry,
state.indirect_format_geometry_function, buffer.format);
const auto field = [&](uint32_t index) {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, geometry, index);
return value;
};
const auto bytes = field(0);
const auto component = Binary(
state, spv::OpUMod, TypeU32(state),
Binary(state, spv::OpISub, TypeU32(state), buffer.selector, ConstantU32(state, 4)), field(1));
const auto alignment = Unary(state, spv::OpUConvert, TypeU64(state), field(3));
const auto base = Binary(state, spv::OpBitwiseAnd, TypeU64(state), buffer.address,
Unary(state, spv::OpNot, TypeU64(state), alignment));
const auto address = Binary(
state, spv::OpIAdd, TypeU64(state), base,
Unary(state, spv::OpUConvert, TypeU64(state),
Binary(state, spv::OpIMul, TypeU32(state), component, field(2))));
const auto memory = AndCondition(
state, Binary(state, spv::OpINotEqual, TypeBool(state), bytes, ConstantU32(state, 0)),
Binary(state, spv::OpUGreaterThanEqual, TypeBool(state), buffer.selector,
ConstantU32(state, 4)));
const auto raw = EmitValueOrZeroIfCondition(
state, AndCondition(state, memory, IndirectBufferInBounds(state, buffer, 0u, bytes, true)),
[&]() {
// Format alignment and component spacing keep every selected component within one DWORD.
const auto word = LoadBdaDword(
ctx, Binary(state, spv::OpBitwiseAnd, TypeU64(state), address,
ConstantU64(state, ~uint64_t {3})));
const auto byte = Binary(
state, spv::OpBitwiseAnd, TypeU32(state),
Unary(state, spv::OpUConvert, TypeU32(state), address), ConstantU32(state, 3));
return Binary(state, spv::OpShiftRightLogical, TypeU32(state), word,
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), byte,
ConstantU32(state, 3)));
});
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32(state), result,
state.indirect_format_function, buffer.format, buffer.selector,
component, raw);
return result;
}
uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
@@ -1135,15 +1132,12 @@ uint32_t LoadRawBuffer(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
uint32_t LoadBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) {
auto& state = ctx.state;
const auto mem = ctx.Memory(inst);
if (mem.kind == IR::ResourceKind::IndirectBuffer && mem.formatted) {
return LoadIndirectFormattedX(ctx, inst);
}
return EmitValueOrDefaultIfCondition(
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU32Composite(state, components),
ConstantU32CompositeZero(state, components), [&]() {
if (mem.kind == IR::ResourceKind::IndirectBuffer) {
const auto function = state.indirect_buffer_load_functions.at(
{mem.data_bits, components, uint32_t(mem.coherent)});
{mem.data_bits, components, uint32_t(mem.coherent), uint32_t(mem.formatted)});
const auto args = IndirectBufferArgs(ctx, inst);
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU32Composite(state, components),
@@ -1231,62 +1225,8 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
});
}
} // namespace
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer || memory.formatted ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent)}, 0u);
if (!inserted) continue;
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto result = LoadIndirectBuffer(ctx, PrepareIndirectBuffer(state, args, false),
memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
auto& state = ctx.state;
const auto needed = std::ranges::any_of(state.program.blocks, [&](const IR::Block* block) {
return std::ranges::any_of(*block, [&](const IR::Inst& inst) {
if (inst.GetOpcode() != IR::ValueOpcode::LoadBufferU32) return false;
const auto& memory = ctx.Memory(inst);
return memory.kind == IR::ResourceKind::IndirectBuffer && memory.formatted;
});
});
if (!needed) return;
const auto type = TypeU32(state);
constexpr auto format_count =
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float) + 1u;
@@ -1358,6 +1298,57 @@ void DefineIndirectBufferFormats(ValueEmitContext& ctx) {
state.builder.AddFunction(spv::OpFunctionEnd);
}
} // namespace
uint32_t GetBdaPointer(EmitterState& state, uint32_t address) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionCall, TypeU64(state), result,
state.bda_pointer_function, address);
return result;
}
void DefineIndirectBufferLoads(ValueEmitContext& ctx) {
auto& state = ctx.state;
for (const auto* block: state.program.blocks) {
for (const auto& inst: *block) {
const auto components = IR::BufferComponentCount(inst.GetOpcode());
if (components == 0u || inst.GetOpcode() == IR::ValueOpcode::ReadConstBuffer) continue;
const auto& memory = ctx.Memory(inst);
if (memory.kind != IR::ResourceKind::IndirectBuffer ||
!memory.SupportsIndirectBufferLoad(inst.GetOpcode())) continue;
auto [entry, inserted] = state.indirect_buffer_load_functions.try_emplace(
std::array {memory.data_bits, components, uint32_t(memory.coherent),
uint32_t(memory.formatted)}, 0u);
if (!inserted) continue;
if (memory.formatted && state.indirect_format_function == 0u) {
DefineIndirectBufferFormats(ctx);
}
const auto function = entry->second = state.builder.AllocateId();
const auto type = TypeU32(state);
const auto result_type = TypeU32Composite(state, components);
const auto signature = state.builder.Type(
spv::OpTypeFunction, result_type, type, type, type, type, type, type, type, type);
const auto name = "load_indirect_buffer_" + std::to_string(memory.data_bits) + "x" +
std::to_string(components) + (memory.coherent ? "_coherent" : "") +
(memory.formatted ? "_formatted" : "");
state.builder.AddName(function, name.c_str());
state.builder.AddFunction(spv::OpFunction, result_type, function,
spv::FunctionControlMaskNone, signature);
std::array<uint32_t, 8> args;
for (auto& arg: args) {
arg = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFunctionParameter, type, arg);
}
EmitLabel(state, state.builder.AllocateId());
const auto buffer = PrepareIndirectBuffer(state, args, memory.formatted);
const auto result = memory.formatted ? LoadIndirectFormattedX(ctx, buffer)
: LoadIndirectBuffer(ctx, buffer, memory.data_bits, components, memory.coherent);
state.builder.AddFunction(spv::OpReturnValue, result);
state.builder.AddFunction(spv::OpFunctionEnd);
}
}
}
void DefineGetBdaPointer(EmitterState& state) {
if (!state.program.info.uses_dma) {
return;
@@ -414,6 +414,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type) {
case IR::Type::U32:
case IR::Type::F16: return TypeU32(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::F32: return TypeF32(state);
case IR::Type::F64: return TypeF64(state);
@@ -675,8 +676,8 @@ void EmitProgram(EmitterState& state) {
}
DefineGetBdaPointer(state);
DefineIndirectBufferLoads(ctx);
DefineIndirectBufferFormats(ctx);
DefineBvhIntersect(state);
DefineTrinityFunctions(state);
if (program.info.uses_swizzle) {
ctx.scratch_u32_variable = state.builder.AllocateId();
if (state.lane_count == 2) {
@@ -29,12 +29,14 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto pointer = state.builder.AllocateId();
if (kind == Attribute::Factor) {
EXIT_NOT_IMPLEMENTED(!inst.Arg(1).IsImmediate());
const auto index = inst.Arg(1).U32() / 4u;
const auto outer = index < 3u;
EXIT_NOT_IMPLEMENTED(index >= 4u);
const auto index = inst.Arg(1).U32() / 4u;
const bool quad = tess.domain == 2u;
const auto outer_count = quad ? 4u : 3u;
const bool outer = index < outer_count;
EXIT_NOT_IMPLEMENTED(index >= (quad ? 6u : 4u));
state.builder.AddFunction(spv::OpAccessChain, TypePointer(state, storage, TypeF32(state)),
pointer, outer ? variable : state.tess_inner_variable,
ConstantU32(state, outer ? index : index - 3u));
ConstantU32(state, outer ? index : index - outer_count));
return pointer;
}
auto address = ctx.Arg(inst, 1);
@@ -128,20 +130,25 @@ void DefineTessellationInterfaces(EmitterState& state) {
void DefineTessellationExecutionModes(EmitterState& state) {
const auto& tess = state.input_info.vertex->tess;
// CxVgtTfParam: triangles, integer or fractional-odd partitioning, clockwise output.
EXIT_NOT_IMPLEMENTED(tess.domain != 1u ||
(tess.partitioning != 0u && tess.partitioning != 2u) ||
tess.output_topology != 2u);
// Triangle or quad domain, integer or fractional partitioning, triangle output.
EXIT_NOT_IMPLEMENTED((tess.domain != 1u && tess.domain != 2u) ||
(tess.partitioning != 0u && tess.partitioning != 2u && tess.partitioning != 3u) ||
(tess.output_topology != 2u && tess.output_topology != 3u));
state.builder.RequireCapability(spv::CapabilityTessellation);
if (state.program.stage == ShaderType::TessellationControl) {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeOutputVertices,
tess.output_control_points);
} else {
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeTriangles);
state.builder.AddExecutionMode(state.main_func, tess.domain == 1u
? spv::ExecutionModeTriangles
: spv::ExecutionModeQuads);
state.builder.AddExecutionMode(
state.main_func, tess.partitioning == 0u ? spv::ExecutionModeSpacingEqual
: spv::ExecutionModeSpacingFractionalOdd);
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeVertexOrderCw);
: tess.partitioning == 2u ? spv::ExecutionModeSpacingFractionalOdd
: spv::ExecutionModeSpacingFractionalEven);
state.builder.AddExecutionMode(state.main_func, tess.output_topology == 2u
? spv::ExecutionModeVertexOrderCw
: spv::ExecutionModeVertexOrderCcw);
}
}
@@ -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 0x0eu: return Opcode::IMAGE_GET_RESINFO;
case 0x60u: return Opcode::IMAGE_GET_LOD;
case 0xe5u: return Opcode::IMAGE_BVH8_INTERSECT_RAY;
case 0xe6u:
case 0xe7u: return Opcode::IMAGE_BVH_INTERSECT_RAY;
default: return Opcode::UNSUPPORTED;
@@ -297,6 +298,7 @@ uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
}
switch (opcode) {
case 0xe5u:
case 0xe6u: return 11u;
case 0xe7u: return 12u;
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");
}
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(vaddr, inst.src0);
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},
{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},
{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},
};
@@ -232,6 +233,7 @@ bool IsDsAtomicOpcode(Opcode opcode) {
uint32_t DsSourceCount(Opcode opcode) {
switch (opcode) {
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32: return 3u;
case Opcode::DS_WRITE2_B32:
case Opcode::DS_WRITE2ST64_B32:
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) {
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) {
SetUnsupported(inst, Family::DS, opcode, "DS compare-store return B32 GDS variant is not implemented");
}
@@ -70,6 +70,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
{0x21u, Opcode::S_SWAPPC_B64},
{0x24u, Opcode::S_AND_SAVEEXEC_B64},
{0x28u, Opcode::S_ORN2_SAVEEXEC_B64},
{0x2cu, Opcode::S_QUADMASK_B32},
{0x2du, Opcode::S_QUADMASK_B64},
{0x34u, Opcode::S_ABS_I32},
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
@@ -517,6 +517,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::S_NOT_B64:
case Opcode::S_WQM_B32:
case Opcode::S_WQM_B64:
case Opcode::S_QUADMASK_B32:
case Opcode::S_QUADMASK_B64:
case Opcode::S_AND_SAVEEXEC_B32:
case Opcode::S_ORN2_SAVEEXEC_B32:
@@ -739,6 +740,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DS_READ2ST64_B64:
case Opcode::DS_READ_B96:
case Opcode::DS_READ_B128:
case Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32:
case Opcode::DS_WRITE_B8:
case Opcode::DS_WRITE_B16:
case Opcode::DS_WRITE_B8_D16_HI:
@@ -63,6 +63,7 @@ enum class Opcode {
S_FLBIT_I32,
S_FLBIT_I32_I64,
S_BITREPLICATE_B64_B32,
S_QUADMASK_B32,
S_QUADMASK_B64,
S_GETPC_B64,
S_SETPC_B64,
@@ -248,7 +249,9 @@ enum class Opcode {
V_MIN_I32,
V_MAX_I32,
V_MUL_I32_I24,
V_MUL_HI_I32_I24,
V_MUL_U32_U24,
V_MUL_HI_U32_U24,
V_MUL_LO_U32,
V_MUL_HI_U32,
V_MUL_LO_I32,
@@ -257,6 +260,7 @@ enum class Opcode {
V_CVT_PK_U8_F32,
V_MAD_F32,
V_MAD_I16,
V_MAD_U16,
V_MAD_I32_I24,
V_MAD_U32_U24,
V_MAD_U64_U32,
@@ -277,16 +281,20 @@ enum class Opcode {
V_MIN3_I32,
V_MIN3_U32,
V_MIN3_F16,
V_MIN3_I16,
V_MIN3_U16,
V_MAX3_F32,
V_MAX3_I32,
V_MAX3_U32,
V_MAX3_F16,
V_MAX3_I16,
V_MAX3_U16,
V_MED3_F32,
V_MED3_I32,
V_MED3_U32,
V_MED3_F16,
V_MED3_I16,
V_MED3_U16,
V_SAD_U32,
V_ADD3_U32,
V_LSHL_ADD_U32,
@@ -354,6 +362,7 @@ enum class Opcode {
V_ASHRREV_I32,
V_LSHLREV_B64,
V_LSHRREV_B64,
V_ASHRREV_I64,
V_LSHLREV_B16,
V_LSHRREV_B16,
V_ASHRREV_I16,
@@ -676,6 +685,7 @@ enum class Opcode {
DS_READ2ST64_B64,
DS_READ_B96,
DS_READ_B128,
DS_BVH_STACK_PUSH8_POP1_RTN_B32,
DS_WRITE_B8,
DS_WRITE_B16,
DS_WRITE_B8_D16_HI,
@@ -691,6 +701,7 @@ enum class Opcode {
DS_WRITE_ADDTID_B32,
DS_READ_ADDTID_B32,
IMAGE_BVH_INTERSECT_RAY,
IMAGE_BVH8_INTERSECT_RAY,
IMAGE_GET_RESINFO,
IMAGE_GET_LOD,
IMAGE_LOAD,
@@ -35,7 +35,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x05u, Opcode::V_SUBREV_F32},
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0au, Opcode::V_MUL_HI_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0fu, Opcode::V_MIN_F32},
{0x10u, Opcode::V_MAX_F32},
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
@@ -157,6 +159,10 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x04u, Opcode::V_CVT_F64_I32},
{0x0fu, Opcode::V_CVT_F32_F64},
{0x10u, Opcode::V_CVT_F64_F32},
{0x11u, Opcode::V_CVT_F32_UBYTE0},
{0x12u, Opcode::V_CVT_F32_UBYTE1},
{0x13u, Opcode::V_CVT_F32_UBYTE2},
{0x14u, Opcode::V_CVT_F32_UBYTE3},
{0x16u, Opcode::V_CVT_F64_U32},
{0x17u, Opcode::V_TRUNC_F64},
{0x18u, Opcode::V_CEIL_F64},
@@ -167,6 +173,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
{0x07u, Opcode::V_CVT_U32_F32},
{0x08u, Opcode::V_CVT_I32_F32},
{0x0au, Opcode::V_CVT_F16_F32},
{0x0bu, Opcode::V_CVT_F32_F16},
{0x0cu, Opcode::V_CVT_RPI_I32_F32},
{0x0du, Opcode::V_CVT_FLR_I32_F32},
{0x0eu, Opcode::V_CVT_OFF_F32_I4},
@@ -334,16 +341,20 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x152u, Opcode::V_MIN3_I32},
{0x153u, Opcode::V_MIN3_U32},
{0x351u, Opcode::V_MIN3_F16},
{0x352u, Opcode::V_MIN3_I16},
{0x353u, Opcode::V_MIN3_U16},
{0x154u, Opcode::V_MAX3_F32},
{0x155u, Opcode::V_MAX3_I32},
{0x156u, Opcode::V_MAX3_U32},
{0x354u, Opcode::V_MAX3_F16},
{0x355u, Opcode::V_MAX3_I16},
{0x356u, Opcode::V_MAX3_U16},
{0x157u, Opcode::V_MED3_F32},
{0x158u, Opcode::V_MED3_I32},
{0x159u, Opcode::V_MED3_U32},
{0x357u, Opcode::V_MED3_F16},
{0x358u, Opcode::V_MED3_I16},
{0x359u, Opcode::V_MED3_U16},
{0x15du, Opcode::V_SAD_U32},
{0x15eu, Opcode::V_CVT_PK_U8_F32},
{0x178u, Opcode::V_XOR3_B32},
@@ -354,6 +365,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x16cu, Opcode::V_MUL_HI_I32},
{0x2ffu, Opcode::V_LSHLREV_B64},
{0x300u, Opcode::V_LSHRREV_B64},
{0x301u, Opcode::V_ASHRREV_I64},
{0x303u, Opcode::V_ADD_NC_U16},
{0x304u, Opcode::V_SUB_NC_U16},
{0x305u, Opcode::V_MUL_LO_U16},
@@ -370,6 +382,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x311u, Opcode::V_PACK_B32_F16},
{0x314u, Opcode::V_LSHLREV_B16},
{0x319u, Opcode::V_SUBREV_I32},
{0x340u, Opcode::V_MAD_U16},
{0x345u, Opcode::V_XAD_U32},
{0x346u, Opcode::V_LSHL_ADD_U32},
{0x347u, Opcode::V_ADD_LSHL_U32},
@@ -488,8 +501,10 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
}
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 ||
opcode == Opcode::V_MAD_I16;
return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 ||
opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 ||
opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16;
}
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
@@ -1335,6 +1350,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
case Opcode::V_SUB_NC_I16:
case Opcode::V_LSHLREV_B64:
case Opcode::V_LSHRREV_B64:
case Opcode::V_ASHRREV_I64:
case Opcode::V_LSHLREV_B16:
case Opcode::V_LSHRREV_B16:
case Opcode::V_ASHRREV_I16:
@@ -1523,7 +1539,10 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
bool SupportsNativeVop3Clamp(Opcode opcode) {
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
opcode == Opcode::V_MAD_I16;
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 ||
opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 ||
opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MUL_HI_I32_I24 || opcode == Opcode::V_MUL_HI_U32_U24;
}
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
@@ -1554,7 +1573,17 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
return clamp != 0u || omod != 0u || neg != 0u;
}
switch (opcode) {
case Opcode::V_MUL_HI_I32_I24:
case Opcode::V_MUL_HI_U32_U24:
// The high product fits in 32 bits, so clamp has no effect.
return abs != 0u || op_sel != 0u || omod != 0u || neg != 0u;
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
case Opcode::V_CVT_F32_UBYTE0:
case Opcode::V_CVT_F32_UBYTE1:
case Opcode::V_CVT_F32_UBYTE2:
case Opcode::V_CVT_F32_UBYTE3:
// Byte conversions ignore input modifiers and support all output modifiers.
return op_sel != 0u;
case Opcode::V_INTERP_P1LL_F16:
case Opcode::V_INTERP_P2_F16:
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
@@ -20,12 +20,12 @@ void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
Write16Bits(DestinationOperand(inst), result);
}
void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
const auto lhs = ReadU16AsU32(inst.src0, true);
const auto rhs = ReadU16AsU32(inst.src1, true);
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
void Translator::Integer16Mad(const Decoder::Instruction& inst, bool sign) {
const auto lhs = ReadU16AsU32(inst.src0, sign);
const auto rhs = ReadU16AsU32(inst.src1, sign);
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, sign));
if (inst.dst.clamp) {
result = Saturate16(result, true);
result = Saturate16(result, sign);
}
Write16Bits(DestinationOperand(inst), result);
}
@@ -147,6 +147,12 @@ void Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
const auto arg_type = IR::ArgTypeOf(opcode, index);
const auto& operand = SourceAt(inst, reverse && index < 2u ? 1u - index : index);
args[index] = ReadOperand(operand, arg_type == IR::Type::Void ? type : arg_type);
if (opcode == IR::ValueOpcode::ShiftRightArithmetic64 && index == 0u &&
operand.kind == Decoder::OperandKind::LiteralConstant) {
args[index] = ir.ConstructU64(
ReadU32(operand),
IR::U32(IR::Value((operand.value & 0x80000000u) ? 0xffffffffu : 0u)));
}
if (mask_shift_count && index == 1u) {
args[index] = ir.BitwiseAnd(IR::U32(args[index]), IR::U32(IR::Value(31u)));
}
@@ -263,7 +269,7 @@ void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
WriteOperand(DestinationOperand(inst), result);
}
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) {
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high) {
const auto extract24 = [&](IR::U32 value) {
return IR::U32(
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
@@ -271,7 +277,9 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
};
const auto lhs = extract24(ReadU32(inst.src0));
const auto rhs = extract24(ReadU32(inst.src1));
auto result = ir.IMul(lhs, rhs);
const auto multiply = high ? (sign ? IR::ValueOpcode::SMulHi : IR::ValueOpcode::UMulHi)
: IR::ValueOpcode::IMul32;
auto result = IR::U32(ir.Emit(multiply, {lhs, rhs}));
if (addend) {
result = ir.IAdd(result, ReadU32(inst.src2));
}
@@ -369,7 +377,7 @@ void Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high)));
}
void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
void Translator::S_QUADMASK(const Decoder::Instruction& inst, bool is64) {
const auto compact = [&](IR::U32 value) {
auto bits = ir.BitwiseOr(value, ir.ShiftRightLogical(value, IR::U32(IR::Value(1u))));
bits = ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(2u))));
@@ -382,12 +390,17 @@ void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
ir.BitwiseOr(bits, ir.ShiftRightLogical(bits, IR::U32(IR::Value(12u)))),
IR::U32(IR::Value(0xffu)));
};
const auto source = ReadU32Pair(inst.src0);
const auto quads = ir.BitwiseOr(
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
const auto result = ir.ConstructU64(quads, IR::U32(IR::Value(0u)));
WriteOperand(DestinationOperand(inst), result);
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
IR::U32 quads;
if (is64) {
const auto source = ReadU32Pair(inst.src0);
quads = ir.BitwiseOr(
compact(source[0]), ir.ShiftLeftLogical(compact(source[1]), IR::U32(IR::Value(8u))));
WriteOperand(DestinationOperand(inst), ir.ConstructU64(quads, IR::U32(IR::Value(0u))));
} else {
quads = compact(ReadU32(inst.src0));
WriteOperand(DestinationOperand(inst), quads);
}
ir.SetScc(ir.INotEqual(quads, IR::U32(IR::Value(0u))));
}
void Translator::BFM_B32(const Decoder::Instruction& inst) {
@@ -326,7 +326,8 @@ IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
inst.image_sample_flags, inst.image_dimension, inst.image_address_components);
EXIT_IF(count > components.size());
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++) {
if (index != 0u && index - 1u < nsa_components) {
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) {
const auto memory = MemoryInfoFromDecoded(inst);
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()}));
}
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) {
switch (inst.opcode) {
// 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:
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
return;
case Decoder::Opcode::IMAGE_BVH8_INTERSECT_RAY: return IMAGE_BVH8(inst);
case Decoder::Opcode::IMAGE_BVH_INTERSECT_RAY: {
const auto result = ir.Emit(IR::ValueOpcode::BvhIntersect,
{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);
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_BVH_STACK_PUSH8_POP1_RTN_B32: return DS_BVH_STACK(inst);
case Decoder::Opcode::DS_READ2_B32:
case Decoder::Opcode::DS_READ2ST64_B32:
case Decoder::Opcode::DS_READ2_B64:
@@ -248,7 +248,8 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_BITSET0_B64: return S_BITSET_B64(inst, false);
case O::S_BITSET1_B64: return S_BITSET_B64(inst, true);
case O::S_BITREPLICATE_B64_B32: return S_BITREPLICATE_B64_B32(inst);
case O::S_QUADMASK_B64: return S_QUADMASK_B64(inst);
case O::S_QUADMASK_B32: return S_QUADMASK(inst, false);
case O::S_QUADMASK_B64: return S_QUADMASK(inst, true);
case O::S_BFM_B32: return BFM_B32(inst);
case O::S_BFM_B64: return S_BFM_B64(inst);
case O::S_BFE_U32: return S_BFE_U32(inst, false);
@@ -1165,6 +1165,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
: IR::U32(IR::Value(0u));
entry_ir.SetExecLo(initial_lo);
entry_ir.SetExecHi(initial_hi);
const auto active_lane_count = [&] {
auto count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
if (options.wave_size == 64u) {
count = entry_ir.IAdd(count, IR::U32(entry_ir.Emit(
IR::ValueOpcode::BitCount32, {initial_hi})));
}
return count;
};
if (options.stage == ShaderType::Compute) {
const auto* cs = options.input_info.compute;
const auto thread_ids =
@@ -1302,7 +1310,7 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
builtin(IR::StageInputKind::WorkgroupId, 1));
}
} else if (options.stage == ShaderType::Local) {
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), active_lane_count());
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(2),
builtin(IR::StageInputKind::VertexIndex));
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(3), IR::U32(IR::Value(0u)));
@@ -1380,15 +1388,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
} else if (options.stage == ShaderType::Vertex) {
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
// ESVertCount describes input vertices, independently of lane ordinals.
auto vertex_count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
if (options.wave_size == 64u) {
vertex_count = entry_ir.IAdd(vertex_count, IR::U32(entry_ir.Emit(
IR::ValueOpcode::BitCount32, {initial_hi})));
}
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
IR::U32(IR::Value(options.wave_size << 12u)));
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
entry_ir.BitwiseOr(active_lane_count(), IR::U32(IR::Value(1u << 28u))));
// Vulkan adds an indirect record's bases to the indices; the hardware passes them
// only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged;
// it does not make the record's bases zero.
@@ -105,6 +105,8 @@ private:
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
const IR::MemoryInfo& memory, uint32_t pc);
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_WRITE(const Decoder::Instruction& inst);
void DS_WRITE2(const Decoder::Instruction& inst);
@@ -167,7 +169,7 @@ private:
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void V_MAD_I16(const Decoder::Instruction& inst);
void Integer16Mad(const Decoder::Instruction& inst, bool sign);
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic);
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
@@ -187,7 +189,7 @@ private:
void V_XOR3_B32(const Decoder::Instruction& inst);
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign);
void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high = false);
void V_MAD_U64_U32(const Decoder::Instruction& inst);
void V_SAD_U32(const Decoder::Instruction& inst);
void V_ADD3_U32(const Decoder::Instruction& inst);
@@ -196,7 +198,7 @@ private:
void V_BCNT_U32_B32(const Decoder::Instruction& inst);
void V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
void S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
void S_QUADMASK_B64(const Decoder::Instruction& inst);
void S_QUADMASK(const Decoder::Instruction& inst, bool is64);
void BFM_B32(const Decoder::Instruction& inst);
IR::U32 RightMask32(IR::U32 count);
void S_BFM_B64(const Decoder::Instruction& inst);
@@ -375,9 +375,14 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_SUB_NC_U16:
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
case O::V_MAD_I16: return V_MAD_I16(inst);
case O::V_MAD_I16: return Integer16Mad(inst, true);
case O::V_MAD_U16: return Integer16Mad(inst, false);
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
case O::V_MIN3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMinTri32, true);
case O::V_MAX3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMaxTri32, true);
case O::V_MAX3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMaxTri32, false);
case O::V_MED3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMedTri32, false);
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
@@ -565,6 +570,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_LSHRREV_B64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightLogical64, IR::Type::U64, true,
false, false);
case O::V_ASHRREV_I64:
return SimpleInteger(inst, IR::ValueOpcode::ShiftRightArithmetic64, IR::Type::U64, true,
false, false);
case O::V_XNOR_B32:
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, false);
@@ -578,6 +586,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MAD_U32_U24: return Integer24(inst, false, true);
case O::V_MUL_I32_I24: return Integer24(inst, true, false);
case O::V_MUL_U32_U24: return Integer24(inst, false, false);
case O::V_MUL_HI_I32_I24: return Integer24(inst, true, false, true);
case O::V_MUL_HI_U32_U24: return Integer24(inst, false, false, true);
case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst);
case O::V_SAD_U32: return V_SAD_U32(inst);
case O::V_ADD3_U32: return V_ADD3_U32(inst);
@@ -155,7 +155,7 @@ KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
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,
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::MaskRanks, a, b, c)
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_dispatch_threads = false;
bool bvh = false;
bool bvh8 = false;
bool bvh_stack = false;
bool subgroup_ballot = false;
bool subgroup_vote = false;
bool subgroup_barrier = false;
@@ -31,6 +31,7 @@ std::string TypeName(Type type) {
"SamplerResource",
"ImageAddress",
"F64",
"Bvh8Result",
};
const auto bits = static_cast<uint32_t>(type);
if (bits == 0) {
+1
View File
@@ -28,6 +28,7 @@ enum class Type : uint32_t {
SamplerResource = 1u << 18u,
ImageAddress = 1u << 19u,
F64 = 1u << 20u,
Bvh8Result = 1u << 21u,
};
constexpr Type operator|(Type lhs, Type rhs) {
@@ -44,6 +44,7 @@ constexpr Type AddressResource = Type::AddressResource;
constexpr Type ImageResource = Type::ImageResource;
constexpr Type SamplerResource = Type::SamplerResource;
constexpr Type ImageAddress = Type::ImageAddress;
constexpr Type Bvh8Result = Type::Bvh8Result;
constexpr std::array<OpcodeMeta, static_cast<size_t>(ValueOpcode::Count)> MetaTable = {{
#define VALUE_OPCODE(name, ...) MakeMeta(#name __VA_OPT__(, ) __VA_ARGS__),
@@ -76,7 +77,8 @@ bool HasSideEffects(ValueOpcode opcode) {
return true;
}
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) {
return true;
}
@@ -156,6 +158,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
SharedAccess SharedAccessOf(ValueOpcode opcode) {
switch (opcode) {
case ValueOpcode::BvhStack: return SharedAccess::ReadWrite;
case ValueOpcode::LoadSharedU8:
case ValueOpcode::LoadSharedU16:
case ValueOpcode::LoadSharedU32:
@@ -17,7 +17,7 @@ enum class ValueOpcode : uint16_t {
};
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 ImageAccess { None, Read, Write, Atomic };
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(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(ImageRead, U32x4, ImageResource, ImageAddress, U1)
VALUE_OPCODE(ImageWrite, Void, ImageResource, ImageAddress, U32x4, U1)
@@ -4182,7 +4182,7 @@ private:
void Collect(Inst& inst) {
const auto op = inst.GetOpcode();
if (op == ValueOpcode::BvhIntersect) {
if (op == ValueOpcode::BvhIntersect || op == ValueOpcode::Bvh8Intersect) {
m_info.uses_dma = true;
return;
}
@@ -515,6 +515,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
case ValueOpcode::StoreCompletion: info.subgroup_barrier = true; break;
case ValueOpcode::DataCacheInvalidate: info.coherent_buffers = 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:
info.subgroup_vote |=
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 +
std::max(params.amm_table_memory, INCLUDED_TABLE_MEMORY) -
3 * INCLUDED_TABLE_MEMORY;
EXIT_IF(g_flexible_memory_size + table_overhead >= GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE);
g_guest_memory_size = GUEST_MEMORY_BUDGET - RESERVED_CACHE_SIZE - table_overhead;
const auto memory_budget = GUEST_MEMORY_BUDGET +
(Loader::SystemContentIsTrinityMode() ? 1216ull * 1024ull * 1024ull : 0);
EXIT_IF(g_flexible_memory_size + table_overhead >= memory_budget - RESERVED_CACHE_SIZE);
g_guest_memory_size = memory_budget - RESERVED_CACHE_SIZE - table_overhead;
LOGF("\t flexible memory size = %" PRIu64 " MiB, direct memory size = %" PRIu64 " MiB\n",
g_flexible_memory_size / (1024ull * 1024ull),
(g_guest_memory_size - g_flexible_memory_size) / (1024ull * 1024ull));
+4 -1
View File
@@ -2277,7 +2277,10 @@ int KYTY_SYSV_ABI PthreadRwlockDestroy(PthreadRwlock* rwlock) {
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);
@@ -254,6 +254,12 @@
<widget class="QGroupBox" name="general_group">
<property name="title"><string>General</string></property>
<layout class="QVBoxLayout" name="generalLayout">
<item>
<widget class="QCheckBox" name="checkBox_pro">
<property name="toolTip"><string>Enable experimental Pro mode for supported titles. PSSR uses linear scaling. Disabled by default.</string></property>
<property name="text"><string>Pro mode (experimental)</string></property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_fullscreen">
<property name="toolTip"><string>Run in borderless desktop fullscreen</string></property>
@@ -406,6 +412,11 @@
<property name="text"><string>Enable network connection</string></property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_psn_emulation">
<property name="text"><string>Emulate PSN (compatibility)</string></property>
</widget>
</item>
<item>
<spacer name="networkSpacer">
<property name="orientation"><enum>Qt::Vertical</enum></property>
+4
View File
@@ -128,6 +128,7 @@ public:
bool perf_overlay_enabled = false;
bool skip_notice_screen = false;
bool network_connected = false;
bool psn_emulation_enabled = false;
int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
@@ -141,6 +142,7 @@ public:
QString printf_output_file = "_kyty.txt";
bool profiler_enabled = false;
bool renderdoc_enabled = false;
bool pro_enabled = false;
bool amd_cpu_enabled = false;
#if defined(_WIN32)
bool red_zone_protection_enabled = false;
@@ -171,6 +173,7 @@ public:
perf_overlay_enabled = other.perf_overlay_enabled;
skip_notice_screen = other.skip_notice_screen;
network_connected = other.network_connected;
psn_emulation_enabled = other.psn_emulation_enabled;
vblank_frequency = other.vblank_frequency;
console_language = other.console_language;
vulkan_validation_enabled = other.vulkan_validation_enabled;
@@ -184,6 +187,7 @@ public:
printf_output_file = other.printf_output_file;
profiler_enabled = other.profiler_enabled;
renderdoc_enabled = other.renderdoc_enabled;
pro_enabled = other.pro_enabled;
amd_cpu_enabled = other.amd_cpu_enabled;
#if defined(_WIN32)
red_zone_protection_enabled = other.red_zone_protection_enabled;
+4
View File
@@ -42,6 +42,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(perf_overlay_enabled);
KYTY_CFG_SET(skip_notice_screen);
KYTY_CFG_SET(network_connected);
KYTY_CFG_SET(psn_emulation_enabled);
KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language);
KYTY_CFG_SET(vulkan_validation_enabled);
@@ -55,6 +56,7 @@ QVariantMap Configuration::GameSettings() const {
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_enabled);
KYTY_CFG_SET(renderdoc_enabled);
KYTY_CFG_SET(pro_enabled);
KYTY_CFG_SET(amd_cpu_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
@@ -96,6 +98,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
KYTY_CFG_GET(perf_overlay_enabled);
KYTY_CFG_GET(skip_notice_screen);
KYTY_CFG_GET(network_connected);
KYTY_CFG_GET(psn_emulation_enabled);
vblank_frequency = s.value("vblank_frequency", vblank_frequency).toInt();
console_language = s.value("console_language", console_language).toInt();
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(profiler_enabled);
KYTY_CFG_GET(renderdoc_enabled);
pro_enabled = s.value("pro_enabled", false).toBool();
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
#if defined(_WIN32)
red_zone_protection_enabled =
@@ -298,6 +298,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_shader_cache->setChecked(info.shader_cache_enabled);
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
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->spinBox_trophy_duration->setValue(info.trophy_duration_seconds);
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_vulkan_validation->setChecked(info.vulkan_validation_enabled);
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
m_ui->checkBox_pro->setChecked(info.pro_enabled);
m_ui->checkBox_amd_cpu->setChecked(info.amd_cpu_enabled);
#if defined(__APPLE__)
m_ui->checkBox_amd_cpu->setVisible(false);
@@ -533,6 +535,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.shader_cache_enabled = ui.checkBox_shader_cache->isChecked();
info.skip_notice_screen = ui.checkBox_skip_notice_screen->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_duration_seconds = ui.spinBox_trophy_duration->value();
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.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked();
info.pro_enabled = ui.checkBox_pro->isChecked();
info.amd_cpu_enabled = ui.checkBox_amd_cpu->isChecked();
#if defined(_WIN32)
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
+2
View File
@@ -276,6 +276,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
AddFlag(args, info.perf_overlay_enabled, "--perf-overlay");
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
AddFlag(args, info.network_connected, "--network-connected");
AddFlag(args, info.psn_emulation_enabled, "--emulate-psn");
AddFlag(args, info.tessellation_enabled, "--tessellation");
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
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-output-file" << info.printf_output_file;
AddFlag(args, info.profiler_enabled, "--profile");
AddFlag(args, info.pro_enabled, "--trinity");
AddFlag(args, info.amd_cpu_enabled, "--amd-cpu");
#if defined(_WIN32)
AddFlag(args, info.red_zone_protection_enabled, "--redzone");
+3 -2
View File
@@ -24,6 +24,7 @@
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <array>
@@ -839,8 +840,8 @@ static bool is_native_reg_indirect_packet(const uint32_t* cmd, RegIndirectPacket
return ((cmd[0] >> 8u) & 0xffu) == reg_indirect_native_op(type);
}
static constexpr bool is_trinity_mode() {
return false;
static bool is_trinity_mode() {
return Loader::SystemContentIsTrinityMode();
}
static uint32_t reg_indirect_pm4_r(RegIndirectPacket type) {
+55 -10
View File
@@ -6,6 +6,7 @@
#include "libs/ajm/decoder.h"
#include "libs/ajm/mp3_decoder.h"
#include "libs/ajm/opus_decoder.h"
#include "libs/ajm/resampler.h"
#include "libs/audio.h"
#include "libs/errno.h"
#include "libs/libs.h"
@@ -63,12 +64,6 @@ struct AjmSidebandMFrame {
uint32_t reserved;
};
struct AjmSidebandResampleInfo {
float ratio;
int32_t num_samples;
uint32_t reserved[8];
};
struct AjmGetStatisticsResult {
AjmSidebandResult result;
float engine_usage_batch;
@@ -104,6 +99,8 @@ constexpr uint64_t AJM_FLAG_RUN_GET_CODEC_INFO = 1ull << 11u;
constexpr uint64_t AJM_FLAG_RUN_MULTIPLE_FRAMES = 1ull << 12u;
constexpr uint64_t AJM_FLAG_CONTROL_RESET = 1ull << 13u;
constexpr uint64_t AJM_FLAG_CONTROL_INITIALIZE = 1ull << 14u;
constexpr uint64_t AJM_FLAG_CONTROL_RESAMPLE = 1ull << 15u;
constexpr uint64_t AJM_FLAG_CONTROL_RESAMPLE_EX = 1ull << 16u;
constexpr uint64_t AJM_FLAG_SIDEBAND_RESAMPLE_INFO = 1ull << 44u;
constexpr uint64_t AJM_FLAG_SIDEBAND_GAPLESS = 1ull << 45u;
constexpr uint64_t AJM_FLAG_SIDEBAND_FORMAT = 1ull << 46u;
@@ -177,6 +174,7 @@ struct AjmInstanceState {
uint64_t flags = 0;
AjmGaplessState gapless;
std::unique_ptr<AjmDecoder> decoder;
AjmResampler resampler;
};
static std::mutex g_ajm_instances_mutex;
@@ -358,7 +356,7 @@ static void AjmWriteSideband(uint64_t flags, void* sideband_output, size_t sideb
offset + AJM_SIDEBAND_RESAMPLE_INFO_SIZE <= sideband_output_size) {
auto* info = reinterpret_cast<AjmSidebandResampleInfo*>(
static_cast<uint8_t*>(sideband_output) + offset);
info->ratio = 1.0f;
*info = decode_result.resample_info;
offset += AJM_SIDEBAND_RESAMPLE_INFO_SIZE;
}
@@ -425,6 +423,12 @@ static AjmDecodeResult AjmDecodeInstance(uint32_t instance, const void* input, s
return result;
}
// The resampler requires one complete decoded packet per decoder call.
if (state->codec == static_cast<uint32_t>(AjmCodec::DecOpus) &&
(state->flags & AJM_INSTANCE_FLAG_RESAMPLE) != 0) {
return state->resampler.Decode(*state->decoder, state->flags, input, input_size, output,
output_size, multiple_frames, &state->gapless);
}
return state->decoder->Decode(input, input_size, output, output_size, multiple_frames,
&state->gapless);
}
@@ -514,6 +518,32 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
sideband_input = static_cast<const uint8_t*>(sideband_input) + AJM_SIDEBAND_GAPLESS_SIZE;
sideband_input_size -= AJM_SIDEBAND_GAPLESS_SIZE;
}
for (const auto flag : {AJM_FLAG_CONTROL_RESAMPLE, AJM_FLAG_CONTROL_RESAMPLE_EX}) {
if ((flags & flag) == 0) {
continue;
}
const bool extended = flag == AJM_FLAG_CONTROL_RESAMPLE_EX;
const size_t size = extended ? 12 : 8;
if (sideband_input == nullptr || sideband_input_size < size ||
(state->flags & AJM_INSTANCE_FLAG_RESAMPLE) == 0) {
result.result = AJM_RESULT_INVALID_PARAMETER;
return result;
}
float ratio, change = 0;
uint32_t resample_flags;
const auto* data = static_cast<const uint8_t*>(sideband_input);
std::memcpy(&ratio, data, sizeof(ratio));
if (extended) {
std::memcpy(&change, data + sizeof(ratio), sizeof(change));
}
std::memcpy(&resample_flags, data + size - sizeof(resample_flags), sizeof(resample_flags));
result.result = state->resampler.SetParameters(ratio, change, resample_flags);
if (result.result != OK) {
return result;
}
sideband_input = data + size;
sideband_input_size -= size;
}
if ((flags & AJM_FLAG_CONTROL_RESET) != 0) {
state->gapless.Reset();
decoder->Reset();
@@ -524,6 +554,7 @@ static AjmDecodeResult AjmControlInstance(uint32_t instance, uint64_t flags,
result = decoder->Initialize(sideband_input, sideband_input_size);
}
result.resample_info = state->resampler.GetInfo();
return result;
}
@@ -572,6 +603,7 @@ static AjmSidebandFormat AjmGetInstanceFormat(uint32_t instance,
if (out_result != nullptr) {
*out_result = state->decoder->MakeResult();
out_result->format = state->decoder->GetFormat();
out_result->resample_info = state->resampler.GetInfo();
}
return state->decoder->GetFormat();
}
@@ -921,7 +953,13 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParameters(AjmBatchInfo* info, uint32_t
float ratio, uint32_t flags, void* result) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(info == nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE);
const struct {
float ratio;
uint32_t flags;
} params {ratio, flags};
const auto decoded = AjmControlInstance(instance, AJM_FLAG_CONTROL_RESAMPLE, &params,
sizeof(params), nullptr, nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decoded);
LOGF("\t instance = 0x%08" PRIx32 ", ratio = %f, flags = 0x%08" PRIx32 "\n", instance,
static_cast<double>(ratio), flags);
return AjmAppendJob(info, AJM_JOB_SET_RESAMPLE_PARAMETERS_SIZE, "set-resample");
@@ -933,7 +971,14 @@ int KYTY_SYSV_ABI AjmBatchJobSetResampleParametersEx(AjmBatchInfo* info, uint32_
void* result) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(info == nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE);
const struct {
float ratio;
float change;
uint32_t flags;
} params {ratio_start, ratio_change_per_sample, flags};
const auto decoded = AjmControlInstance(instance, AJM_FLAG_CONTROL_RESAMPLE_EX, &params,
sizeof(params), nullptr, nullptr);
AjmWriteResult(result, AJM_SIDEBAND_RESULT_SIZE, decoded);
LOGF("\t instance = 0x%08" PRIx32 ", ratio_start = %f, ratio_change = %f, flags = 0x%08" PRIx32
"\n",
instance, static_cast<double>(ratio_start), static_cast<double>(ratio_change_per_sample),
@@ -952,7 +997,7 @@ int KYTY_SYSV_ABI AjmBatchJobGetResampleInfo(AjmBatchInfo* info, uint32_t instan
if (result != nullptr) {
auto* resample = reinterpret_cast<AjmSidebandResampleInfo*>(static_cast<uint8_t*>(result) +
AJM_SIDEBAND_RESULT_SIZE);
resample->ratio = 1.0f;
*resample = decode_result.resample_info;
}
LOGF("\t instance = 0x%08" PRIx32 "\n", instance);
return AjmAppendJob(info, AJM_JOB_RUN_SIZE, "get-resample-info");
+15 -8
View File
@@ -56,15 +56,22 @@ enum class AjmSampleEncoding : uint32_t {
Float = 2,
};
struct AjmSidebandResampleInfo {
float ratio = 1.0f;
int32_t num_samples = 0;
uint32_t reserved[8] {};
};
struct AjmDecodeResult {
int32_t result = OK;
int32_t internal_result = OK;
size_t input_consumed = 0;
size_t output_written = 0;
uint64_t total_decoded_samples = 0;
uint32_t frames = 0;
uint32_t frames_per_packet = 0;
AjmSidebandFormat format {};
int32_t result = OK;
int32_t internal_result = OK;
size_t input_consumed = 0;
size_t output_written = 0;
uint64_t total_decoded_samples = 0;
uint32_t frames = 0;
uint32_t frames_per_packet = 0;
AjmSidebandFormat format {};
AjmSidebandResampleInfo resample_info {};
};
constexpr int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001;
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include "libs/ajm/decoder.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
namespace Libs::Audio::Ajm {
constexpr uint64_t AJM_INSTANCE_FLAG_GAPLESS_LOOP = 1ull << 10u;
constexpr uint64_t AJM_INSTANCE_FLAG_RESAMPLE = 1ull << 12u;
constexpr uint64_t AJM_INSTANCE_FLAG_SEAMLESS_LOOP = 1ull << 13u;
constexpr uint32_t AJM_RESAMPLE_FLAG_RESET = 1u;
constexpr uint32_t AJM_RESAMPLE_FLAG_END = 2u;
constexpr uint32_t AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER = 8u;
class AjmResampler {
public:
int32_t SetParameters(float ratio, float change, uint32_t flags) {
if ((flags & AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER) == 0 &&
(!std::isfinite(ratio) || ratio < 0 || ratio > 4 || !std::isfinite(change))) {
return AJM_RESULT_INVALID_PARAMETER;
}
if ((flags & AJM_RESAMPLE_FLAG_RESET) != 0) {
m_pcm.clear();
m_position = 0;
m_end = false;
}
if ((flags & AJM_RESAMPLE_FLAG_IGNORE_RATIO_PARAMETER) == 0) {
m_ratio = ratio;
m_change = std::abs(change) < 0.000001f ? 0 : change;
}
m_end |= (flags & AJM_RESAMPLE_FLAG_END) != 0;
return OK;
}
[[nodiscard]] AjmSidebandResampleInfo GetInfo() const {
AjmSidebandResampleInfo info;
info.ratio = static_cast<float>(m_ratio);
info.num_samples = m_end && m_position >= Samples()
? -1
: static_cast<int32_t>(std::ceil(Samples() - m_position));
return info;
}
AjmDecodeResult Decode(AjmDecoder& decoder, uint64_t flags, const void* input,
size_t input_size, void* output, size_t output_size,
bool multiple_frames, AjmGaplessState* gapless) {
auto result = decoder.MakeResult();
if (result.format.channel_num == 0) {
return decoder.Decode(input, input_size, output, output_size, multiple_frames, gapless);
}
bool stop_decoding = false;
for (;;) {
const auto format = decoder.GetFormat();
const auto encoding = static_cast<AjmSampleEncoding>(format.sample_encoding);
const auto sample_bytes = format.channel_num * AjmBytesPerSample(encoding);
if (!m_pcm.empty() && sample_bytes != m_sample_bytes) {
result.result = AJM_RESULT_INVALID_PARAMETER;
break;
}
if (output == nullptr || output_size - result.output_written < sample_bytes) {
if (m_position < Samples() || result.input_consumed < input_size) {
result.result |= AJM_RESULT_NOT_ENOUGH_ROOM;
}
break;
}
const auto index = static_cast<size_t>(m_position);
const auto fraction = m_position - static_cast<double>(index);
const bool need_next = fraction != 0 && index + 1 >= Samples();
if (index >= Samples() || need_next) {
Compact();
if (!stop_decoding && input != nullptr && result.input_consumed < input_size &&
(gapless == nullptr || !gapless->IsEnd())) {
// A decode job can produce up to six 20 ms Opus frames in one packet.
const auto channels = std::max<uint32_t>(format.channel_num, flags & 0x7fu);
m_frame.resize(5760 * channels * AjmBytesPerSample(encoding));
auto decoded =
decoder.Decode(static_cast<const uint8_t*>(input) + result.input_consumed,
input_size - result.input_consumed, m_frame.data(),
m_frame.size(), false, gapless);
result.input_consumed += decoded.input_consumed;
result.frames += decoded.frames;
result.frames_per_packet = decoded.frames_per_packet;
result.total_decoded_samples = decoded.total_decoded_samples;
result.format = decoded.format;
result.result |= decoded.result;
result.internal_result = decoded.internal_result;
m_sample_bytes = decoded.format.channel_num * AjmBytesPerSample(encoding);
m_pcm.insert(m_pcm.end(), m_frame.begin(),
m_frame.begin() + decoded.output_written);
stop_decoding =
!multiple_frames || decoded.result != OK || decoded.input_consumed == 0;
if (gapless != nullptr && gapless->IsEnd() &&
(flags & AJM_INSTANCE_FLAG_GAPLESS_LOOP) != 0) {
gapless->Reset();
stop_decoding |= (flags & AJM_INSTANCE_FLAG_SEAMLESS_LOOP) == 0;
}
continue;
}
if (!m_end || m_position >= Samples()) {
if (!m_end && !stop_decoding && (gapless == nullptr || !gapless->IsEnd())) {
result.result |= AJM_RESULT_PARTIAL_INPUT;
}
break;
}
}
const auto current = static_cast<size_t>(m_position);
const auto next = std::min(current + 1, Samples() - 1);
auto* dst = static_cast<uint8_t*>(output) + result.output_written;
const auto* a = m_pcm.data() + current * sample_bytes;
const auto* b = m_pcm.data() + next * sample_bytes;
const auto weight = m_position - static_cast<double>(current);
if (weight == 0) {
std::memcpy(dst, a, sample_bytes);
} else {
switch (encoding) {
case AjmSampleEncoding::S16:
Interpolate<int16_t>(dst, a, b, format.channel_num, weight);
break;
case AjmSampleEncoding::S32:
Interpolate<int32_t>(dst, a, b, format.channel_num, weight);
break;
case AjmSampleEncoding::Float:
Interpolate<float>(dst, a, b, format.channel_num, weight);
break;
}
}
result.output_written += sample_bytes;
m_position += m_ratio;
m_ratio = std::clamp(m_ratio + m_change, 0.0, 4.0);
if (m_ratio == 0 || m_ratio == 4) {
m_change = 0;
}
}
Compact();
result.resample_info = GetInfo();
return result;
}
private:
template <typename T>
static void Interpolate(uint8_t* dst, const uint8_t* a, const uint8_t* b, uint32_t channels,
double weight) {
for (uint32_t channel = 0; channel < channels; ++channel) {
T first, second;
std::memcpy(&first, a + channel * sizeof(T), sizeof(T));
std::memcpy(&second, b + channel * sizeof(T), sizeof(T));
const auto value =
static_cast<T>(first + (static_cast<double>(second) - first) * weight);
std::memcpy(dst + channel * sizeof(T), &value, sizeof(T));
}
}
[[nodiscard]] size_t Samples() const {
return m_sample_bytes == 0 ? 0 : m_pcm.size() / m_sample_bytes;
}
void Compact() {
const auto consumed = std::min(static_cast<size_t>(m_position), Samples());
const auto bytes = consumed * m_sample_bytes;
if (bytes != 0) {
std::memmove(m_pcm.data(), m_pcm.data() + bytes, m_pcm.size() - bytes);
m_pcm.resize(m_pcm.size() - bytes);
m_position -= static_cast<double>(consumed);
}
}
std::vector<uint8_t> m_pcm;
std::vector<uint8_t> m_frame;
size_t m_sample_bytes = 0;
double m_position = 0;
double m_ratio = 1;
double m_change = 0;
bool m_end = false;
};
} // namespace Libs::Audio::Ajm
+3 -4
View File
@@ -32,6 +32,7 @@ namespace AppContent {
constexpr uint32_t APP_CONTENT_APPPARAM_SKU_FLAG_FULL = 3;
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_FOUND = -2133262331; /* 0x80D90005 */
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) {
if (m.label == entry->entitlement_label) {
const auto guest_point = m.point.substr(0, m.point.size() - 1);
std::strncpy(mount_point->data, guest_point.c_str(), sizeof(mount_point->data) - 1);
LOGF("\t already mounted %s\n", guest_point.c_str());
return OK;
LOGF("\t already mounted %s\n", m.point.c_str());
return APP_CONTENT_ERROR_BUSY;
}
}
+2
View File
@@ -1162,6 +1162,8 @@ LIB_DEFINE(InitAudio_1_Ngs2) {
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
LIB_FUNC("9eic4AmjGVI", Ngs2::Ngs2VoiceQueryInfo);
LIB_FUNC("WCayTgob7-o", Ngs2::Ngs2VoiceGetPortInfo);
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
+3 -13
View File
@@ -20,6 +20,7 @@
#include "libs/network.h"
#include "loader/elf.h"
#include "loader/runtimeLinker.h"
#include "loader/systemContent.h"
#include "loader/symbolDatabase.h"
#include <algorithm>
@@ -1321,23 +1322,12 @@ static void* KYTY_SYSV_ABI KernelGetProcParam() {
}
static int KYTY_SYSV_ABI KernelIsTrinityMode() {
static std::atomic_uint32_t call_count {0};
auto count = call_count.fetch_add(1, std::memory_order_relaxed);
if (count < 8 || (count % 1024) == 0) {
PRINT_NAME();
LOGF("\tcall = %u, return = 0 (base PS5 mode)\n", count + 1);
}
return 0;
return Loader::SystemContentIsTrinityMode() ? 1 : 0;
}
static int KYTY_SYSV_ABI KernelGetOperationMode(int* mode, int* submode) {
PRINT_NAME();
*mode = 2; // PS5 Base
*mode = Loader::SystemContentIsTrinityMode() ? 3 : 2;
*submode = 0; // None
LOGF("\t mode = %d, submode = %d\n", *mode, *submode);
return OK;
}
+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_REQUEST_ID_OFFSET = 0x10000000;
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_REQUEST_MAX = -2141912315; /* 0x80550305 */
constexpr int NP_AUTH_ERROR_REQUEST_NOT_FOUND = -2141912314; /* 0x80550306 */
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 */
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 {
None = 0,
Ready,
@@ -1822,7 +1844,7 @@ static int np_auth_wait_or_poll_async(int req_id, int* result) {
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) {
request->result = 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->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) {
@@ -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);
}
static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* param,
void* auth_code, int* issuer_id) {
static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(
int req_id, const NpAuthGetAuthorizationCodeParameterV3* param, void* auth_code,
int* issuer_id) {
PRINT_NAME();
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 issuer_id = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(issuer_id));
if (auth_code != nullptr) {
memset(auth_code, 0, 136);
if (req_id <= 0 || param == nullptr || auth_code == nullptr) {
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) {
*issuer_id = 0;
}
@@ -1877,46 +1910,35 @@ static int KYTY_SYSV_ABI NpAuthGetAuthorizationCodeV3(int req_id, const void* pa
if (request == nullptr) {
return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
if (request->state == NpAuthRequestState::Aborted) {
// 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);
return np_auth_complete_unavailable(request, param->user_id);
}
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();
LOGF("\t req_id = %d\n", req_id);
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));
if (id_token != nullptr) {
memset(id_token, 0, 4104);
if (req_id <= 0 || param == nullptr || id_token == nullptr) {
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);
auto* request = np_auth_find_request(req_id);
if (request == nullptr) {
return NP_AUTH_ERROR_REQUEST_NOT_FOUND;
}
if (request->state == NpAuthRequestState::Aborted) {
// 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);
return np_auth_complete_unavailable(request, param->user_id);
}
LIB_DEFINE(InitNet_1_NpAuth) {
+283 -256
View File
@@ -1,349 +1,376 @@
#include "graphics/guest_gpu/pm4.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/shader/shaderBindings.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include "loader/systemContent.h"
#include <atomic>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <unordered_map>
namespace Libs {
namespace LibPsml {
namespace Libs::LibPsml {
LIB_VERSION("Psml", 1, "Psml", 1, 1);
namespace Psml {
namespace {
constexpr int PSML_ERROR_NOT_INITIALIZED = static_cast<int>(0x8a810001u);
constexpr int PSML_ERROR_INVALID_OBJECT = static_cast<int>(0x8a810005u);
constexpr int PSML_ERROR_INVALID_POINTER = static_cast<int>(0x8a810009u);
constexpr int PSML_ERROR_INVALID_VALUE = static_cast<int>(0x8a81000du);
constexpr int PSML_ERROR_NULL_OBJECT = static_cast<int>(0x8a810014u);
constexpr int NOT_INITIALIZED = static_cast<int>(0x8a810001u);
constexpr int ALREADY_INITIALIZED = static_cast<int>(0x8a810002u);
constexpr int TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
constexpr int INVALID_CONTEXT = static_cast<int>(0x8a810005u);
constexpr int COMMAND_BUFFER_FULL = static_cast<int>(0x8a810006u);
constexpr int COMMAND_BUFFER_BAD = static_cast<int>(0x8a810007u);
constexpr int INSUFFICIENT_BUFFER = static_cast<int>(0x8a810008u);
constexpr int ARGUMENT_NULL = static_cast<int>(0x8a810009u);
constexpr int TEXTURE_NULL = static_cast<int>(0x8a81000au);
constexpr int INVALID_TEXTURE = static_cast<int>(0x8a81000bu);
constexpr int INVALID_PARAMETER = static_cast<int>(0x8a81000du);
constexpr int UNSUPPORTED_RENDER_SIZE = static_cast<int>(0x8a81000eu);
constexpr int INVALID_OUTPUT_TEXTURE = static_cast<int>(0x8a81000fu);
constexpr int NOT_TRINITY_MODE = static_cast<int>(0x8a810012u);
constexpr int UNSUPPORTED_OUTPUT_SIZE = static_cast<int>(0x8a810013u);
constexpr int INVALID_SHARED_RESOURCES = static_cast<int>(0x8a810014u);
constexpr int TOO_MANY_SHARED_RESOURCES = static_cast<int>(0x8a810015u);
constexpr uint64_t MEMORY_BLOCK_SIZE = 0x200000;
constexpr uint32_t PACKET_DWORDS = 1 + Graphics::Pssr::RequestDwords;
constexpr uint32_t SHARED_RESOURCES_MAGIC = 0xa9c4;
constexpr uint32_t CONTEXT_MAGIC = 0x9231;
constexpr uint64_t MAIN_MEMORY_BLOCK_SIZE = 0x200000;
constexpr uint64_t MAIN_MEMORY_ALIGNMENT = 0x200000;
constexpr uint64_t EXTRA_VA_BYTES = 0x600000;
struct MainMemoryRequirements {
struct MemoryRequirements {
uint64_t block_size;
uint64_t alignment;
uint64_t block_count;
};
struct MainMemoryParameters {
uint32_t type;
uint32_t reserved;
};
struct DirectMemoryBlock {
uint64_t address;
uint64_t offset;
uint64_t size;
};
struct SharedResourcesInitParameters {
uint32_t type;
uint32_t reserved;
struct MemoryParameters {
uint32_t profile;
uint32_t flags;
const DirectMemoryBlock* blocks;
uint64_t block_count;
uint64_t virtual_address_start;
};
static_assert(sizeof(MainMemoryRequirements) == 24);
static_assert(sizeof(DirectMemoryBlock) == 16);
struct SharedParameters {
MemoryParameters memory;
uint64_t virtual_address;
};
static bool g_initialized = false;
static uint32_t g_supported_modes = 0x3;
static std::atomic<int32_t> g_shared_ref_count = 0;
struct ContextParameters {
MemoryParameters memory;
uint16_t width;
uint16_t height;
uint32_t reserved;
const void* depth_copy;
const void* motion_copy;
const char* name;
void* shared;
};
static_assert(offsetof(ContextParameters, width) == 0x18);
static_assert(offsetof(ContextParameters, shared) == 0x38);
static uint64_t BaseVaBytes(uint32_t type) {
// The native implementation indexes this table before adding one 6 MiB range.
switch (type) {
case 0: return 0x06200000;
case 1: return 0x18200000;
case 2: return 0x12200000;
default: return 0;
struct SharedResources {
uint32_t contexts = 0;
};
struct Context {
uint32_t width;
uint32_t height;
SharedResources* shared;
};
struct CommandSpace {
uint32_t* cmd;
uint32_t dwords;
};
std::mutex g_mutex;
bool g_initialized = false;
std::unique_ptr<SharedResources> g_shared;
std::unordered_map<const void*, std::unique_ptr<Context>> g_contexts;
uint64_t SharedBlockCount(uint32_t profile) {
constexpr uint64_t bytes[] = {0x06200000, 0x18200000, 0x12200000};
return profile < 3 ? (bytes[profile] + 0x600000) / MEMORY_BLOCK_SIZE : 0;
}
int CheckContext(const void* context) {
if (!g_initialized) {
return NOT_INITIALIZED;
}
return g_contexts.contains(context) ? OK : INVALID_CONTEXT;
}
static bool IsSupportedType(uint32_t type) {
switch (type) {
case 0: return true;
case 1: return (g_supported_modes & 0x1u) != 0;
case 2: return (g_supported_modes & 0x2u) != 0;
default: return false;
int KYTY_SYSV_ABI Initialize() {
std::lock_guard lock(g_mutex);
if (!Loader::SystemContentIsTrinityMode()) {
return NOT_TRINITY_MODE;
}
}
static uint64_t RequiredBlockCount(uint32_t type) {
const auto base = BaseVaBytes(type);
if (base == 0) {
return 0;
if (g_initialized) {
return ALREADY_INITIALIZED;
}
return (base + EXTRA_VA_BYTES) / MAIN_MEMORY_BLOCK_SIZE;
}
static int CheckInitialized() {
return (g_initialized ? OK : PSML_ERROR_NOT_INITIALIZED);
}
static void MarkSharedResources(void* resources, const SharedResourcesInitParameters& params) {
auto* bytes = static_cast<uint8_t*>(resources);
*reinterpret_cast<uint32_t*>(bytes) = SHARED_RESOURCES_MAGIC;
*reinterpret_cast<const DirectMemoryBlock**>(bytes + 0x08) = params.blocks;
*reinterpret_cast<uint64_t*>(bytes + 0x18) = params.block_count;
*reinterpret_cast<uint32_t*>(bytes + 0x20) = params.type;
*reinterpret_cast<uint64_t*>(bytes + 0x28) = params.virtual_address_start;
}
static bool HasKnownObjectMagic(const void* object) {
if (object == nullptr) {
return false;
}
const auto magic = *static_cast<const uint32_t*>(object);
return magic == SHARED_RESOURCES_MAGIC || magic == CONTEXT_MAGIC;
}
static int KYTY_SYSV_ABI PsmlInitialize() {
PRINT_NAME();
g_initialized = true;
g_supported_modes = 0x3;
g_shared_ref_count = 0;
g_initialized = true;
return OK;
}
static int KYTY_SYSV_ABI PsmlGetMainMemoryRequirements(MainMemoryRequirements* out,
const MainMemoryParameters* params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI GetSharedRequirements(MemoryRequirements* out, const MemoryParameters* params) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (out == nullptr || params == nullptr) {
return PSML_ERROR_INVALID_POINTER;
return ARGUMENT_NULL;
}
if (!IsSupportedType(params->type)) {
return PSML_ERROR_INVALID_VALUE;
const auto blocks = SharedBlockCount(params->profile);
if (blocks == 0) {
return INVALID_PARAMETER;
}
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
out->alignment = MAIN_MEMORY_ALIGNMENT;
out->block_count = RequiredBlockCount(params->type);
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, blocks};
return OK;
}
static int KYTY_SYSV_ABI
PsmlSharedResourcesInitialize(void* resources, const SharedResourcesInitParameters* params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI CreateSharedResources(void** out, const SharedParameters* params) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (resources == nullptr || params == nullptr || params->blocks == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (out == nullptr || params == nullptr || params->memory.blocks == nullptr) {
return ARGUMENT_NULL;
}
if (params->reserved != 0 || !IsSupportedType(params->type) ||
params->block_count < RequiredBlockCount(params->type)) {
return PSML_ERROR_INVALID_VALUE;
const auto required = SharedBlockCount(params->memory.profile);
if (required == 0 || params->memory.flags != 0) {
return INVALID_PARAMETER;
}
MarkSharedResources(resources, *params);
g_shared_ref_count.fetch_add(1, std::memory_order_relaxed);
if (params->memory.block_count < required) {
return INSUFFICIENT_BUFFER;
}
if (g_shared) {
return TOO_MANY_SHARED_RESOURCES;
}
g_shared = std::make_unique<SharedResources>();
*out = g_shared.get();
return OK;
}
static int KYTY_SYSV_ABI PsmlSharedResourcesFinalize(void* resources) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI ReleaseSharedResources(void* resources) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (resources == nullptr) {
return PSML_ERROR_NULL_OBJECT;
if (!g_shared || resources != g_shared.get()) {
return INVALID_SHARED_RESOURCES;
}
g_shared_ref_count.fetch_sub(1, std::memory_order_relaxed);
if (g_shared->contexts != 0) {
return INVALID_CONTEXT;
}
g_shared.reset();
return OK;
}
static int KYTY_SYSV_ABI PsmlGetContextMemoryRequirements(MainMemoryRequirements* out,
uint64_t /*params*/) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI GetContextRequirements(MemoryRequirements* out, const void* params) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (out == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (out == nullptr || params == nullptr) {
return ARGUMENT_NULL;
}
out->block_size = MAIN_MEMORY_BLOCK_SIZE;
out->alignment = MAIN_MEMORY_ALIGNMENT;
out->block_count = 1;
*out = {MEMORY_BLOCK_SIZE, MEMORY_BLOCK_SIZE, 1};
return OK;
}
static int KYTY_SYSV_ABI PsmlContextInitialize(void* context, const void* params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int CreateContext(void** out, const MemoryParameters* memory, uint32_t width, uint32_t height,
void* shared, bool use_shared) {
std::lock_guard lock(g_mutex);
if (!g_initialized) {
return NOT_INITIALIZED;
}
if (context == nullptr || params == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (out == nullptr || memory == nullptr || memory->blocks == nullptr) {
return ARGUMENT_NULL;
}
auto* bytes = static_cast<uint8_t*>(context);
auto* params_bytes = static_cast<const uint8_t*>(params);
auto* shared_resources = *reinterpret_cast<void* const*>(params_bytes + 0x08);
if (shared_resources == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (memory->profile > 2) {
return INVALID_PARAMETER;
}
*reinterpret_cast<uint32_t*>(bytes) = CONTEXT_MAGIC;
*reinterpret_cast<void**>(bytes + 0x360) = shared_resources;
*reinterpret_cast<uint8_t*>(bytes + 0x368) = 0;
if (memory->block_count == 0) {
return INSUFFICIENT_BUFFER;
}
if (width == 0 || height == 0 || width > 16384 || height > 16384) {
return UNSUPPORTED_OUTPUT_SIZE;
}
if (use_shared && (!g_shared || shared != g_shared.get())) {
return INVALID_SHARED_RESOURCES;
}
if (g_contexts.size() == 4) {
return TOO_MANY_CONTEXTS;
}
auto record =
std::make_unique<Context>(Context {width, height, use_shared ? g_shared.get() : nullptr});
auto* handle = record.get();
g_contexts.emplace(handle, std::move(record));
if (handle->shared != nullptr) {
++handle->shared->contexts;
}
*out = handle;
return OK;
}
static int KYTY_SYSV_ABI PsmlContextFinalize(void* context) {
PRINT_NAME();
int KYTY_SYSV_ABI CreateFixedContext(void** out, const MemoryParameters* params) {
const bool large = params != nullptr && params->profile == 1;
return CreateContext(out, params, large ? 7680 : 3840, large ? 4320 : 2160, nullptr, false);
}
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI CreatePrivateContext(void** out, const ContextParameters* params) {
return CreateContext(out, params == nullptr ? nullptr : &params->memory,
params == nullptr ? 0 : params->width,
params == nullptr ? 0 : params->height, nullptr, false);
}
int KYTY_SYSV_ABI CreateSharedContext(void** out, const ContextParameters* params) {
return CreateContext(
out, params == nullptr ? nullptr : &params->memory, params == nullptr ? 0 : params->width,
params == nullptr ? 0 : params->height, params == nullptr ? nullptr : params->shared, true);
}
int KYTY_SYSV_ABI ReleaseContext(void* context) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (!HasKnownObjectMagic(context)) {
return PSML_ERROR_INVALID_OBJECT;
auto& record = g_contexts.at(context);
if (record->shared != nullptr) {
--record->shared->contexts;
}
*static_cast<uint32_t*>(context) = 0;
g_contexts.erase(context);
return OK;
}
static int KYTY_SYSV_ABI PsmlGetWorkAreaSize(const void* object, uint32_t* out_size) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
int KYTY_SYSV_ABI GetPacketSize(const void* context, uint32_t* out_size) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (out_size == nullptr) {
return PSML_ERROR_INVALID_POINTER;
return ARGUMENT_NULL;
}
*out_size = 0x600;
*out_size = PACKET_DWORDS;
return OK;
}
static int KYTY_SYSV_ABI PsmlDispatch(void* context, uint64_t command, uint64_t params) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI Dispatch(const void* context, const CommandSpace* space, const void* params) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (!HasKnownObjectMagic(context)) {
return PSML_ERROR_INVALID_OBJECT;
if (space == nullptr || params == nullptr || space->cmd == nullptr) {
return ARGUMENT_NULL;
}
if (command == 0 || params == 0) {
return PSML_ERROR_INVALID_POINTER;
if (space->dwords < PACKET_DWORDS) {
return COMMAND_BUFFER_FULL;
}
if (space->dwords > 0x4001) {
return COMMAND_BUFFER_BAD;
}
const uint32_t* descriptors[2];
std::memcpy(descriptors, params, sizeof(descriptors));
if (descriptors[0] == nullptr || descriptors[1] == nullptr) {
return TEXTURE_NULL;
}
Graphics::Pssr::Request request {};
std::memcpy(request.output, descriptors[0], sizeof(request.output));
std::memcpy(request.input, descriptors[1], sizeof(request.input));
std::memcpy(&request.width, static_cast<const uint8_t*>(params) + 0xf0, sizeof(uint32_t) * 2);
Graphics::ShaderTextureResource input, output;
std::memcpy(input.fields, request.input, sizeof(input.fields));
std::memcpy(output.fields, request.output, sizeof(output.fields));
namespace Gpu = Graphics::Prospero;
const auto is_2d = [](const Graphics::ShaderTextureResource& descriptor) {
return !descriptor.IsNull() &&
(descriptor.Type() == Gpu::ImageType::kColor2D ||
descriptor.Type() == Gpu::ImageType::kColor2DArray) &&
descriptor.TileMode() != Gpu::TileMode::kDepth &&
descriptor.BaseLevel() <= descriptor.LastLevel() &&
descriptor.LastLevel() <= descriptor.MaxMip() &&
descriptor.BaseArray5() <= descriptor.Depth();
};
if (!is_2d(input) || (input.Format() != Gpu::BufferFormat::k11_11_10Float &&
input.Format() != Gpu::BufferFormat::k10_11_11Float &&
input.Format() != Gpu::BufferFormat::k16_16_16_16Float)) {
return INVALID_TEXTURE;
}
if (!is_2d(output) || output.Format() != Gpu::BufferFormat::k11_11_10Float ||
output.TileMode() != Gpu::TileMode::kStandard256B || output.BaseLevel() != 0 ||
output.LastLevel() != 0 || output.MaxMip() != 0 || output.BaseArray5() != 0 ||
output.Depth() != 0 || input.Base40() == output.Base40()) {
return INVALID_OUTPUT_TEXTURE;
}
const uint32_t input_width = std::max(1u, (input.Width5() + 1u) >> input.BaseLevel());
const uint32_t input_height = std::max(1u, (input.Height5() + 1u) >> input.BaseLevel());
if (request.width == 0 || request.height == 0 || request.width > input_width ||
request.height > input_height) {
return UNSUPPORTED_RENDER_SIZE;
}
const auto& record = *g_contexts.at(context);
if (uint32_t(output.Width5()) + 1 != record.width ||
uint32_t(output.Height5()) + 1 != record.height) {
return INVALID_OUTPUT_TEXTURE;
}
namespace Pm4 = Graphics::Pm4;
space->cmd[0] = KYTY_PM4(space->dwords, Pm4::IT_NOP, Pm4::R_PSSR);
std::memcpy(space->cmd + 1, &request, sizeof(request));
std::memset(space->cmd + PACKET_DWORDS, 0, (space->dwords - PACKET_DWORDS) * sizeof(uint32_t));
return OK;
}
static int KYTY_SYSV_ABI PsmlGetProgress(const void* object, float* out_progress,
uint32_t /*frame_index*/) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
int KYTY_SYSV_ABI GetMipmapBias(const void* context, float* bias, uint32_t width, uint32_t height) {
std::lock_guard lock(g_mutex);
if (const int error = CheckContext(context); error != OK) {
return error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
if (bias == nullptr) {
return ARGUMENT_NULL;
}
if (out_progress == nullptr) {
return PSML_ERROR_INVALID_POINTER;
if (width == 0 || height == 0) {
return UNSUPPORTED_RENDER_SIZE;
}
*out_progress = 0.0f;
*bias = std::log2(float(width) / float(g_contexts.at(context)->width)) - 1.0f;
return OK;
}
static int KYTY_SYSV_ABI PsmlRequestCapture(const void* object) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
}
return OK;
int KYTY_SYSV_ABI ValidateContext(const void* context) {
std::lock_guard lock(g_mutex);
return CheckContext(context);
}
static int KYTY_SYSV_ABI PsmlValidateObject(const void* object) {
PRINT_NAME();
const auto init_error = CheckInitialized();
if (init_error != OK) {
return init_error;
}
if (!HasKnownObjectMagic(object)) {
return PSML_ERROR_INVALID_OBJECT;
}
return OK;
}
} // namespace Psml
} // namespace
LIB_DEFINE(InitPsml_1) {
LIB_FUNC("3WVD91e12ZQ", Psml::PsmlInitialize);
LIB_FUNC("+2KpvixvL6E", Psml::PsmlGetMainMemoryRequirements);
LIB_FUNC("eWoKNeB6V-k", Psml::PsmlSharedResourcesInitialize);
LIB_FUNC("jEevBXmagOQ", Psml::PsmlSharedResourcesFinalize);
LIB_FUNC("EO9YQXmJEN8", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("IMlj247LdTo", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("sjTyYheKTrU", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("ArakEpzsZo0", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("VGjrQa-WqdU", Psml::PsmlGetContextMemoryRequirements);
LIB_FUNC("2ecEbQaf9VU", Psml::PsmlContextInitialize);
LIB_FUNC("vUk2pWMx3KQ", Psml::PsmlContextInitialize);
LIB_FUNC("gxv3i+MTEzU", Psml::PsmlContextInitialize);
LIB_FUNC("JaLBe0P3jSU", Psml::PsmlContextFinalize);
LIB_FUNC("AHalTX9wFZY", Psml::PsmlGetWorkAreaSize);
LIB_FUNC("RUNLFro+qok", Psml::PsmlDispatch);
LIB_FUNC("GHna9-DvnUk", Psml::PsmlGetProgress);
LIB_FUNC("GJY0MvuTcs8", Psml::PsmlRequestCapture);
LIB_FUNC("LXq+6mIxpCw", Psml::PsmlValidateObject);
LIB_FUNC("FSGaTQze0UY", Psml::PsmlValidateObject);
LIB_FUNC("3WVD91e12ZQ", Initialize);
LIB_FUNC("+2KpvixvL6E", GetSharedRequirements);
LIB_FUNC("eWoKNeB6V-k", CreateSharedResources);
LIB_FUNC("jEevBXmagOQ", ReleaseSharedResources);
LIB_FUNC("EO9YQXmJEN8", GetContextRequirements);
LIB_FUNC("IMlj247LdTo", GetContextRequirements);
LIB_FUNC("sjTyYheKTrU", GetContextRequirements);
LIB_FUNC("ArakEpzsZo0", GetContextRequirements);
LIB_FUNC("VGjrQa-WqdU", GetContextRequirements);
LIB_FUNC("2ecEbQaf9VU", CreateFixedContext);
LIB_FUNC("vUk2pWMx3KQ", CreatePrivateContext);
LIB_FUNC("gxv3i+MTEzU", CreateSharedContext);
LIB_FUNC("JaLBe0P3jSU", ReleaseContext);
LIB_FUNC("AHalTX9wFZY", GetPacketSize);
LIB_FUNC("RUNLFro+qok", Dispatch);
LIB_FUNC("GHna9-DvnUk", GetMipmapBias);
LIB_FUNC("GJY0MvuTcs8", ValidateContext);
LIB_FUNC("LXq+6mIxpCw", ValidateContext);
LIB_FUNC("FSGaTQze0UY", ValidateContext);
}
} // namespace LibPsml
} // namespace Libs
} // namespace Libs::LibPsml
+70 -46
View File
@@ -4002,6 +4002,11 @@ struct NpCountryCode {
char padding[1];
};
struct NpLanguageCode {
char code[36];
char padding[12];
};
struct NpAgeRestriction {
NpCountryCode country_code;
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_not_found = -2141913068; /* 0x80550014 */
constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */
constexpr int np_error_network_not_available = -2141872086; /* 0x8055a02a */
constexpr int np_request_max = 128;
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 {
NpStateCallbackA callback = nullptr;
@@ -4117,7 +4137,7 @@ static NpRequest* np_get_request_locked(int req_id) {
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) {
request->result = 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->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() {
@@ -4144,7 +4164,9 @@ int KYTY_SYSV_ABI NpCheckCallback() {
}
}
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;
@@ -4239,12 +4261,13 @@ int KYTY_SYSV_ABI NpGetNpId(int user_id, NpId* np_id) {
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));
// return OK;
if (IsSignedIn(user_id)) {
std::memcpy(np_id->handle.data, "Kyty", 4);
return OK;
}
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);
// 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));
// return OK;
if (IsSignedIn(user_id)) {
std::memcpy(online_id->data, "Kyty", 4);
return OK;
}
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);
// *account_id = 0x00000000feedfaceull;
*account_id = 0;
// return OK;
if (IsSignedIn(user_id)) {
*account_id = 0x00000000feedfaceull;
return OK;
}
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);
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;
}
@@ -4305,11 +4334,14 @@ int KYTY_SYSV_ABI NpGetAccountAge(int req_id, int user_id, uint8_t* age) {
return np_error_invalid_argument;
}
// *age = 21;
*age = 0;
std::lock_guard lock(g_np_request_mutex);
auto* request = np_get_request_locked(req_id);
if (request == nullptr) {
return np_error_request_not_found;
}
// return OK;
return np_error_signed_out;
*age = IsSignedIn(user_id) ? 21 : 0;
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) {
@@ -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);
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_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() {
@@ -4428,8 +4466,9 @@ int KYTY_SYSV_ABI NpCheckNpAvailability(int req_id, const char* user, void* resu
return np_error_request_not_found;
}
// return OK;
return np_complete_signed_out_locked(request);
return np_complete_locked(request, std::strcmp(user, "Kyty") == 0
? np_reachability_result(Config::GetUserId())
: np_error_signed_out);
}
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 OK;
return np_complete_signed_out_locked(request);
return np_complete_locked(request, np_reachability_result(user_id));
}
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;
}
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));
// result->authorized = true;
// request->state = NpRequestState::Complete;
// request->result = OK;
// return OK;
return np_complete_signed_out_locked(request);
// The simulated account has no premium eligibility.
return np_complete_locked(request, np_reachability_result(param->user_id));
}
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);
*state = np_state_signed_out;
*state = IsSignedIn(user_id) ? np_state_signed_in : np_state_signed_out;
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);
// *state = 2; // SCE_NP_REACHABILITY_STATE_REACHABLE
*state = 0; // SCE_NP_REACHABILITY_STATE_UNAVAILABLE
*state = np_reachability_result(user_id) == OK ? 2 : 0; // REACHABLE / UNAVAILABLE
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);
*has_signed_up = false;
*has_signed_up = IsSignedIn(user_id);
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);
bool IsSignedIn(int user_id);
int KYTY_SYSV_ABI NpCheckCallback();
int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret);
int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction);
+177 -24
View File
@@ -546,6 +546,7 @@ struct Ngs2VoiceInternal {
};
struct Module {
std::vector<uint8_t> param, work, state;
std::vector<float> channel_matrix;
void KYTY_SYSV_ABI (*handler)(Ngs2UserFxProcessContext*) = nullptr;
std::array<uintptr_t, 3> user_data {};
uint32_t flags = 1;
@@ -563,9 +564,13 @@ struct Ngs2VoiceInternal {
std::vector<std::vector<float>> matrices;
std::vector<Module> modules;
std::vector<float> samples;
std::vector<float> mix_samples;
std::vector<float> output_matrix;
uint32_t channels = 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 rendered = false;
bool has_samples = false;
@@ -614,6 +619,27 @@ struct Ngs2VoiceInternal {
return samples.data() + size_t(id) * grain * channels;
}
void MixChannels(const std::vector<float>& matrix, uint32_t buffer, uint32_t grain) {
if (matrix.empty()) {
return;
}
EXIT_NOT_IMPLEMENTED(channels > 37);
auto* input = Buffer(buffer, grain);
mix_samples.assign(size_t(channels) * grain, 0.0f);
for (uint32_t dst = 0; dst < channels; ++dst) {
for (uint32_t src = 0; src < channels; ++src) {
const float level = matrix[dst * 37 + src];
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) {
mix_samples[dst * grain + i] += input[src * grain + i] * level;
}
}
}
std::copy(mix_samples.begin(), mix_samples.end(), input);
}
void ResetSetupState() {
SetEvent(4);
std::ranges::fill(ports, Port {});
@@ -687,10 +713,11 @@ struct Ngs2VoiceInternal {
if (count > capacity) {
return NGS2_ERROR_INVALID_NUM_WAVEFORM_BLOCKS;
}
EXIT_NOT_IMPLEMENTED(!accepts_blocks || (flags & ~0x17u) != 0);
const bool clear = (flags & 4u) != 0;
const bool only_data = (flags & 2u) != 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();
Block tail = retained == 0 ? Block {} : blocks.back();
std::vector<Block> pending;
@@ -1777,6 +1804,18 @@ static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) {
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,
uint32_t grain) {
if (voice.rendered) {
@@ -1825,7 +1864,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
EXIT_NOT_IMPLEMENTED(port.input >= voice.rack->NumBuffers());
const auto* input = source->Buffer(source->rack->PortBufferId(p), 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);
if (matrix != nullptr && matrix->empty()) {
matrix = nullptr;
}
const size_t output_channels =
matrix == nullptr
? voice.channels
@@ -1834,7 +1877,8 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
for (uint32_t src = 0; src < source->channels; ++src) {
const float level =
port.volume * (matrix == nullptr
? (src == dst ? 1.0f : 0.0f)
? Ngs2DefaultMatrixLevel(source->channels, src,
voice.channels, dst)
: (*matrix)[dst * source->channels + src]);
for (uint32_t i = 0; i < grain; ++i) {
output[dst * grain + i] += input[src * grain + i] * level;
@@ -1844,12 +1888,21 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
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) {
const auto& custom = voice.rack->option.custom_sampler.custom_rack_option;
const auto rate = voice.rack->ngs->option.sample_rate;
for (size_t m = 0; m < voice.modules.size(); ++m) {
const auto& info = custom.module[m];
auto& module = voice.modules[m];
if (info.module_id == 0x17) {
voice.MixChannels(module.channel_matrix, info.source_buffer_id, grain);
continue;
}
if (info.module_id != 0x19 && info.module_id != 0x1f) {
if (!module.render_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf(
@@ -1934,7 +1987,8 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
const auto& output = buffer_info[voice->output_id];
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
EXIT_NOT_IMPLEMENTED((output.waveform_type != 0x12 && output.waveform_type != 0x18) ||
EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x12 && output.waveform_type != 0x18);
EXIT_NOT_IMPLEMENTED(voice->rack->type == Ngs2RackType::Mastering &&
output.num_channels != voice->channels);
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels *
(output.waveform_type == 0x12 ? sizeof(int16_t)
@@ -1950,10 +2004,16 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
pcm = static_cast<float*>(output.buffer);
}
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) {
const float level = voice->output_matrix.empty()
? (src == dst ? 1.0f : 0.0f)
: voice->output_matrix.at(src * output.num_channels + dst);
const float level =
gain * (voice->output_matrix.empty()
? static_cast<float>(src == dst)
: voice->output_matrix.at(src * output.num_channels + dst));
if (level == 0.0f) {
continue;
}
for (uint32_t i = 0; i < grain; ++i) {
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
}
@@ -2308,6 +2368,17 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i
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)
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) {
PRINT_NAME();
@@ -2377,7 +2448,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size());
voice->ports[patch->port].dest =
reinterpret_cast<Ngs2VoiceInternal*>(patch->dest_handle);
Ngs2FindPatchDestination(patch->dest_handle);
voice->ports[patch->port].input = patch->dest_input_id;
LOGF("\t connect->port = %u\n"
"\t connect->dest_input_id = %u\n"
@@ -2425,7 +2496,31 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
}
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: {
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering);
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
@@ -2439,6 +2534,12 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
matrix->levels + matrix->num_levels);
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;
default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id);
}
@@ -2452,7 +2553,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
voice->rack->option.custom_sampler.custom_rack_option.module[index].module_id;
auto& module = voice->modules[index];
if ((param->id & 0xffffffe0u) != (0x40000000u | module_id << 8u) ||
(module_id != 0x19 && module_id != 0x1f)) {
(module_id != 0x17 && module_id != 0x19 && module_id != 0x1f)) {
if (!module.control_warned) {
Log::WriteToConsoleAndLog(fmt::sprintf(
"warning: NGS2 custom control 0x%08" PRIx32 " is ignored\n",
@@ -2461,6 +2562,16 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
}
break;
}
if (module_id == 0x17) {
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceParamHeader) +
37 * 37 * sizeof(float));
const auto* levels = reinterpret_cast<const float*>(param + 1);
module.channel_matrix.resize(37 * 37);
std::transform(levels, levels + module.channel_matrix.size(),
module.channel_matrix.begin(),
[](float level) { return std::clamp(level, -32.0f, 32.0f); });
break;
}
if (module_id == 0x19) {
struct FxParam {
Ngs2VoiceParamHeader header;
@@ -2660,24 +2771,22 @@ int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* stat
submixer->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::CustomMastering: {
const auto configured_size =
voice->rack->option.custom_mastering.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomMasteringVoiceState));
EXIT_NOT_IMPLEMENTED(state_size != configured_size);
std::memset(state, 0, state_size);
auto* mastering = reinterpret_cast<Ngs2CustomMasteringVoiceState*>(state);
mastering->voice_state.state_flags = Ngs2GetStateFlags(voice);
break;
}
case Ngs2RackType::CustomMastering:
case Ngs2RackType::CustomSampler: {
const bool sampler = voice->rack->type == Ngs2RackType::CustomSampler;
const auto configured_size =
voice->rack->option.custom_sampler.custom_rack_option.state_size;
EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomSamplerVoiceState));
EXIT_NOT_IMPLEMENTED(state_size != configured_size);
std::memset(state, 0, state_size);
auto* sampler = reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state);
Ngs2FillSamplerState(*voice, *sampler);
EXIT_NOT_IMPLEMENTED(configured_size < (sampler ? sizeof(Ngs2CustomSamplerVoiceState)
: sizeof(Ngs2CustomMasteringVoiceState)));
if (state_size < configured_size) {
return static_cast<int32_t>(0x804a8011u);
}
std::memset(state, 0, configured_size);
if (sampler) {
Ngs2FillSamplerState(*voice, *reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state));
} else {
state->state_flags = Ngs2GetStateFlags(voice);
}
break;
}
case Ngs2RackType::Sampler: {
@@ -2722,6 +2831,50 @@ int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state
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) {
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,
size_t state_size);
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,
uint32_t num_buffer_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/common.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
#include "common/singleton.h"
@@ -90,6 +91,7 @@ private:
};
struct SystemContent {
bool trinity_mode = false;
std::filesystem::path psf_path;
Psf psf;
std::filesystem::path playgo_path;
@@ -603,6 +605,14 @@ void SystemContentLoadParamSfo(const std::filesystem::path& file_name) {
sc->psf_path = file_name;
sc->psf.DbgPrint();
int32_t attribute3 = 0;
const bool title_supports_trinity = sc->psf.GetParamInt("ATTRIBUTE3", &attribute3) &&
(static_cast<uint32_t>(attribute3) & (1u << 22u)) != 0;
sc->trinity_mode = Config::Ps5ProEnabled() && title_supports_trinity;
if (Config::Ps5ProEnabled()) {
Log::WriteToConsoleAndLog(fmt::format("[PS5] title Trinity support={}\n",
title_supports_trinity ? "yes" : "no"));
}
sc->icon_path = file_name.parent_path() / "icon0.png";
@@ -626,6 +636,10 @@ void SystemContentLoadParamSfo(const std::filesystem::path& file_name) {
}
}
bool SystemContentIsTrinityMode() {
return Common::Singleton<SystemContent>::Instance()->trinity_mode;
}
bool SystemContentParamSfoGetInt(const char* name, int32_t* value) {
if (name == nullptr || value == nullptr) {
return false;
+1
View File
@@ -11,6 +11,7 @@
namespace Loader {
void SystemContentLoadParamSfo(const std::filesystem::path& file_name);
bool SystemContentIsTrinityMode();
bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
bool SystemContentParamSfoGetString(const char* name, std::string* value);
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
+5
View File
@@ -74,11 +74,14 @@ static void PrintUsage() {
" --stretch Stretch the image to the window. Default: off.\n");
::printf(" --no-splash Disable the startup splash screen.\n");
::printf(" --vr Enable the virtual VR headset.\n");
::printf(" --trinity Enable experimental PS5 Pro mode for supported titles\n"
" (PSSR uses linear scaling). Default: off.\n");
::printf(" --amd-cpu Apply AMD CPU instruction patches.\n");
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
::printf(" --console-language <0-29> Console language. Default: 1 (English US).\n");
::printf(" --vulkan-validation Enable Vulkan validation.\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"
" Implies --vulkan-validation; very slow.\n");
::printf(" --shader-validation Enable shader validation.\n");
@@ -204,8 +207,10 @@ static constexpr Flag FLAGS[] = {
{"--stretch", &ConfigOptions::stretch_enabled},
{"--no-splash", &ConfigOptions::splash_enabled, false},
{"--vr", &ConfigOptions::vr_enabled},
{"--trinity", &ConfigOptions::pro_enabled},
{"--amd-cpu", &ConfigOptions::amd_cpu_enabled},
{"--network-connected", &ConfigOptions::network_connected},
{"--emulate-psn", &ConfigOptions::psn_emulation_enabled},
{"--vulkan-validation", &ConfigOptions::vulkan_validation_enabled},
{"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled},
{"--shader-validation", &ConfigOptions::shader_validation_enabled},
+360
View File
@@ -0,0 +1,360 @@
#include "libs/ajm.cpp"
#include <array>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <opus.h>
using namespace Libs::Audio::Ajm;
namespace {
void Check(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "AjmResampleTests: %s\n", message);
std::abort();
}
}
struct DecodeResult {
AjmSidebandResult result;
AjmSidebandStream stream;
AjmSidebandMFrame frames;
};
struct Fixture {
uint32_t instance = 0;
uint32_t channels;
std::array<uint8_t, 4096> batch_data {};
AjmBatchInfo batch {};
explicit Fixture(uint32_t channels = 1, AjmSampleEncoding encoding = AjmSampleEncoding::S16,
uint64_t flags = 0x1000)
: channels(channels) {
Check(AjmInstanceCreate(1, 24, flags | channels | (uint64_t(encoding) << 7), &instance) ==
OK,
"create Opus instance");
Initialize();
}
~Fixture() { AjmInstanceDestroy(1, instance); }
AjmBatchInfo* Batch() {
batch = {batch_data.data(), 0, batch_data.size(), nullptr, nullptr};
return &batch;
}
void Initialize() {
AjmDecOpusInitializeParameters params {channels, 48000, 0};
AjmSidebandResult result {};
AjmBatchJobInitialize(Batch(), instance, &params, sizeof(params), &result);
Check(result.result == OK, "initialize Opus instance");
}
void Parameters(float ratio, uint32_t flags, float change = 0, bool extended = false) {
AjmSidebandResult result {};
if (extended) {
AjmBatchJobSetResampleParametersEx(Batch(), instance, ratio, change, flags, &result);
} else {
AjmBatchJobSetResampleParameters(Batch(), instance, ratio, flags, &result);
}
Check(result.result == OK, "set resampling parameters");
}
AjmSidebandResampleInfo Info() {
struct {
AjmSidebandResult result;
AjmSidebandResampleInfo info;
} result {};
AjmBatchJobGetResampleInfo(Batch(), instance, &result);
Check(result.result.result == OK, "get resampling info");
return result.info;
}
void Gapless(uint32_t samples, uint16_t skip) {
AjmSidebandGaplessDecode params {samples, skip, 0};
AjmSidebandResult result {};
AjmBatchJobSetGaplessDecode(Batch(), instance, &params, 1, &result);
Check(result.result == OK, "set gapless parameters");
}
DecodeResult Decode(const void* input, size_t size, void* output, size_t capacity) {
DecodeResult result {};
AjmBatchJobDecode(Batch(), instance, input, size, output, capacity, &result);
Check((result.result.result & ~(AJM_RESULT_NOT_ENOUGH_ROOM | AJM_RESULT_PARTIAL_INPUT)) ==
0,
"decode returned unexpected error");
return result;
}
};
std::vector<uint8_t> Encode(uint32_t channels, int packets) {
int error = 0;
auto* encoder = opus_encoder_create(48000, channels, OPUS_APPLICATION_AUDIO, &error);
Check(encoder != nullptr && error == OPUS_OK, "create test encoder");
std::vector<uint8_t> stream;
std::vector<int16_t> pcm(960 * channels);
std::array<uint8_t, 4096> packet;
for (int p = 0; p < packets; ++p) {
for (size_t i = 0; i < pcm.size(); ++i) {
pcm[i] = static_cast<int16_t>(
12000 * std::sin((i / channels + p * 960) * 0.041 + (i % channels)));
}
const int size = opus_encode(encoder, pcm.data(), 960, packet.data(), packet.size());
Check(size > 0, "encode test packet");
stream.push_back(static_cast<uint8_t>(size));
stream.push_back(static_cast<uint8_t>(size >> 8));
stream.insert(stream.end(), packet.begin(), packet.begin() + size);
}
opus_encoder_destroy(encoder);
return stream;
}
void SmallOutputs(const std::vector<uint8_t>& packet, uint16_t skip = 0) {
Fixture f;
f.Gapless(960 - skip, skip);
f.Parameters(1, 3);
std::array<uint8_t, 1030> output;
output.fill(0x5a);
auto first = f.Decode(packet.data(), packet.size(), output.data(), 1024);
Check(first.stream.size_consumed == static_cast<int>(packet.size()),
"small output must consume packet");
Check(first.stream.size_produced == 1024, "small output must produce 1024 bytes");
Check(first.frames.num_frames == 1 && first.stream.total_decoded_samples == 960 - skip,
"decoded frame counted once before resampling");
Check(f.Info().num_samples == 448 - skip, "report retained source samples");
Check(std::all_of(output.begin() + 1024, output.end(), [](auto v) { return v == 0x5a; }),
"do not write past output capacity");
const auto last = f.Decode(nullptr, 0, output.data(), 1024);
Check(last.result.result == OK && last.stream.size_consumed == 0 &&
last.stream.size_produced == 896 - skip * 2 && last.frames.num_frames == 0 &&
last.stream.total_decoded_samples == first.stream.total_decoded_samples,
"END drains retained PCM without decoding or reporting partial input");
Check(f.Decode(nullptr, 0, output.data(), 1024).stream.size_produced == 0,
"END drains only once");
}
void UnityMatchesWholeDecode(uint32_t channels, AjmSampleEncoding encoding) {
const auto stream = Encode(channels, 6);
const auto sample_bytes = channels * AjmBytesPerSample(encoding);
Fixture reference(channels, encoding, 0);
reference.Gapless(5328, 312);
std::vector<uint8_t> expected(5760 * sample_bytes);
const auto full =
reference.Decode(stream.data(), stream.size(), expected.data(), expected.size());
expected.resize(full.stream.size_produced);
Check(expected.size() == 5328 * sample_bytes, "reference gapless length");
Fixture f(channels, encoding);
f.Gapless(5328, 312);
f.Parameters(1, 3);
std::vector<uint8_t> actual;
std::array<uint8_t, 1025> output;
size_t offset = 0;
uint32_t frames = 0;
for (int i = 0; i < 100; ++i) {
const auto decoded = f.Decode(offset < stream.size() ? stream.data() + offset : nullptr,
stream.size() - offset, output.data(), output.size());
offset += decoded.stream.size_consumed;
frames += decoded.frames.num_frames;
actual.insert(actual.end(), output.begin(), output.begin() + decoded.stream.size_produced);
if (decoded.stream.size_produced == 0) {
break;
}
}
Check(actual == expected && frames == 6 && offset == stream.size(),
"chunked PCM must exactly match whole decode, without loss or duplication");
}
void ChunkedRates(const std::vector<uint8_t>& stream, uint32_t samples, uint16_t skip) {
for (float ratio: {0.125f, 0.749153f, 1.334840f}) {
Fixture reference;
reference.Gapless(samples, skip);
reference.Parameters(ratio, 3);
std::vector<uint8_t> expected(
(static_cast<size_t>(std::ceil(samples / double(ratio))) + 1) * 2);
const auto full =
reference.Decode(stream.data(), stream.size(), expected.data(), expected.size());
expected.resize(full.stream.size_produced);
Check(expected.size() == static_cast<size_t>(std::ceil(samples / double(ratio))) * 2,
"fractional resampling rounds up final output sample count");
Fixture f;
f.Gapless(samples, skip);
f.Parameters(ratio, 1);
std::vector<uint8_t> actual;
std::array<uint8_t, 1024> output;
size_t offset = 0;
for (int i = 0; i < 1000; ++i) {
if (offset == stream.size()) {
// The caller can signal END only after submitting the last packet.
f.Parameters(0, 2 | 8);
}
const auto decoded = f.Decode(offset < stream.size() ? stream.data() + offset : nullptr,
stream.size() - offset, output.data(), output.size());
offset += decoded.stream.size_consumed;
actual.insert(actual.end(), output.begin(),
output.begin() + decoded.stream.size_produced);
if (decoded.stream.size_produced == 0) {
break;
}
}
Check(actual == expected && offset == stream.size(),
"non-unity PCM is independent of output chunk boundaries and deferred END");
}
}
void LoopBoundaries() {
const auto loop = Encode(1, 2);
auto stream = loop;
stream.insert(stream.end(), loop.begin(), loop.end());
for (uint64_t flags: {0x1400u, 0x3400u}) {
Fixture f(1, AjmSampleEncoding::S16, flags);
f.Gapless(1500, 100);
f.Parameters(1, 1);
std::array<uint8_t, 8192> pcm;
const auto first = f.Decode(stream.data(), stream.size(), pcm.data(), 1024);
Check(first.stream.size_produced == 1024, "loop first partial output");
const auto second =
f.Decode(stream.data() + first.stream.size_consumed,
stream.size() - first.stream.size_consumed, pcm.data(), pcm.size());
const bool seamless = flags == 0x3400;
Check(second.stream.size_produced == (seamless ? 6000 - 1024 : 3000 - 1024),
"non-seamless loop stops job at stream boundary");
const auto consumed = first.stream.size_consumed + second.stream.size_consumed;
Check(consumed == static_cast<int>(seamless ? stream.size() : loop.size()),
"loop boundary input consumption");
if (!seamless) {
Check(
f.Decode(stream.data() + consumed, stream.size() - consumed, pcm.data(), pcm.size())
.stream.size_produced == 3000,
"next job resumes gapless loop");
}
}
}
void RatesAndControls(const std::vector<uint8_t>& packet) {
for (float ratio: {0.125f, 0.5f, 1.0f, 1.25f, 2.0f, 4.0f}) {
Fixture f;
f.Parameters(ratio, 3);
std::vector<int16_t> pcm(8000);
const auto result = f.Decode(packet.data(), packet.size(), pcm.data(), pcm.size() * 2);
Check(result.stream.size_produced == static_cast<int>(std::ceil(960.0 / ratio)) * 2,
"resampling ratio controls output duration");
Check(result.stream.total_decoded_samples == 960,
"ratio does not change decoded sample count");
}
Fixture f;
f.Parameters(1, 1);
std::array<int16_t, 512> pcm;
f.Decode(packet.data(), packet.size(), pcm.data(), sizeof(pcm));
f.Initialize();
Check(f.Info().num_samples == 448, "codec initialize must preserve resampler PCM");
f.Parameters(0, 0);
const auto frozen = f.Decode(nullptr, 0, pcm.data(), sizeof(pcm));
Check(
frozen.stream.size_produced == sizeof(pcm) && f.Info().num_samples == 448 &&
std::all_of(pcm.begin(), pcm.end(), [&](auto sample) { return sample == pcm.front(); }),
"zero ratio repeats one sample without advancing input");
f.Parameters(1, 0, 0.25f, true);
f.Parameters(std::numeric_limits<float>::quiet_NaN(), 8);
f.Decode(nullptr, 0, pcm.data(), 4 * sizeof(int16_t));
Check(f.Info().ratio == 2.0f, "ignore preserves ratio ramp");
f.Parameters(1, 1);
Check(f.Info().num_samples == 0, "resample RESET discards retained PCM");
Check(f.Decode(nullptr, 0, pcm.data(), sizeof(pcm)).stream.size_produced == 0,
"RESET leaves no stale audio");
}
void UnbufferedAndSplit(const std::vector<uint8_t>& packet) {
Fixture raw(1, AjmSampleEncoding::S16, 0);
std::array<uint8_t, 1024> pcm;
const auto too_small = raw.Decode(packet.data(), packet.size(), pcm.data(), pcm.size());
Check(too_small.result.result == AJM_RESULT_NOT_ENOUGH_ROOM &&
too_small.stream.size_consumed == 0 && too_small.stream.size_produced == 0,
"ordinary decoding still requires room for a whole packet");
Fixture f;
f.Parameters(1, 3);
AjmBuffer input[] {{const_cast<uint8_t*>(packet.data()), 1},
{const_cast<uint8_t*>(packet.data()) + 1, packet.size() - 1}};
AjmBuffer output[] {{pcm.data(), 257}, {pcm.data() + 257, 767}};
DecodeResult result {};
AjmBatchJobDecodeSplit(f.Batch(), f.instance, input, 2, output, 2, &result);
Check(result.stream.size_produced == 1024 &&
result.stream.size_consumed == static_cast<int>(packet.size()),
"split buffers retain PCM across jobs");
}
void FormatChangeRequiresReset() {
Fixture f(2);
f.Parameters(1, 1);
const auto stereo = Encode(2, 1);
std::array<uint8_t, 4096> pcm;
f.Decode(stereo.data(), stereo.size(), pcm.data(), 1024);
f.channels = 1;
f.Initialize();
DecodeResult result {};
AjmBatchJobDecode(f.Batch(), f.instance, nullptr, 0, pcm.data(), pcm.size(), &result);
Check(result.result.result == AJM_RESULT_INVALID_PARAMETER && result.stream.size_produced == 0,
"format change cannot reinterpret retained PCM with a different channel stride");
f.Parameters(1, 3);
const auto mono = Encode(1, 1);
Check(f.Decode(mono.data(), mono.size(), pcm.data(), pcm.size()).stream.size_produced == 1920,
"resampler RESET permits a new stream format");
}
void EndPersistsUntilReset(const std::vector<uint8_t>& packet) {
Fixture f;
f.Parameters(0.5f, 1);
std::array<int16_t, 64> pcm;
auto decoded = f.Decode(packet.data(), packet.size(), pcm.data(), sizeof(pcm));
size_t produced = decoded.stream.size_produced;
f.Parameters(0.5f, 2);
for (int i = 0; i < 40; ++i) {
f.Parameters(0.5f, 0);
decoded = f.Decode(nullptr, 0, pcm.data(), sizeof(pcm));
produced += decoded.stream.size_produced;
Check((decoded.result.result & AJM_RESULT_PARTIAL_INPUT) == 0,
"END remains active across subsequent ratio controls");
if (f.Info().num_samples < 0) {
break;
}
}
Check(produced == 3840 && f.Info().num_samples < 0,
"fully drained END reports a negative retained sample count");
f.Parameters(1, 1);
Check(f.Info().num_samples == 0, "RESET clears end-of-stream state");
}
} // namespace
int main(int argc, char** argv) {
const auto packet = Encode(1, 1);
SmallOutputs(packet);
SmallOutputs(packet, 312);
for (uint32_t channels: {1u, 2u}) {
for (auto encoding:
{AjmSampleEncoding::S16, AjmSampleEncoding::S32, AjmSampleEncoding::Float}) {
UnityMatchesWholeDecode(channels, encoding);
}
}
RatesAndControls(packet);
UnbufferedAndSplit(packet);
ChunkedRates(Encode(1, 6), 5328, 312);
LoopBoundaries();
FormatChangeRequiresReset();
EndPersistsUntilReset(packet);
if (argc >= 2) {
std::ifstream file(argv[1], std::ios::binary);
std::vector<uint8_t> real_packet((std::istreambuf_iterator<char>(file)), {});
Check(real_packet.size() == 209, "expected real 209-byte packet fixture");
SmallOutputs(real_packet, 312);
}
if (argc >= 3) {
std::ifstream file(argv[2], std::ios::binary);
std::vector<uint8_t> real_stream((std::istreambuf_iterator<char>(file)), {});
Check(real_stream.size() == 4048, "expected complete real stream fixture");
ChunkedRates(real_stream, 25920, 312);
}
std::puts("AjmResampleTests: passed");
}
+283 -5
View File
@@ -73,6 +73,10 @@ namespace Libs::LibNpCommerce {
void InitNet_1_NpCommerce(Loader::SymbolDatabase *symbols);
}
namespace Libs::LibNpAuth {
void InitNet_1_NpAuth(Loader::SymbolDatabase *symbols);
}
namespace Libs::LibNpEntitlementAccess {
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 {
Libs::LibKernel::PthreadMutex *mutex;
Libs::LibKernel::KernelUseconds timeout = 0;
@@ -573,17 +588,16 @@ void TestAddContent(const std::filesystem::path& root) {
Check(mount(0, &info[3].label, &point) == OK &&
!std::string_view(point.data).ends_with('/'),
"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 int fd = FileSystem::KernelOpen(mounted_file.c_str(), 0, 0);
std::array<char, 11> payload {};
Check(fd >= 3 && FileSystem::KernelRead(fd, payload.data(), payload.size()) == payload.size() &&
std::string_view(payload.data(), payload.size()) == "DLC payload" &&
FileSystem::KernelClose(fd) == OK,
"guest reads files from the selected title's add-on");
MountPoint repeated {};
Check(mount(0, &info[3].label, &repeated) == OK &&
std::strcmp(repeated.data, point.data) == 0,
"duplicate mount returns the same mount point");
"guest reads the original mount after a rejected duplicate mount");
Check(unmount(&point) == OK && FileSystem::GetRealFilename(mounted_file).empty() &&
unmount(&point) == static_cast<int>(0x80d90004u),
"unmount removes mapping and repeated unmount reports NOT_MOUNTED");
@@ -2082,6 +2096,256 @@ void TestNetCtlHostIpv4() {
}
#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() {
Loader::SymbolDatabase symbols;
Libs::LibNet::InitNet_1_Net(&symbols);
@@ -2458,7 +2722,19 @@ int main(int argc, char** argv) {
Config::Load(options);
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();
TestStaticRwlockDestroy();
if (argc == 2 && std::string_view(argv[1]) == "--mutex-only") {
subsystems.Destroy();
std::printf("KernelFileSystemTests: mutex cases passed\n");
@@ -2503,6 +2779,8 @@ int main(int argc, char** argv) {
#if defined(__linux__)
TestNetCtlHostIpv4();
#endif
TestNpEmulation();
TestNpAuthEmulation();
TestNpWebApi2Memory();
TestNpCommerceDialog();
graphics.reset();
+262 -19
View File
@@ -751,6 +751,26 @@ void TestCustomSideChainUserFx() {
"user FX did not receive the side-chain buffer after the source channels");
}
struct RenderRack {
std::unique_ptr<std::byte[]> storage;
uintptr_t handle = 0;
RenderRack(Ngs2Internal& system, uint32_t id, const Ngs2RackOption& option) {
Ngs2ContextBufferInfo buffer {};
Check(Ngs2RackQueryBufferSize(id, &option, &buffer) == OK, "rack size query failed");
storage = std::make_unique<std::byte[]>(buffer.host_buffer_size);
buffer.host_buffer = storage.get();
Check(Ngs2RackCreate(reinterpret_cast<uintptr_t>(&system), id, &option, &buffer,
&handle) == OK, "render rack creation failed");
}
~RenderRack() { Check(Ngs2RackDestroy(handle, nullptr) == OK, "render rack cleanup failed"); }
Ngs2VoiceInternal& Voice() {
return *reinterpret_cast<Ngs2VoiceInternal*>(reinterpret_cast<Ngs2RackInternal*>(handle) + 1);
}
};
void TestPcm16RenderOutput() {
Ngs2Internal system;
system.option = Ngs2DefaultSystemOption();
@@ -760,23 +780,11 @@ void TestPcm16RenderOutput() {
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
sampler_option.common.max_voices = 2;
sampler_option.common.max_ports = 1;
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
std::byte sampler_storage[sizeof(Ngs2RackInternal) + 2 * sizeof(Ngs2VoiceInternal)];
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
std::byte mastering_storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
Ngs2ContextBufferInfo sampler_buffer {sampler_storage, sizeof(sampler_storage)};
Ngs2ContextBufferInfo mastering_buffer {mastering_storage, sizeof(mastering_storage)};
uintptr_t sampler_handle = 0, mastering_handle = 0;
RenderRack sampler(system, 0x1000, sampler_option.common);
RenderRack master(system, 0x3000, mastering_option.common);
const auto system_handle = reinterpret_cast<uintptr_t>(&system);
Check(Ngs2RackCreate(system_handle, 0x1000, &sampler_option.common, &sampler_buffer,
&sampler_handle) == OK &&
Ngs2RackCreate(system_handle, 0x3000, &mastering_option.common,
&mastering_buffer, &mastering_handle) == OK,
"PCM16 render rack creation failed");
auto* samplers = reinterpret_cast<Ngs2VoiceInternal*>(
reinterpret_cast<Ngs2RackInternal*>(sampler_handle) + 1);
auto& mastering = *reinterpret_cast<Ngs2VoiceInternal*>(
reinterpret_cast<Ngs2RackInternal*>(mastering_handle) + 1);
auto* samplers = &sampler.Voice();
auto& mastering = master.Voice();
mastering.channels = 1;
mastering.state = Ngs2VoicePlayState::Playing;
const int16_t source[2][4] = {{16384, -16384, 32767, -32768},
@@ -797,9 +805,174 @@ void TestPcm16RenderOutput() {
Check(Ngs2SystemRender(system_handle, &output, 1) == OK &&
std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }),
"PCM16 output retained samples from the previous render");
Check(Ngs2RackDestroy(mastering_handle, nullptr) == OK &&
Ngs2RackDestroy(sampler_handle, nullptr) == OK,
"PCM16 render rack cleanup failed");
}
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() {
Ngs2Internal system;
system.option = Ngs2DefaultSystemOption();
system.option.num_grain_samples = 6;
Ngs2RackOptionUnion sampler_option {};
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
sampler_option.common.max_voices = 1;
sampler_option.common.max_ports = 1;
Ngs2CustomMasteringRackOption master_option {};
auto& custom = master_option.custom_rack_option;
custom.rack_option.size = sizeof(master_option);
custom.rack_option.max_voices = 1;
custom.rack_option.max_voice_channels = 6;
custom.num_buffers = 1;
custom.num_modules = 1;
custom.module[0].module_id = 0x17;
RenderRack sampler(system, 0x1000, sampler_option.common);
RenderRack master(system, 0x4003, custom.rack_option);
auto& source = sampler.Voice();
auto& output = master.Voice();
output.channels = 6;
output.SetEvent(1);
struct {
Ngs2VoiceParamHeader header {5484, 0, 0x40001700};
float levels[37][37] {};
} matrix;
constexpr float center = 0.70710677f;
matrix.levels[0][0] = matrix.levels[1][1] = 1.0f;
matrix.levels[0][2] = matrix.levels[1][2] = center;
matrix.levels[0][4] = matrix.levels[1][5] = center;
int16_t impulses[6][6] {};
for (uint32_t c = 0; c < 6; ++c) impulses[c][c] = 8192;
for (unsigned pass = 0; pass < 2; ++pass) {
if (pass == 1) {
std::memset(matrix.levels, 0, sizeof(matrix.levels));
matrix.levels[0][1] = 64.0f;
matrix.levels[1][0] = -64.0f;
}
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&output), &matrix.header) == OK,
"custom channel mixer control failed");
source.SetupSampler({0x12, 6, 48000});
source.blocks.push_back({reinterpret_cast<const uint8_t*>(impulses),
{0, sizeof(impulses), 0, 0, 6}});
source.ports[0].dest = &output;
source.SetEvent(1);
std::array<float, 14> pcm;
pcm.fill(99.0f);
Ngs2RenderBufferInfo buffer {pcm.data() + 1, 12 * sizeof(float), 0x18, 2};
Check(Ngs2SystemRender(reinterpret_cast<uintptr_t>(&system), &buffer, 1) == OK,
"custom mastering rejected stereo output");
const std::array<float, 12> expected = pass == 0
? std::array<float, 12> {0.25f, 0, 0, 0.25f, center / 4, center / 4,
0, 0, center / 4, 0, 0, center / 4}
: std::array<float, 12> {0, -8, 8, 0};
Check(pcm.front() == 99 && pcm.back() == 99 &&
std::equal(expected.begin(), expected.end(), pcm.begin() + 1),
"custom mixer lost channel routing, in-place input, clamping or output bounds");
}
}
void TestCustomVoiceStateCapacity() {
for (auto type: {Ngs2RackType::CustomSampler, Ngs2RackType::CustomMastering}) {
Fixture f(48000, 1, Ngs2RackType::CustomSampler);
f.rack.type = type;
f.rack.option.custom_sampler.custom_rack_option.state_size = 212;
f.voice.state_flags = 3;
f.voice.decoded_samples = 37;
f.voice.decoded_bytes = 128;
alignas(Ngs2CustomSamplerVoiceState) std::array<uint8_t, 384> storage;
auto* state = reinterpret_cast<Ngs2VoiceState*>(storage.data() + 8);
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
for (size_t capacity: {212u, 368u}) {
storage.fill(0xa5);
Check(Ngs2VoiceGetState(handle, state, capacity) == OK && state->state_flags == 3 &&
state->error_code == 0 &&
std::all_of(storage.begin(), storage.begin() + 8,
[](uint8_t v) { return v == 0xa5; }) &&
std::all_of(storage.begin() + 220, storage.end(),
[](uint8_t v) { return v == 0xa5; }),
"oversized custom state buffer was rejected or overwritten past configured state");
if (type == Ngs2RackType::CustomSampler) {
const auto* sampler = reinterpret_cast<const Ngs2CustomSamplerVoiceState*>(state);
Check(sampler->num_decoded_samples == 37 && sampler->decoded_data_size == 128,
"custom sampler state omitted playback progress");
}
const size_t base_size = type == Ngs2RackType::CustomSampler
? sizeof(Ngs2CustomSamplerVoiceState) : sizeof(Ngs2CustomMasteringVoiceState);
Check(std::all_of(storage.begin() + 8 + base_size, storage.begin() + 220,
[](uint8_t v) { return v == 0; }),
"configured custom state extension was not initialized");
}
storage.fill(0xa5);
Check(Ngs2VoiceGetState(handle, state, 211) == static_cast<int32_t>(0x804a8011u) &&
std::ranges::all_of(storage, [](uint8_t v) { return v == 0xa5; }),
"undersized custom state buffer was accepted or modified");
}
}
void TestPitchLoopAndSkip() {
@@ -923,6 +1096,35 @@ void TestMonoRateAndRouting() {
"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() {
Fixture f(48000, 2);
Ngs2RackInternal mastering_rack {};
@@ -1008,6 +1210,38 @@ void TestDeferredSamplerSetup() {
"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() {
Fixture f(48000);
const int16_t pcm[] = {8192, -16384};
@@ -1700,6 +1934,11 @@ int main() {
TestAtrac9Vibration();
TestAtrac9Discrete();
TestPcm16RenderOutput();
TestPatchToRackHandle();
TestMasteringGain();
TestReverbDryLevel();
TestCustomMasteringChannelMixer();
TestCustomVoiceStateCapacity();
TestSubmixerSetupAndRouting();
TestCustomSideChainUserFx();
TestWaveformBlockGeometry();
@@ -1732,8 +1971,12 @@ int main() {
TestPcmSkipAcrossBlocks();
TestPcmBlocksEndWithTheirData();
TestMonoRateAndRouting();
TestDefaultMatrix();
TestMatrixOutputChannels();
TestCustomPcmStillPlays();
TestClearReopensClosedList();
TestSetupQuery();
TestPortInfo();
TestPlayStateBeforeRender();
TestStatePublication();
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 test;
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() {
TestTrinityBvhInstruction();
TestTrinityStackInstruction();
namespace D = ShaderRecompiler::Decoder;
const std::array code{
0xf1989f01u, 0x00081014u,
File diff suppressed because it is too large. Load diff
+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
int RunTrinityTests();
int main(int argc, char** argv) {
if (argc == 2 && std::strcmp(argv[1], "--trinity-only") == 0) {
return RunTrinityTests();
}
InitSubsystems();
if (argc == 2 && std::strcmp(argv[1], "--memory-bottlenecks-only") == 0) {
RunTest(TestFlexibleZeroingTracksBackingLifetime);
+82
View File
@@ -2397,6 +2397,50 @@ void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() {
CheckSpirvBinaryValidates(result.spirv);
}
void TestVop3FloatConversionDecode() {
namespace Decoder = ShaderRecompiler::Decoder;
const std::pair<uint32_t, Decoder::Opcode> conversions[] = {
{0x18b, Decoder::Opcode::V_CVT_F32_F16},
{0x191, Decoder::Opcode::V_CVT_F32_UBYTE0},
{0x192, Decoder::Opcode::V_CVT_F32_UBYTE1},
{0x193, Decoder::Opcode::V_CVT_F32_UBYTE2},
{0x194, Decoder::Opcode::V_CVT_F32_UBYTE3},
};
for (const auto &[encoding, opcode] : conversions) {
for (const uint32_t source : {261u, 12u, 128u, 255u}) {
const uint32_t code[] = {
EncodeVop3Word0(encoding, 20, 0, 1, true),
EncodeVop3Word1(source, 0, 0) | (2u << 27u) | (1u << 29u),
0xa5a53e00u,
};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == opcode && inst.family == Decoder::Family::VOP3 &&
inst.src_count == 1 && inst.word_count == (source == 255u ? 3u : 2u),
"VOP3 conversion lost its opcode, unary source, or literal length");
Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 20 &&
inst.dst.sdwa_sel == 6 && inst.dst.clamp && inst.dst.omod == 2,
"VOP3 conversion lost its full-width destination or output modifiers");
const bool half = opcode == Decoder::Opcode::V_CVT_F32_F16;
Check(inst.src0.absolute == half && inst.src0.negate == half,
"VOP3 conversion must apply half-float modifiers and ignore byte modifiers");
Decoder::Operand expected_source;
Decoder::DecodeScalarSource(source, 0, expected_source);
Check(inst.src0.kind == expected_source.kind && inst.src0.reg == expected_source.reg &&
(source != 255u || inst.src0.value == code[2]),
"VOP3 conversion changed its source register or literal value");
}
for (const uint32_t op_sel : {1u, 2u, 4u, 8u}) {
const uint32_t code[] = {EncodeVop3Word0(encoding, 20, op_sel),
EncodeVop3Word1(261, 0, 0)};
Decoder::Instruction inst;
Decoder::DecodeInstruction(code, 0, inst);
Check(inst.opcode == Decoder::Opcode::UNSUPPORTED,
"VOP3 conversion accepted unsupported OP_SEL bits");
}
}
}
void TestNewShaderRecompilerMoreAluFamilies() {
const uint32_t shader[] = {
EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7
@@ -10550,6 +10594,43 @@ void TestNewShaderRecompilerNativeU64Translation() {
"captured VOP3 V_LSHLREV_B64 was not present in the decoded dump");
CheckSpirvBinaryValidates(result.spirv);
const uint32_t ashrrev_i64_shader[] = {
0xd7010001u, 0x0000d50cu,
EncodeSopp(0x01),
};
Decoder::Instruction decoded_ashrrev_i64;
ShaderRecompiler::Decoder::DecodeInstruction(ashrrev_i64_shader, 0u,
decoded_ashrrev_i64);
Check(decoded_ashrrev_i64.opcode == Decoder::Opcode::V_ASHRREV_I64 &&
decoded_ashrrev_i64.dst.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_i64.dst.reg == 1u &&
decoded_ashrrev_i64.src_count == 2u &&
decoded_ashrrev_i64.src0.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_i64.src0.reg == 12u &&
decoded_ashrrev_i64.src1.kind == Decoder::OperandKind::VccLo,
"decoder rejected VOP3 V_ASHRREV_I64 fields");
result = RecompileForTest(ashrrev_i64_shader, options);
Check((result.decoded_dump.find("V_ASHRREV_I64 v1, v12, vcc_lo") != std::string::npos),
"VOP3 V_ASHRREV_I64 was not present in the decoded dump");
CheckSpirvBinaryValidates(result.spirv);
const uint32_t ashrrev_captured[] = {0xd7010007u, 0x00020498u, EncodeSopp(0x01)};
Decoder::Instruction decoded_ashrrev_captured;
ShaderRecompiler::Decoder::DecodeInstruction(ashrrev_captured, 0u,
decoded_ashrrev_captured);
Check(decoded_ashrrev_captured.opcode == Decoder::Opcode::V_ASHRREV_I64 &&
decoded_ashrrev_captured.family == Decoder::Family::VOP3 &&
decoded_ashrrev_captured.src_count == 2u &&
decoded_ashrrev_captured.dst.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_captured.dst.reg == 7u &&
decoded_ashrrev_captured.src0.kind == Decoder::OperandKind::IntegerInlineConstant &&
decoded_ashrrev_captured.src0.value == 24u &&
decoded_ashrrev_captured.src1.kind == Decoder::OperandKind::Vgpr &&
decoded_ashrrev_captured.src1.reg == 2u,
"captured VOP3 V_ASHRREV_I64 opcode 0x301 was not decoded");
result = RecompileForTest(ashrrev_captured, options);
CheckSpirvBinaryValidates(result.spirv);
const uint32_t cmpx_i64_shader[] = {
0xd4b5007eu, 0x00020e80u, // v_cmpx_ne_i64 exec, 0, v[7:8]
EncodeSopp(0x01),
@@ -17121,6 +17202,7 @@ int main() {
using namespace Libs::Graphics;
EnsureConfigInitialized();
TestVop3FloatConversionDecode();
TestRayTracingInstructions();
TestResourceDescriptorClassification();
TestShaderBufferResourceSize();