mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-30 01:01:21 +00:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b65dd18224 | ||
|
|
2f6dccb10b | ||
|
|
08a7d7fbed | ||
|
|
2aa0d58ffb | ||
|
|
9bf311c6c7 | ||
|
|
e66a7f52b5 | ||
|
|
67f8276751 | ||
|
|
3ad21e35cc | ||
|
|
5eab0ec738 | ||
|
|
bfd4c34b88 | ||
|
|
6ff6e7551d | ||
|
|
25f3ba4eae | ||
|
|
4592e4c178 | ||
|
|
97bef2992f | ||
|
|
15452a5fee | ||
|
|
651ef73a03 | ||
|
|
5e837a9bea | ||
|
|
a0b99a8c21 | ||
|
|
8cfe1c4d34 | ||
|
|
f71da54b9b | ||
|
|
63207739bc | ||
|
|
143f640706 | ||
|
|
54b937c85f | ||
|
|
22610cb93e | ||
|
|
0386f33bf3 | ||
|
|
138d5b86c4 | ||
|
|
963aaeb3b6 | ||
|
|
77458260f1 | ||
|
|
41de2fd64e | ||
|
|
128c938514 | ||
|
|
a8e3c29b2e | ||
|
|
c528324d27 | ||
|
|
c67352d5d6 | ||
|
|
6c01401862 | ||
|
|
a833e1b735 | ||
|
|
1be1c6c4f9 |
No files matched your search
@@ -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
-2
@@ -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
|
||||
@@ -700,8 +736,6 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME wait_until_safe_for_rendering
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||
add_test(NAME pm4_flip
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||
add_test(NAME depth_bits
|
||||
@@ -764,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -92,12 +91,6 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::Wake() {
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
m_wake_pending = true;
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::ProcessCommands() {
|
||||
EXIT_IF(!IsGpuThread());
|
||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||
@@ -443,10 +436,7 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
}
|
||||
if (selected_queue < 0) {
|
||||
gpu->m_processing = false;
|
||||
// A wake that came before this wait must not be lost.
|
||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
}
|
||||
for (auto& queue: gpu->m_queues) {
|
||||
if (!queue.empty()) {
|
||||
queue.front().blocked = false;
|
||||
@@ -866,9 +856,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// The GPU reads the records if the shaders allow; the VS prolog loads the patched SGPRs from
|
||||
// Vulkan's bases. With no SGPR patched, Vulkan applies the offsets, which matches while they
|
||||
// are zero.
|
||||
// The GPU reads the records if the shaders allow; the VS prolog removes Vulkan's bases
|
||||
// from the index VGPRs and copies them only to the user SGPRs the packet patches.
|
||||
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
@@ -940,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) {
|
||||
@@ -960,11 +954,10 @@ void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
// Stall only this command processor, not the host command lane or other queues.
|
||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index))) {
|
||||
SuspendPm4();
|
||||
}
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
}
|
||||
|
||||
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
||||
|
||||
@@ -31,8 +31,6 @@ public:
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
||||
void Wake();
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> commands);
|
||||
@@ -84,7 +82,6 @@ private:
|
||||
bool m_processing = false;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_wake_pending = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -289,6 +297,7 @@ ClassifyVideoOutCompression(bool compressed, uint64_t metadata_address, uint32_t
|
||||
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_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) {
|
||||
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 ||
|
||||
|
||||
@@ -689,6 +689,7 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||
if (!tess_active && !mesh_active) {
|
||||
vertex_info[0].indirect_offsets = state.indirect_offsets;
|
||||
vertex_info[0].indirect_draw = state.indirect_draw;
|
||||
}
|
||||
if (object_primitive_id) {
|
||||
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
||||
|
||||
@@ -139,6 +139,7 @@ struct DrawRenderState {
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
// Input: the user-data slots a GPU-read indirect draw patches.
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
bool indirect_draw = false;
|
||||
|
||||
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -52,7 +52,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info) {
|
||||
auto vertex_offset = static_cast<int32_t>(index_offset);
|
||||
uint32_t instance_offset = 0;
|
||||
if (!vs_input_info.fetch_embedded) {
|
||||
if (!vs_input_info.has_dynamic_vertex_fetch) {
|
||||
return {vertex_offset, instance_offset};
|
||||
}
|
||||
|
||||
@@ -1251,16 +1251,26 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry
|
||||
// their own offsets.
|
||||
// and quad-list draws for counts, tessellation draws for the index/SGPR prolog. Indirect records
|
||||
// carry their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
int32_t first_vertex,
|
||||
uint32_t first_instance,
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if ((indirect.address != 0 || source == DrawOffsetSource::IndirectArgs) &&
|
||||
ucfg.GetIndexOffset() != 0) {
|
||||
static std::once_flag warning_once;
|
||||
std::call_once(warning_once, [&] {
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"GPU: indirect draw ignores GE_INDX_OFFSET=0x{:08x}; geometry may be incorrect.\n",
|
||||
ucfg.GetIndexOffset()));
|
||||
});
|
||||
}
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
if (!vs.indirect_draw ||
|
||||
vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||
vs.indirect_offsets != indirect.offsets) {
|
||||
return std::nullopt;
|
||||
@@ -1269,7 +1279,7 @@ static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig&
|
||||
}
|
||||
if (source == DrawOffsetSource::IndirectArgs) {
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
return vs.has_dynamic_vertex_fetch ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
}
|
||||
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
|
||||
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
||||
@@ -1326,7 +1336,8 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||
.indirect_draw = args.indirect.address != 0};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1431,7 +1442,8 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||
.indirect_draw = args.indirect.address != 0};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
ResetBindings();
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "gpu_blit_shaders/gpu_pssr_linear_spv.h"
|
||||
#include "graphics/guest_gpu/pssr.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/image.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
struct PssrParameters {
|
||||
uint32_t input_size[2];
|
||||
uint32_t output_size[2];
|
||||
uint32_t output_channels[4];
|
||||
};
|
||||
|
||||
bool IsInputFormat(Prospero::BufferFormat format) {
|
||||
return format == Prospero::BufferFormat::k11_11_10Float ||
|
||||
format == Prospero::BufferFormat::k10_11_11Float ||
|
||||
format == Prospero::BufferFormat::k16_16_16_16Float;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderExecutor::~RenderExecutor() {
|
||||
const auto device = m_context.GetGraphics().device;
|
||||
device.destroyPipeline(m_pssr_pipeline, nullptr);
|
||||
device.destroyPipelineLayout(m_pssr_pipeline_layout, nullptr);
|
||||
device.destroyDescriptorSetLayout(m_pssr_descriptor_layout, nullptr);
|
||||
}
|
||||
|
||||
void RenderExecutor::ProcessPssr(CommandBuffer& buffer, const Pssr::Request& request) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
ShaderTextureResource input_descriptor {}, output_descriptor {};
|
||||
std::copy_n(request.input, 8, input_descriptor.fields);
|
||||
std::copy_n(request.output, 8, output_descriptor.fields);
|
||||
const auto is_2d = [](const ShaderTextureResource& descriptor) {
|
||||
return !descriptor.IsNull() &&
|
||||
(descriptor.Type() == Prospero::ImageType::kColor2D ||
|
||||
descriptor.Type() == Prospero::ImageType::kColor2DArray) &&
|
||||
descriptor.TileMode() != Prospero::TileMode::kDepth;
|
||||
};
|
||||
EXIT_IF(!is_2d(input_descriptor) || !is_2d(output_descriptor) ||
|
||||
!IsInputFormat(input_descriptor.Format()) ||
|
||||
output_descriptor.Format() != Prospero::BufferFormat::k11_11_10Float ||
|
||||
output_descriptor.TileMode() != Prospero::TileMode::kStandard256B ||
|
||||
output_descriptor.BaseLevel() != 0 || output_descriptor.LastLevel() != 0 ||
|
||||
output_descriptor.MaxMip() != 0 || output_descriptor.BaseArray5() != 0 ||
|
||||
output_descriptor.Depth() != 0 || request.width == 0 || request.height == 0);
|
||||
const auto input_width = std::max(1u, (input_descriptor.Width5() + 1u) >>
|
||||
input_descriptor.BaseLevel());
|
||||
const auto input_height = std::max(1u, (input_descriptor.Height5() + 1u) >>
|
||||
input_descriptor.BaseLevel());
|
||||
EXIT_IF(request.width > input_width || request.height > input_height);
|
||||
PssrParameters parameters {{request.width, request.height},
|
||||
{output_descriptor.Width5() + 1u, output_descriptor.Height5() + 1u},
|
||||
{4, 4, 4, 4}};
|
||||
for (uint32_t component = 0; component < 3; ++component) {
|
||||
for (uint32_t source = 0; source < 4; ++source) {
|
||||
if (GetDstSel(output_descriptor.DstSelXYZW(), source) == 4u + component) {
|
||||
parameters.output_channels[component] = source;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto resolve = [&](const uint32_t* words, bool output) {
|
||||
ShaderRecompiler::IR::ImageResource resource {};
|
||||
resource.resource_class = output ? ShaderRecompiler::IR::ImageResourceClass::Storage
|
||||
: ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
||||
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
resource.read = !output;
|
||||
resource.written = output;
|
||||
ShaderRecompiler::IR::DescriptorValue value {};
|
||||
std::copy_n(words, 8, value.dwords.begin());
|
||||
value.dword_count = 8;
|
||||
auto binding = ResolveTexture(resource, value);
|
||||
binding.desc.view_info.level_count = 1;
|
||||
binding.desc.view_info.min_lod = 0;
|
||||
return binding;
|
||||
};
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
auto input = resolve(request.input, false);
|
||||
BindImage(input.image_id, false);
|
||||
auto output = resolve(request.output, true);
|
||||
BindImage(output.image_id, true);
|
||||
EXIT_IF(input.desc.info.data.address < output.desc.info.data.End() &&
|
||||
output.desc.info.data.address < input.desc.info.data.End());
|
||||
const auto acquire = [&](TextureBinding& binding, const uint32_t* words, bool storage) {
|
||||
auto& previous = cache.GetImage(binding.image_id);
|
||||
if (!previous.registered || previous.binding.needs_rebind) {
|
||||
previous.binding = {};
|
||||
binding = resolve(words, storage);
|
||||
BindImage(binding.image_id, storage);
|
||||
}
|
||||
binding.image_view = cache.FindTexture(binding.image_id, binding.desc);
|
||||
};
|
||||
acquire(input, request.input, false);
|
||||
acquire(output, request.output, true);
|
||||
auto& source = cache.GetImage(input.image_id);
|
||||
auto& destination = cache.GetImage(output.image_id);
|
||||
const auto& source_view = input.desc.view_info;
|
||||
const auto& destination_view = output.desc.view_info;
|
||||
EXIT_IF(source.info.IsDepth() || destination.info.IsDepth() ||
|
||||
source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||
(input.image_id == output.image_id &&
|
||||
source_view.base_level == destination_view.base_level &&
|
||||
source_view.base_layer == destination_view.base_layer));
|
||||
|
||||
auto& graphics = m_context.GetGraphics();
|
||||
if (m_pssr_pipeline == nullptr) {
|
||||
const std::array bindings {
|
||||
vk::DescriptorSetLayoutBinding {0, vk::DescriptorType::eSampledImage, 1,
|
||||
vk::ShaderStageFlagBits::eCompute},
|
||||
vk::DescriptorSetLayoutBinding {1, vk::DescriptorType::eStorageImage, 1,
|
||||
vk::ShaderStageFlagBits::eCompute}};
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = bindings.size();
|
||||
descriptor_info.pBindings = bindings.data();
|
||||
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(
|
||||
&descriptor_info, nullptr, &m_pssr_descriptor_layout),
|
||||
"create upscaling descriptor layout");
|
||||
const vk::PushConstantRange push {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(PssrParameters)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_pssr_descriptor_layout;
|
||||
layout_info.pushConstantRangeCount = 1;
|
||||
layout_info.pPushConstantRanges = &push;
|
||||
RequireVulkanSuccess(graphics.device.createPipelineLayout(
|
||||
&layout_info, nullptr, &m_pssr_pipeline_layout),
|
||||
"create upscaling pipeline layout");
|
||||
const auto shader = CompileSPV(GPU_PSSR_LINEAR_SPV, graphics.device);
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
create.stage.module = shader;
|
||||
create.stage.pName = "main";
|
||||
create.layout = m_pssr_pipeline_layout;
|
||||
const auto result = graphics.device.createComputePipelines(
|
||||
nullptr, 1, &create, nullptr, &m_pssr_pipeline);
|
||||
graphics.device.destroyShaderModule(shader, nullptr);
|
||||
RequireVulkanSuccess(result, "create upscaling pipeline");
|
||||
}
|
||||
buffer.EndRendering();
|
||||
const auto command = buffer.Handle();
|
||||
source.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderRead,
|
||||
ImageSubresourceRange {source_view.base_level, 1, source_view.base_layer, 1},
|
||||
command);
|
||||
destination.Transit(vk::ImageLayout::eGeneral, vk::AccessFlagBits2::eShaderWrite,
|
||||
ImageSubresourceRange {destination_view.base_level, 1,
|
||||
destination_view.base_layer, 1}, command);
|
||||
const std::array images {
|
||||
vk::DescriptorImageInfo {nullptr, input.image_view, vk::ImageLayout::eGeneral},
|
||||
vk::DescriptorImageInfo {nullptr, output.image_view, vk::ImageLayout::eGeneral}};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t i = 0; i < writes.size(); ++i) {
|
||||
writes[i].dstBinding = i;
|
||||
writes[i].descriptorCount = 1;
|
||||
writes[i].descriptorType = i == 0 ? vk::DescriptorType::eSampledImage
|
||||
: vk::DescriptorType::eStorageImage;
|
||||
writes[i].pImageInfo = &images[i];
|
||||
}
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pssr_pipeline_layout,
|
||||
0, writes.size(), writes.data());
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_pssr_pipeline);
|
||||
command.pushConstants(m_pssr_pipeline_layout, vk::ShaderStageFlagBits::eCompute,
|
||||
0, sizeof(parameters), ¶meters);
|
||||
command.dispatch((parameters.output_size[0] + 7) / 8,
|
||||
(parameters.output_size[1] + 7) / 8, 1);
|
||||
ShaderAccessBarrier(command, vk::PipelineStageFlagBits::eComputeShader);
|
||||
ResetBindings();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,33 @@
|
||||
#version 450 core
|
||||
#extension GL_EXT_samplerless_texture_functions : require
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
layout(binding = 0) uniform texture2D source;
|
||||
layout(binding = 1) writeonly uniform image2D destination;
|
||||
layout(push_constant) uniform Parameters {
|
||||
uvec2 input_size;
|
||||
uvec2 output_size;
|
||||
uvec4 output_channels;
|
||||
} parameters;
|
||||
|
||||
vec4 fetch_color(ivec2 coordinate) {
|
||||
return texelFetch(source, clamp(coordinate, ivec2(0), ivec2(parameters.input_size) - 1), 0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec2 output_coordinate = gl_GlobalInvocationID.xy;
|
||||
if (any(greaterThanEqual(output_coordinate, parameters.output_size))) return;
|
||||
vec2 input_coordinate = (vec2(output_coordinate) + 0.5) *
|
||||
vec2(parameters.input_size) / vec2(parameters.output_size) - 0.5;
|
||||
ivec2 lower = ivec2(floor(input_coordinate));
|
||||
vec2 weight = fract(input_coordinate);
|
||||
vec4 color = mix(mix(fetch_color(lower), fetch_color(lower + ivec2(1, 0)), weight.x),
|
||||
mix(fetch_color(lower + ivec2(0, 1)), fetch_color(lower + ivec2(1, 1)), weight.x),
|
||||
weight.y);
|
||||
vec4 stored = vec4(0.0);
|
||||
for (uint channel = 0; channel < 3; ++channel) {
|
||||
uint selected = parameters.output_channels[channel];
|
||||
if (selected < 4) stored[channel] = color[selected];
|
||||
}
|
||||
imageStore(destination, ivec2(output_coordinate), stored);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>(
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
|
||||
private:
|
||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||
@@ -240,7 +240,6 @@ private:
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
void SignalDoneLocked();
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
Common::Mutex m_mutex;
|
||||
@@ -251,7 +250,6 @@ private:
|
||||
std::list<Request> m_cpu_requests;
|
||||
std::list<Request> m_cancelled_requests;
|
||||
bool m_processing = false;
|
||||
bool m_gpu_waiting = false;
|
||||
uint64_t m_next_request_id = 1;
|
||||
};
|
||||
|
||||
@@ -955,7 +953,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
}
|
||||
}
|
||||
}
|
||||
SignalDoneLocked();
|
||||
m_done_cond_var.SignalAll();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||
@@ -1035,7 +1033,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
it = queue->erase(it);
|
||||
}
|
||||
}
|
||||
SignalDoneLocked();
|
||||
m_done_cond_var.SignalAll();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_submit_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
@@ -1207,24 +1205,17 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::SignalDoneLocked() {
|
||||
m_done_cond_var.SignalAll();
|
||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||
if (m_gpu_waiting) {
|
||||
m_gpu_waiting = false;
|
||||
m_presenter.Renderer().GetGpu().Wake();
|
||||
}
|
||||
}
|
||||
|
||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto has_request = [this, &cfg, index] {
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
m_gpu_waiting = !safe;
|
||||
return safe;
|
||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
};
|
||||
while (has_request()) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
bool FlipQueue::Flip(uint32_t micros) {
|
||||
@@ -1320,7 +1311,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_processing = false;
|
||||
SignalDoneLocked();
|
||||
m_done_cond_var.SignalAll();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
@@ -1703,12 +1694,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
auto* ctx = m_impl->Get(handle);
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
||||
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot(int handle);
|
||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -814,7 +814,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
const auto& cfg = *selected_cfg;
|
||||
Frontend::EmbeddedFetchPlan embedded_fetch;
|
||||
if ((options.stage == ShaderType::Vertex || options.stage == ShaderType::Local) &&
|
||||
options.input_info.vertex->fetch_embedded) {
|
||||
options.input_info.vertex->has_dynamic_vertex_fetch) {
|
||||
embedded_fetch = DetectEmbeddedVertexFetch(
|
||||
decoded, *options.input_info.vertex, options.user_data_base,
|
||||
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
|
||||
|
||||
@@ -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,10 +1026,8 @@ 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);
|
||||
@@ -1072,7 +1070,6 @@ uint32_t LoadIndirectFormattedX(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
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) {
|
||||
|
||||
@@ -30,11 +30,13 @@ uint32_t TessellationPointer(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
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 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)));
|
||||
};
|
||||
IR::U32 quads;
|
||||
if (is64) {
|
||||
const auto source = ReadU32Pair(inst.src0);
|
||||
const auto quads = ir.BitwiseOr(
|
||||
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})})));
|
||||
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,21 +1388,17 @@ 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.
|
||||
// only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged;
|
||||
// it does not make the record's bases zero.
|
||||
const auto& offsets = options.input_info.vertex->indirect_offsets;
|
||||
const auto index = [&](uint8_t slot, IR::StageInputKind kind,
|
||||
IR::StageInputKind base_kind) {
|
||||
if (offsets == ShaderIndirectOffsets {}) {
|
||||
if (!options.input_info.vertex->indirect_draw) {
|
||||
return builtin(kind);
|
||||
}
|
||||
const auto base = builtin(base_kind);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -291,8 +291,7 @@ static bool ShaderGetStaticVertexInputInfo(uint64_t shader_addr, const HW::UserS
|
||||
const auto& metadata = data.vertex;
|
||||
|
||||
if (metadata.vertex_attrib_reg >= 0) {
|
||||
info.fetch_external = false;
|
||||
info.fetch_embedded = true;
|
||||
info.has_dynamic_vertex_fetch = true;
|
||||
info.fetch_attrib_reg = metadata.vertex_attrib_reg;
|
||||
info.fetch_buffer_reg = metadata.vertex_buffer_reg;
|
||||
|
||||
@@ -413,7 +412,7 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
|
||||
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
|
||||
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.clear();
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_embedded));
|
||||
key.push_back(static_cast<uint32_t>(info.has_dynamic_vertex_fetch));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
|
||||
key.push_back(info.resources_num);
|
||||
@@ -422,7 +421,8 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(info.lds_size_dwords);
|
||||
key.push_back(info.pa_cl_vs_out_cntl);
|
||||
key.push_back(info.user_clip_planes);
|
||||
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u));
|
||||
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u) |
|
||||
(uint32_t {info.indirect_draw} << 16u));
|
||||
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
||||
if (info.clip_space.enabled) {
|
||||
for (const float value: info.clip_space.scale) {
|
||||
@@ -693,9 +693,7 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
|
||||
|
||||
LOGF("ShaderDbgDumpInputInfo()\n");
|
||||
|
||||
LOGF("\t fetch_external = %s\n"
|
||||
"\t fetch_embedded = %s\n",
|
||||
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false");
|
||||
LOGF("\t has_dynamic_vertex_fetch = %s\n", info.has_dynamic_vertex_fetch ? "true" : "false");
|
||||
|
||||
for (int i = 0; i < info.resources_num; i++) {
|
||||
LOGF("\t input %d\n", i);
|
||||
|
||||
@@ -125,8 +125,7 @@ struct ShaderTessellationInputInfo {
|
||||
};
|
||||
|
||||
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance
|
||||
// to. Like the hardware index VGPRs, the vertex shader's indices exclude both bases once either
|
||||
// slot is set.
|
||||
// to. A packet can leave both slots unused even when its record contains nonzero offsets.
|
||||
struct ShaderIndirectOffsets {
|
||||
static constexpr uint8_t None = UINT8_MAX;
|
||||
|
||||
@@ -159,8 +158,10 @@ struct ShaderVertexInputInfo {
|
||||
ShaderMeshInputInfo mesh;
|
||||
ShaderTessellationInputInfo tess;
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
bool fetch_external = false;
|
||||
bool fetch_embedded = false;
|
||||
// GPU-read records add Vulkan bases even when the packet patches no user SGPRs.
|
||||
bool indirect_draw = false;
|
||||
// Attribute-table metadata enables dynamic fetch detection; loads are matched later.
|
||||
bool has_dynamic_vertex_fetch = false;
|
||||
};
|
||||
|
||||
struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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, ¶ms,
|
||||
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, ¶ms,
|
||||
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");
|
||||
|
||||
@@ -56,6 +56,12 @@ 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;
|
||||
@@ -65,6 +71,7 @@ struct AjmDecodeResult {
|
||||
uint32_t frames = 0;
|
||||
uint32_t frames_per_packet = 0;
|
||||
AjmSidebandFormat format {};
|
||||
AjmSidebandResampleInfo resample_info {};
|
||||
};
|
||||
|
||||
constexpr int32_t AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
+280
-253
@@ -1,349 +1,376 @@
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/guest_gpu/pssr.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/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;
|
||||
}
|
||||
|
||||
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 CheckContext(const void* context) {
|
||||
if (!g_initialized) {
|
||||
return NOT_INITIALIZED;
|
||||
}
|
||||
return g_contexts.contains(context) ? OK : INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
static uint64_t RequiredBlockCount(uint32_t type) {
|
||||
const auto base = BaseVaBytes(type);
|
||||
if (base == 0) {
|
||||
return 0;
|
||||
int KYTY_SYSV_ABI Initialize() {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (!Loader::SystemContentIsTrinityMode()) {
|
||||
return NOT_TRINITY_MODE;
|
||||
}
|
||||
return (base + EXTRA_VA_BYTES) / MAIN_MEMORY_BLOCK_SIZE;
|
||||
if (g_initialized) {
|
||||
return ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
static int CheckInitialized() {
|
||||
return (g_initialized ? OK : PSML_ERROR_NOT_INITIALIZED);
|
||||
}
|
||||
|
||||
static void MarkSharedResources(void* resources, const SharedResourcesInitParameters& params) {
|
||||
auto* bytes = static_cast<uint8_t*>(resources);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(bytes) = SHARED_RESOURCES_MAGIC;
|
||||
*reinterpret_cast<const DirectMemoryBlock**>(bytes + 0x08) = params.blocks;
|
||||
*reinterpret_cast<uint64_t*>(bytes + 0x18) = params.block_count;
|
||||
*reinterpret_cast<uint32_t*>(bytes + 0x20) = params.type;
|
||||
*reinterpret_cast<uint64_t*>(bytes + 0x28) = params.virtual_address_start;
|
||||
}
|
||||
|
||||
static bool HasKnownObjectMagic(const void* object) {
|
||||
if (object == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto magic = *static_cast<const uint32_t*>(object);
|
||||
return magic == SHARED_RESOURCES_MAGIC || magic == CONTEXT_MAGIC;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI PsmlInitialize() {
|
||||
PRINT_NAME();
|
||||
|
||||
g_initialized = true;
|
||||
g_supported_modes = 0x3;
|
||||
g_shared_ref_count = 0;
|
||||
|
||||
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();
|
||||
|
||||
const auto init_error = CheckInitialized();
|
||||
if (init_error != OK) {
|
||||
return init_error;
|
||||
}
|
||||
if (!HasKnownObjectMagic(context)) {
|
||||
return PSML_ERROR_INVALID_OBJECT;
|
||||
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);
|
||||
}
|
||||
|
||||
*static_cast<uint32_t*>(context) = 0;
|
||||
int KYTY_SYSV_ABI CreatePrivateContext(void** out, const ContextParameters* params) {
|
||||
return CreateContext(out, params == nullptr ? nullptr : ¶ms->memory,
|
||||
params == nullptr ? 0 : params->width,
|
||||
params == nullptr ? 0 : params->height, nullptr, false);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI CreateSharedContext(void** out, const ContextParameters* params) {
|
||||
return CreateContext(
|
||||
out, params == nullptr ? nullptr : ¶ms->memory, params == nullptr ? 0 : params->width,
|
||||
params == nullptr ? 0 : params->height, params == nullptr ? nullptr : params->shared, true);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI ReleaseContext(void* context) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (const int error = CheckContext(context); error != OK) {
|
||||
return error;
|
||||
}
|
||||
auto& record = g_contexts.at(context);
|
||||
if (record->shared != nullptr) {
|
||||
--record->shared->contexts;
|
||||
}
|
||||
g_contexts.erase(context);
|
||||
return OK;
|
||||
}
|
||||
|
||||
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;
|
||||
int KYTY_SYSV_ABI ValidateContext(const void* context) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
return CheckContext(context);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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, ¶ms, 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, ¶ms, 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");
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
#include "common/abi.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "graphics/guest_gpu/pssr.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::LibPsml {
|
||||
void InitPsml_1(Loader::SymbolDatabase *symbols);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool g_trinity = true;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
bool SystemContentIsTrinityMode() { return g_trinity; }
|
||||
} // namespace Loader
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Pm4 = Libs::Graphics::Pm4;
|
||||
namespace Pssr = Libs::Graphics::Pssr;
|
||||
|
||||
constexpr int ERROR_ALREADY_INITIALIZED = static_cast<int>(0x8a810002u);
|
||||
constexpr int ERROR_TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
|
||||
constexpr int ERROR_INVALID_CONTEXT = static_cast<int>(0x8a810005u);
|
||||
constexpr int ERROR_NO_SPACE = static_cast<int>(0x8a810006u);
|
||||
constexpr int ERROR_BAD_COMMAND_BUFFER = static_cast<int>(0x8a810007u);
|
||||
constexpr int ERROR_NULL_ARGUMENT = static_cast<int>(0x8a810009u);
|
||||
constexpr int ERROR_NULL_TEXTURE = static_cast<int>(0x8a81000au);
|
||||
constexpr int ERROR_INVALID_TEXTURE = static_cast<int>(0x8a81000bu);
|
||||
constexpr int ERROR_RENDER_SIZE = static_cast<int>(0x8a81000eu);
|
||||
constexpr int ERROR_OUTPUT_TEXTURE = static_cast<int>(0x8a81000fu);
|
||||
constexpr int ERROR_NOT_TRINITY = static_cast<int>(0x8a810012u);
|
||||
constexpr int ERROR_INVALID_SHARED = static_cast<int>(0x8a810014u);
|
||||
constexpr uint64_t CANARY = 0xfedcba9876543210;
|
||||
|
||||
struct MemoryBlock {
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0x200000;
|
||||
};
|
||||
|
||||
struct SharedParameters {
|
||||
uint32_t profile = 0;
|
||||
uint32_t reserved = 0;
|
||||
const MemoryBlock *blocks = nullptr;
|
||||
uint64_t block_count = 0;
|
||||
uint64_t virtual_address = 0;
|
||||
};
|
||||
|
||||
struct ContextParameters {
|
||||
uint32_t profile = 0;
|
||||
uint32_t reserved = 0;
|
||||
const MemoryBlock *blocks = nullptr;
|
||||
uint64_t block_count = 0;
|
||||
uint16_t width = 960;
|
||||
uint16_t height = 540;
|
||||
uint32_t reserved1 = 0;
|
||||
const void *depth_copy = nullptr;
|
||||
const void *motion_copy = nullptr;
|
||||
const char *name = nullptr;
|
||||
void *shared = nullptr;
|
||||
};
|
||||
static_assert(offsetof(ContextParameters, shared) == 0x38);
|
||||
|
||||
struct CommandSpace {
|
||||
uint32_t *cmd;
|
||||
uint32_t dwords;
|
||||
};
|
||||
|
||||
struct DispatchParameters {
|
||||
const uint32_t *output;
|
||||
const uint32_t *input;
|
||||
std::array<uint8_t, 0xe0> unused{};
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
static_assert(offsetof(DispatchParameters, width) == 0xf0);
|
||||
|
||||
struct GuardedHandle {
|
||||
uint64_t before = CANARY;
|
||||
void *value = nullptr;
|
||||
std::array<uint64_t, 128> after;
|
||||
|
||||
GuardedHandle() { after.fill(CANARY); }
|
||||
|
||||
bool Intact() const {
|
||||
return before == CANARY &&
|
||||
std::all_of(after.begin(), after.end(),
|
||||
[](uint64_t value) { return value == CANARY; });
|
||||
}
|
||||
};
|
||||
|
||||
using Initialize = int(KYTY_SYSV_ABI *)();
|
||||
using CreateShared = int(KYTY_SYSV_ABI *)(void **, const SharedParameters *);
|
||||
using CreateContext = int(KYTY_SYSV_ABI *)(void **, const ContextParameters *);
|
||||
using Release = int(KYTY_SYSV_ABI *)(void *);
|
||||
using GetSize = int(KYTY_SYSV_ABI *)(const void *, uint32_t *);
|
||||
using Dispatch = int(KYTY_SYSV_ABI *)(const void *, const CommandSpace *,
|
||||
const void *);
|
||||
using GetBias = int(KYTY_SYSV_ABI *)(const void *, float *, uint32_t, uint32_t);
|
||||
using Predicate = int(KYTY_SYSV_ABI *)(const void *);
|
||||
|
||||
Loader::SymbolDatabase g_symbols;
|
||||
|
||||
template <typename T> T Function(const char *nid) {
|
||||
const auto *symbol = g_symbols.FindByNid(nid, Loader::SymbolType::Func);
|
||||
EXPECT_NE(symbol, nullptr);
|
||||
return symbol == nullptr ? nullptr : reinterpret_cast<T>(symbol->vaddr);
|
||||
}
|
||||
|
||||
std::array<uint32_t, 8> Texture(uint32_t width, uint32_t height,
|
||||
uint32_t address) {
|
||||
std::array<uint32_t, 8> result{};
|
||||
result[0] = address;
|
||||
result[1] = (((width - 1) & 3) << 30) | (36u << 20);
|
||||
result[2] = ((width - 1) >> 2) | ((height - 1) << 14);
|
||||
result[3] = (9u << 28) | (1u << 20) | 0xfacu;
|
||||
return result;
|
||||
}
|
||||
|
||||
class PsmlTest : public testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() {
|
||||
Libs::LibPsml::InitPsml_1(&g_symbols);
|
||||
g_trinity = false;
|
||||
ASSERT_EQ(Function<Initialize>("3WVD91e12ZQ")(), ERROR_NOT_TRINITY);
|
||||
g_trinity = true;
|
||||
ASSERT_EQ(Function<Initialize>("3WVD91e12ZQ")(), 0);
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
SharedParameters params;
|
||||
params.blocks = blocks.data();
|
||||
params.block_count = blocks.size();
|
||||
GuardedHandle result;
|
||||
ASSERT_EQ(Function<CreateShared>("eWoKNeB6V-k")(&result.value, ¶ms), 0);
|
||||
ASSERT_TRUE(result.Intact());
|
||||
ASSERT_GT(reinterpret_cast<uintptr_t>(result.value), 0x10000u);
|
||||
shared = result.value;
|
||||
|
||||
context_params.blocks = blocks.data();
|
||||
context_params.block_count = 1;
|
||||
context_params.shared = shared;
|
||||
result.value = nullptr;
|
||||
ASSERT_EQ(
|
||||
Function<CreateContext>("gxv3i+MTEzU")(&result.value, &context_params),
|
||||
0);
|
||||
ASSERT_TRUE(result.Intact());
|
||||
ASSERT_GT(reinterpret_cast<uintptr_t>(result.value), 0x10000u);
|
||||
context = result.value;
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
for (auto *extra : extra_contexts) {
|
||||
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(extra), 0);
|
||||
}
|
||||
if (context != nullptr) {
|
||||
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), 0);
|
||||
}
|
||||
if (shared != nullptr) {
|
||||
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<MemoryBlock, 52> blocks;
|
||||
ContextParameters context_params;
|
||||
void *shared = nullptr;
|
||||
void *context = nullptr;
|
||||
std::vector<void *> extra_contexts;
|
||||
};
|
||||
|
||||
TEST_F(PsmlTest, HandlesPreserveAdjacentMemoryAndRejectReleasedObjects) {
|
||||
EXPECT_EQ(Function<Initialize>("3WVD91e12ZQ")(), ERROR_ALREADY_INITIALIZED);
|
||||
uint32_t size = 0;
|
||||
EXPECT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &size), 0);
|
||||
EXPECT_EQ(size, Pssr::RequestDwords + 1);
|
||||
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), ERROR_INVALID_CONTEXT);
|
||||
EXPECT_EQ(Function<Predicate>("LXq+6mIxpCw")(context), 0);
|
||||
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), 0);
|
||||
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), ERROR_INVALID_CONTEXT);
|
||||
EXPECT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &size),
|
||||
ERROR_INVALID_CONTEXT);
|
||||
EXPECT_EQ(Function<Predicate>("LXq+6mIxpCw")(context), ERROR_INVALID_CONTEXT);
|
||||
context = nullptr;
|
||||
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), 0);
|
||||
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), ERROR_INVALID_SHARED);
|
||||
shared = nullptr;
|
||||
EXPECT_EQ(
|
||||
Function<GetSize>("AHalTX9wFZY")(reinterpret_cast<void *>(1), &size),
|
||||
ERROR_INVALID_CONTEXT);
|
||||
}
|
||||
|
||||
TEST_F(PsmlTest, LegacyContextVariantsUseTheirOwnLayoutsAndOutputResolution) {
|
||||
context_params.shared = reinterpret_cast<void *>(1);
|
||||
GuardedHandle result;
|
||||
ASSERT_EQ(
|
||||
Function<CreateContext>("gxv3i+MTEzU")(&result.value, &context_params),
|
||||
ERROR_INVALID_SHARED);
|
||||
ASSERT_TRUE(result.Intact());
|
||||
ASSERT_EQ(result.value, nullptr);
|
||||
ASSERT_EQ(
|
||||
Function<CreateContext>("vUk2pWMx3KQ")(&result.value, &context_params),
|
||||
0);
|
||||
ASSERT_TRUE(result.Intact());
|
||||
extra_contexts.push_back(result.value);
|
||||
float bias = 0;
|
||||
EXPECT_EQ(Function<GetBias>("GHna9-DvnUk")(result.value, &bias, 480, 270), 0);
|
||||
EXPECT_FLOAT_EQ(bias, -2.0f);
|
||||
ASSERT_EQ(
|
||||
Function<CreateContext>("2ecEbQaf9VU")(&result.value, &context_params),
|
||||
0);
|
||||
ASSERT_TRUE(result.Intact());
|
||||
extra_contexts.push_back(result.value);
|
||||
EXPECT_EQ(Function<GetBias>("GHna9-DvnUk")(result.value, &bias, 1920, 1080),
|
||||
0);
|
||||
EXPECT_FLOAT_EQ(bias, -2.0f);
|
||||
}
|
||||
|
||||
TEST_F(PsmlTest, CapsLiveContextsAndReleasesCapacity) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
void *extra = nullptr;
|
||||
ASSERT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&extra, &context_params),
|
||||
0);
|
||||
extra_contexts.push_back(extra);
|
||||
}
|
||||
void *fifth = nullptr;
|
||||
EXPECT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&fifth, &context_params),
|
||||
ERROR_TOO_MANY_CONTEXTS);
|
||||
EXPECT_EQ(fifth, nullptr);
|
||||
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(extra_contexts.back()), 0);
|
||||
extra_contexts.pop_back();
|
||||
ASSERT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&fifth, &context_params), 0);
|
||||
extra_contexts.push_back(fifth);
|
||||
}
|
||||
|
||||
TEST_F(PsmlTest, DispatchSnapshotsFixedSlotsAndActiveExtent) {
|
||||
auto output = Texture(960, 540, 0x10000);
|
||||
auto input = Texture(1920, 1080, 0x20000);
|
||||
DispatchParameters params{output.data(), input.data(), {}, 640, 360};
|
||||
std::array<uint32_t, 32> command;
|
||||
command.fill(0xdeadbeef);
|
||||
const CommandSpace space{command.data() + 1, 28};
|
||||
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms), 0);
|
||||
EXPECT_EQ(KYTY_PM4_LEN(command[1]), space.dwords);
|
||||
EXPECT_EQ(KYTY_PM4_R(command[1]), Pm4::R_PSSR);
|
||||
EXPECT_EQ((command[1] >> 8) & 0xffu, Pm4::IT_NOP);
|
||||
Pssr::Request request;
|
||||
std::memcpy(&request, command.data() + 2, sizeof(request));
|
||||
EXPECT_EQ(std::memcmp(request.output, output.data(), sizeof(request.output)),
|
||||
0);
|
||||
EXPECT_EQ(std::memcmp(request.input, input.data(), sizeof(request.input)), 0);
|
||||
EXPECT_EQ(request.width, 640u);
|
||||
EXPECT_EQ(request.height, 360u);
|
||||
EXPECT_EQ(command.front(), 0xdeadbeefu);
|
||||
EXPECT_EQ(command[29], 0xdeadbeefu);
|
||||
for (size_t i = 2 + Pssr::RequestDwords; i < 29; ++i) {
|
||||
EXPECT_EQ(command[i], 0u);
|
||||
}
|
||||
output.fill(0);
|
||||
input.fill(0);
|
||||
params = {};
|
||||
Pssr::Request snapshot;
|
||||
std::memcpy(&snapshot, command.data() + 2, sizeof(snapshot));
|
||||
EXPECT_EQ(std::memcmp(&snapshot, &request, sizeof(request)), 0);
|
||||
}
|
||||
|
||||
TEST_F(PsmlTest, DispatchAcceptsEqualSizedBuffersAndValidatesWithoutWriting) {
|
||||
auto output = Texture(960, 540, 0x10000);
|
||||
auto input = Texture(960, 540, 0x20000);
|
||||
DispatchParameters params{output.data(), input.data(), {}, 960, 540};
|
||||
uint32_t dwords = 0;
|
||||
ASSERT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &dwords), 0);
|
||||
std::vector<uint32_t> command(dwords, 0xdeadbeef);
|
||||
CommandSpace space{command.data(), dwords};
|
||||
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms), 0);
|
||||
std::fill(command.begin(), command.end(), 0xdeadbeef);
|
||||
space.dwords = dwords - 1;
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_NO_SPACE);
|
||||
space.dwords = 0x4002;
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_BAD_COMMAND_BUFFER);
|
||||
space.dwords = dwords;
|
||||
params.width = 961;
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_RENDER_SIZE);
|
||||
params.width = 960;
|
||||
output = Texture(960, 539, 0x10000);
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_OUTPUT_TEXTURE);
|
||||
params.input = nullptr;
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_NULL_TEXTURE);
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, nullptr),
|
||||
ERROR_NULL_ARGUMENT);
|
||||
EXPECT_TRUE(std::all_of(command.begin(), command.end(),
|
||||
[](uint32_t value) { return value == 0xdeadbeef; }));
|
||||
}
|
||||
|
||||
TEST_F(PsmlTest, DispatchChecksDescriptorContractAndSelectedMip) {
|
||||
auto output = Texture(960, 540, 0x10000);
|
||||
auto input = Texture(1920, 1080, 0x20000);
|
||||
input[3] |= (1u << 12) | (1u << 16);
|
||||
input[5] |= 1u << 4;
|
||||
DispatchParameters params{output.data(), input.data(), {}, 960, 540};
|
||||
std::array<uint32_t, Pssr::RequestDwords + 1> command{};
|
||||
const CommandSpace space{command.data(), command.size()};
|
||||
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms), 0);
|
||||
command.fill(0xdeadbeef);
|
||||
params.width = 961;
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_RENDER_SIZE);
|
||||
params.width = 960;
|
||||
input[1] &= ~(0x1ffu << 20);
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_INVALID_TEXTURE);
|
||||
input = Texture(960, 540, 0x20000);
|
||||
input[3] = (input[3] & ~(0xfu << 28)) | (14u << 28);
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_INVALID_TEXTURE);
|
||||
input = Texture(960, 540, 0x20000);
|
||||
output[3] &= ~(0x1fu << 20);
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_OUTPUT_TEXTURE);
|
||||
output = Texture(960, 540, 0x20000);
|
||||
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, ¶ms),
|
||||
ERROR_OUTPUT_TEXTURE);
|
||||
EXPECT_TRUE(std::all_of(command.begin(), command.end(),
|
||||
[](uint32_t value) { return value == 0xdeadbeef; }));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
void CheckPssr() {
|
||||
constexpr const char *name = "PssrLinear";
|
||||
constexpr uintptr_t base = 0x0000000206a00000ull;
|
||||
constexpr uint64_t size = 0x100000;
|
||||
constexpr auto packed = Prospero::BufferFormat::k11_11_10Float;
|
||||
constexpr auto linear = Prospero::TileMode::kLinear;
|
||||
constexpr auto tiled = Prospero::TileMode::kStandard256B;
|
||||
constexpr u32 red = 0x000003c0, green = 0x001e0000, blue = 0x78000000;
|
||||
const auto descriptor = [](uint64_t address, u32 width, u32 height,
|
||||
Prospero::BufferFormat format, Prospero::TileMode tile,
|
||||
u32 swizzle = DstSel(4, 5, 6, 7)) {
|
||||
ShaderTextureResource result{};
|
||||
result.fields[0] = static_cast<u32>(address >> 8);
|
||||
result.fields[1] = static_cast<u32>(address >> 40) |
|
||||
(static_cast<u32>(format) << 20) | (((width - 1) & 3) << 30);
|
||||
result.fields[2] = ((width - 1) >> 2) | ((height - 1) << 14);
|
||||
result.fields[3] = swizzle | (static_cast<u32>(tile) << 20) |
|
||||
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28);
|
||||
result.fields[5] = 0x00700000;
|
||||
return result;
|
||||
};
|
||||
EnsureRuntimeContext();
|
||||
GraphicsInitJmpTables();
|
||||
int64_t physical = -1;
|
||||
Require(name, "allocate", LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, LibKernel::Memory::KernelGetDirectMemorySize(), size, 0x10000, 0, &physical) == 0,
|
||||
"upscaling allocation failed");
|
||||
void *mapped = reinterpret_cast<void *>(base);
|
||||
Require(name, "map", LibKernel::Memory::KernelMapDirectMemory(
|
||||
&mapped, size, 0x3, 0x10, physical, 0x10000) == 0 &&
|
||||
mapped == reinterpret_cast<void *>(base), "upscaling mapping failed");
|
||||
std::memset(mapped, 0, size);
|
||||
RenderContext context(m_runtime_context);
|
||||
context.InitializeGpu(nullptr);
|
||||
LibKernel::Memory::InstallGpuResources(&context);
|
||||
context.GetGpu().SendCommandSync([&] {
|
||||
CommandProcessor processor(context, 0);
|
||||
processor.BufferInit();
|
||||
context.MapMemory(base, size);
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
auto &cache = context.GetTextureCache();
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
const auto resolve = [&](const ShaderTextureResource &texture) {
|
||||
ShaderRecompiler::IR::ImageResource resource{};
|
||||
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
resource.numeric_class = Prospero::TextureNumericClass::Float;
|
||||
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
resource.read = true;
|
||||
ShaderRecompiler::IR::DescriptorValue value{};
|
||||
value.dword_count = 8;
|
||||
std::copy_n(texture.fields, 8, value.dwords.begin());
|
||||
return executor.ResolveTexture(resource, value);
|
||||
};
|
||||
const auto write_linear = [&](const ShaderTextureResource &texture,
|
||||
std::span<const u32> texels) {
|
||||
auto info = MakeTilingImage(texture.Format(), texture.Width5() + 1u,
|
||||
texture.Height5() + 1u, 1, 1, linear, 0, false);
|
||||
const u32 words_per_texel = info.bytes_per_block / sizeof(u32);
|
||||
Require(name, "source size", texels.size() ==
|
||||
info.extent.width * info.extent.height * words_per_texel,
|
||||
"source texel fixture size is wrong");
|
||||
std::vector<u32> words(info.data.size / sizeof(u32));
|
||||
for (u32 y = 0; y < info.extent.height; ++y) {
|
||||
std::copy_n(texels.begin() + y * info.extent.width * words_per_texel,
|
||||
info.extent.width * words_per_texel,
|
||||
words.begin() + y * info.mip_layout[0].pitch * words_per_texel);
|
||||
}
|
||||
Require(name, "invalidate source", context.InvalidateMemory(
|
||||
texture.Base40(), info.data.size), "source fixture is outside mapped memory");
|
||||
LibKernel::Memory::WriteBacking(texture.Base40(), words.data(), info.data.size);
|
||||
};
|
||||
const auto run = [&](const ShaderTextureResource &input,
|
||||
const ShaderTextureResource &output, u32 width, u32 height) {
|
||||
Pssr::Request request{};
|
||||
std::copy_n(input.fields, 8, request.input);
|
||||
std::copy_n(output.fields, 8, request.output);
|
||||
request.width = width;
|
||||
request.height = height;
|
||||
// The reserved packet includes trailing words that the parser must consume.
|
||||
constexpr u32 reserved_words = Pssr::RequestDwords + 9;
|
||||
std::vector<u32> packet(reserved_words, 0xdeadbeef);
|
||||
packet[0] = KYTY_PM4(reserved_words, Pm4::IT_NOP, Pm4::R_PSSR);
|
||||
std::memcpy(packet.data() + 1, &request, sizeof(request));
|
||||
u32 sentinel = 0;
|
||||
const auto address = reinterpret_cast<uint64_t>(&sentinel);
|
||||
packet.insert(packet.end(), {KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0), 0,
|
||||
static_cast<u32>(address), static_cast<u32>(address >> 32), 33});
|
||||
Pm4Execution execution;
|
||||
Require(name, "packet and suffix", processor.Process(execution, packet) ==
|
||||
Pm4ProcessResult::Complete && sentinel == 33,
|
||||
"upscaling packet did not consume its reserved payload before the suffix");
|
||||
const auto binding = resolve(output);
|
||||
return ReadCachedTexel(name, context, binding.image_id, {},
|
||||
{output.Width5() + 1u, output.Height5() + 1u, 1},
|
||||
binding.desc.view_info.base_layer, binding.desc.view_info.base_level);
|
||||
};
|
||||
|
||||
auto input = descriptor(base, 2, 1, packed, linear);
|
||||
auto output = descriptor(base + 0x10000, 4, 1, packed, tiled);
|
||||
write_linear(input, std::array<u32, 2>{red, green});
|
||||
Require(name, "CPU-first bilinear source", run(input, output, 2, 1) ==
|
||||
std::vector<u32>{red, 0x3a0 | (0x340 << 11), 0x340 | (0x3a0 << 11), green},
|
||||
"first-use source upload or linear interpolation is wrong");
|
||||
write_linear(input, std::array<u32, 2>{blue, blue});
|
||||
Require(name, "CPU source refresh", run(input, output, 2, 1) ==
|
||||
std::vector<u32>(4, blue), "reused source retained stale GPU texels");
|
||||
|
||||
input = descriptor(base + 0x20000, 2, 1, packed, linear);
|
||||
output = descriptor(base + 0x30000, 2, 1, packed, tiled);
|
||||
write_linear(input, std::array<u32, 2>{red, green});
|
||||
Require(name, "same-size copy", run(input, output, 2, 1) ==
|
||||
std::vector<u32>{red, green}, "same-size upscaling lost source pixels");
|
||||
|
||||
input = descriptor(base + 0x40000, 8, 4, packed, linear);
|
||||
output = descriptor(base + 0x50000, 8, 4, packed, tiled);
|
||||
std::vector<u32> cropped(32, blue);
|
||||
cropped[0] = cropped[1] = cropped[8] = cropped[9] = red;
|
||||
write_linear(input, cropped);
|
||||
Require(name, "active rectangle", run(input, output, 2, 2) ==
|
||||
std::vector<u32>(32, red),
|
||||
"equal allocation extents bypassed scaling or sampled outside the active rectangle");
|
||||
|
||||
input = descriptor(base + 0x60000, 2, 1, packed, linear, DstSel(6, 5, 4, 7));
|
||||
output = descriptor(base + 0x70000, 2, 1, packed, tiled);
|
||||
write_linear(input, std::array<u32, 2>{red, green});
|
||||
Require(name, "sampled view swizzle", run(input, output, 2, 1) ==
|
||||
std::vector<u32>{blue, green}, "sampled descriptor swizzle was ignored");
|
||||
output.fields[3] = (output.fields[3] & ~0xfffu) | DstSel(6, 5, 4, 7);
|
||||
Require(name, "storage swizzle", run(input, output, 2, 1) ==
|
||||
std::vector<u32>{red, green}, "output channel mapping was ignored");
|
||||
|
||||
input = descriptor(base + 0x80000, 2, 1,
|
||||
Prospero::BufferFormat::k16_16_16_16Float, linear);
|
||||
output = descriptor(base + 0x90000, 2, 1, packed, tiled);
|
||||
write_linear(input, std::array<u32, 4>{0x00003c00, 0x3c000000,
|
||||
0x3c000000, 0x3c000000});
|
||||
Require(name, "float16 input", run(input, output, 2, 1) ==
|
||||
std::vector<u32>{red, green}, "float16 input conversion failed");
|
||||
|
||||
ImageDesc parent{};
|
||||
parent.type = BindingType::Texture;
|
||||
parent.info = MakeTilingImage(packed, 4, 2, 1, 2, tiled, 0, false);
|
||||
parent.info.data.address = base + 0xa0000;
|
||||
parent.info.pixel_format = vk::Format::eB10G11R11UfloatPack32;
|
||||
parent.view_info.format = parent.info.pixel_format;
|
||||
parent.view_info.type = vk::ImageViewType::e2DArray;
|
||||
parent.view_info.layer_count = 2;
|
||||
const auto parent_id = cache.FindImage(parent);
|
||||
(void)cache.FindTexture(parent_id, parent);
|
||||
vk::ClearValue clear{};
|
||||
clear.color.float32 = std::array<float, 4>{1, 0, 0, 1};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear);
|
||||
clear.color.float32 = std::array<float, 4>{0, 0, 1, 1};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear);
|
||||
input = descriptor(parent.info.data.address, 4, 2, packed, tiled);
|
||||
output = descriptor(parent.info.data.address + parent.info.tiled_slice_stride,
|
||||
4, 2, packed, tiled);
|
||||
const auto resolved_output = resolve(output);
|
||||
Require(name, "parent array resolution", resolved_output.image_id == parent_id &&
|
||||
resolved_output.desc.view_info.base_layer == 1,
|
||||
"standalone output did not resolve into the existing array's second layer");
|
||||
Require(name, "resolved output layer", run(input, output, 4, 2) ==
|
||||
std::vector<u32>(8, red), "upscaling wrote layer zero instead of the resolved layer");
|
||||
Require(name, "source layer preserved", ReadCachedTexel(name, context, parent_id,
|
||||
{}, {4, 2, 1}) == std::vector<u32>(8, red),
|
||||
"upscaling modified the source layer of its shared native image");
|
||||
clear.color.float32 = std::array<float, 4>{0, 1, 0, 1};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear);
|
||||
Require(name, "resolved source layer", run(output, input, 4, 2) ==
|
||||
std::vector<u32>(8, green), "upscaling read layer zero instead of the resolved layer");
|
||||
|
||||
input = descriptor(base + 0xc0000, 2, 1, packed, linear);
|
||||
output = descriptor(base + 0xd0000, 2, 1, packed, tiled);
|
||||
write_linear(input, std::array<u32, 2>{red, green});
|
||||
ImageDesc raw{};
|
||||
raw.type = BindingType::Texture;
|
||||
raw.info = MakeTilingImage(Prospero::BufferFormat::k32UInt, 2, 1, 1, 1,
|
||||
linear, 0, false);
|
||||
raw.info.data.address = input.Base40();
|
||||
raw.info.pixel_format = vk::Format::eR32Uint;
|
||||
raw.view_info.format = raw.info.pixel_format;
|
||||
const auto raw_id = cache.FindImage(raw);
|
||||
(void)cache.FindTexture(raw_id, raw);
|
||||
const auto float_binding = resolve(input);
|
||||
Require(name, "reinterpreted sampled view", float_binding.image_id == raw_id &&
|
||||
cache.GetImage(raw_id).backing.format == vk::Format::eR32Uint &&
|
||||
float_binding.desc.view_info.format == vk::Format::eB10G11R11UfloatPack32,
|
||||
"float sampled descriptor did not reuse the integer native allocation");
|
||||
Require(name, "sampled view format", run(input, output, 2, 1) ==
|
||||
std::vector<u32>{red, green}, "upscaling used the allocation format instead of its view");
|
||||
|
||||
ImageDesc mip_parent{};
|
||||
mip_parent.type = BindingType::Texture;
|
||||
mip_parent.info = MakeTilingImage(packed, 128, 64, 2, 1, tiled, 0, false);
|
||||
mip_parent.info.data.address = base + 0xe0000;
|
||||
mip_parent.info.pixel_format = vk::Format::eB10G11R11UfloatPack32;
|
||||
mip_parent.view_info.format = mip_parent.info.pixel_format;
|
||||
mip_parent.view_info.level_count = 2;
|
||||
const auto mip_parent_id = cache.FindImage(mip_parent);
|
||||
(void)cache.FindTexture(mip_parent_id, mip_parent);
|
||||
clear.color.float32 = std::array<float, 4>{0, 0, 1, 1};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), mip_parent_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear);
|
||||
clear.color.float32 = std::array<float, 4>{0, 1, 0, 1};
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), mip_parent_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 1, 1, 0, 1}, clear);
|
||||
input = descriptor(mip_parent.info.data.address + mip_parent.info.mip_layout[1].offset,
|
||||
64, 32, packed, tiled);
|
||||
output = descriptor(base + 0xf0000, 64, 32, packed, tiled);
|
||||
const auto resolved_mip = resolve(input);
|
||||
Require(name, "parent mip resolution", resolved_mip.image_id == mip_parent_id &&
|
||||
resolved_mip.desc.view_info.base_level == 1,
|
||||
"standalone source did not resolve into the existing image's second mip");
|
||||
Require(name, "resolved source mip", run(input, output, 64, 32) ==
|
||||
std::vector<u32>(64 * 32, green),
|
||||
"upscaling read mip zero instead of the resolved mip");
|
||||
context.UnmapMemory(base, size);
|
||||
scheduler.Finish();
|
||||
});
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
context.ShutdownGpu();
|
||||
Require(name, "release", LibKernel::Memory::KernelCheckedReleaseDirectMemory(
|
||||
physical, size) == 0, "upscaling allocation release failed");
|
||||
std::printf("[gpu] %-32s ok\n", name);
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -0,0 +1,129 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/virtualMemory.h"
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "libs/agc.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace Libs {
|
||||
void InitLibKernel_1(Loader::SymbolDatabase*);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Pm4 = Libs::Graphics::Pm4;
|
||||
|
||||
TEST(Trinity, ModeQueriesPacketsAndMemoryAgree) {
|
||||
static Common::Subsystems subsystems;
|
||||
Common::VirtualMemory::Init();
|
||||
Common::InitializeThreads();
|
||||
subsystems.Initialize<Config::Lifecycle>();
|
||||
Config::ConfigOptions options;
|
||||
options.printf_direction = Config::LogDirection::Silent;
|
||||
Config::Load(options);
|
||||
subsystems.Initialize<Log::Lifecycle>();
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::InitLibKernel_1(&symbols);
|
||||
const auto* query = symbols.FindByNid("tU5e3f9gSiU", Loader::SymbolType::Func);
|
||||
const auto* operation = symbols.FindByNid("NH6xARDOVv8", Loader::SymbolType::Func);
|
||||
ASSERT_NE(query, nullptr);
|
||||
ASSERT_NE(operation, nullptr);
|
||||
const auto is_trinity = reinterpret_cast<int(KYTY_SYSV_ABI*)()>(query->vaddr);
|
||||
const auto operation_mode = reinterpret_cast<int(KYTY_SYSV_ABI*)(int*, int*)>(operation->vaddr);
|
||||
|
||||
struct CommandBuffer {
|
||||
uint32_t* bottom;
|
||||
uint32_t* top;
|
||||
uint32_t* cursor_up;
|
||||
uint32_t* cursor_down;
|
||||
void* callback;
|
||||
void* user_data;
|
||||
uint32_t reserved_dw;
|
||||
};
|
||||
const auto path = std::filesystem::temp_directory_path() /
|
||||
("kyty_trinity_" + std::to_string(reinterpret_cast<uintptr_t>(&symbols)) + ".json");
|
||||
struct Case { bool enabled; uint32_t attribute; bool expected; };
|
||||
for (const auto test: {Case {false, 0, false}, Case {false, 1u << 22, false}, Case {true, 0, false},
|
||||
Case {true, 1u << 21, false}, Case {true, 1u << 22, true}}) {
|
||||
options.pro_enabled = test.enabled;
|
||||
Config::Load(options);
|
||||
{
|
||||
std::ofstream file(path);
|
||||
file << "{\"attribute3\":" << test.attribute
|
||||
<< R"(,"kernel":{"flexibleMemorySize":3221225472,"cpuPageTableSize":134217728,"gpuPageTableSize":0},"amm":{"pagetableMemorySizeInMib":96}})";
|
||||
ASSERT_TRUE(file.good());
|
||||
}
|
||||
testing::internal::CaptureStdout();
|
||||
Loader::SystemContentLoadParamSfo(path);
|
||||
std::filesystem::remove(path);
|
||||
EXPECT_EQ(Loader::SystemContentIsTrinityMode(), test.expected);
|
||||
EXPECT_EQ(is_trinity(), test.expected ? 1 : 0);
|
||||
int mode = -1, submode = -1;
|
||||
EXPECT_EQ(operation_mode(&mode, &submode), 0);
|
||||
EXPECT_EQ(mode, test.expected ? 3 : 2);
|
||||
EXPECT_EQ(submode, 0);
|
||||
uint8_t graphics_mode = 0xff;
|
||||
Libs::Graphics::Gen5::AgcGetIsTrinityMode(&graphics_mode);
|
||||
EXPECT_EQ(graphics_mode, test.expected ? 1 : 0);
|
||||
std::array<uint32_t, 5> packet {};
|
||||
CommandBuffer buffer {packet.data(), packet.data() + packet.size(), packet.data(),
|
||||
packet.data() + packet.size(), nullptr, nullptr, 0};
|
||||
Libs::Graphics::Gen5::AgcDcbSetUcRegistersIndirect(
|
||||
reinterpret_cast<Libs::Graphics::Gen5::CommandBuffer*>(&buffer), nullptr, 0);
|
||||
EXPECT_EQ(packet[0], KYTY_PM4(5, Pm4::IT_SET_UCONFIG_REG_INDIRECT,
|
||||
test.expected ? 1 : 0));
|
||||
const auto console = testing::internal::GetCapturedStdout();
|
||||
if (test.enabled) {
|
||||
EXPECT_EQ(console, test.expected ? "[PS5] title Trinity support=yes\n"
|
||||
: "[PS5] title Trinity support=no\n");
|
||||
} else {
|
||||
EXPECT_TRUE(console.empty()) << console;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Libs::LibKernel::Memory;
|
||||
subsystems.Initialize<Lifecycle>();
|
||||
constexpr uint64_t mib = 1024 * 1024;
|
||||
constexpr uint64_t backing = (12800 + 1216 - 64 - 96) * mib;
|
||||
constexpr uint64_t flexible = 3072 * mib;
|
||||
EXPECT_EQ(TestGuestBackingSize(), backing);
|
||||
EXPECT_EQ(KernelGetDirectMemorySize(), backing - flexible);
|
||||
size_t configured = 0;
|
||||
ASSERT_EQ(KernelConfiguredFlexibleMemorySize(&configured), 0);
|
||||
EXPECT_EQ(configured, flexible);
|
||||
int64_t available_start = -1;
|
||||
size_t available_size = 0;
|
||||
ASSERT_EQ(KernelAvailableDirectMemorySize(0, backing - flexible, 0x4000,
|
||||
&available_start, &available_size), 0);
|
||||
EXPECT_EQ(available_start, 0);
|
||||
EXPECT_EQ(available_size, backing - flexible);
|
||||
int64_t physical = -1;
|
||||
ASSERT_EQ(KernelAllocateDirectMemory(backing - flexible - 0x4000, backing - flexible,
|
||||
0x4000, 0x4000, 0, &physical), 0);
|
||||
void* mapped = nullptr;
|
||||
ASSERT_EQ(KernelMapDirectMemory(&mapped, 0x4000, 2, 0, physical, 0), 0);
|
||||
*static_cast<volatile uint64_t*>(mapped) = 0x123456789abcdef0ull;
|
||||
EXPECT_EQ(*static_cast<volatile uint64_t*>(mapped), 0x123456789abcdef0ull);
|
||||
EXPECT_EQ(KernelMunmap(reinterpret_cast<uint64_t>(mapped), 0x4000), 0);
|
||||
EXPECT_EQ(KernelCheckedReleaseDirectMemory(physical, 0x4000), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int RunTrinityTests() {
|
||||
int argc = 1;
|
||||
char name[] = "trinity_tests";
|
||||
char* argv[] = {name, nullptr};
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -5708,7 +5708,12 @@ void TestTlsStubsPreserveFlags() {
|
||||
}
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
@@ -1938,7 +1938,7 @@ void TestVertexBufferGrouping() {
|
||||
Check(input.fetch_attrib_reg == 2, "a draw reread the vertex metadata of a mapped shader");
|
||||
ShaderMapUserData(regs.es_regs.data_addr, mapped);
|
||||
(void)PrepareProgram(regs, HW::Context{}, HW::UserConfig{}, input);
|
||||
Check(!input.fetch_embedded, "remapping a shader kept its previous vertex metadata");
|
||||
Check(!input.has_dynamic_vertex_fetch, "remapping a shader kept its previous vertex metadata");
|
||||
}
|
||||
|
||||
void TestNggVertexEntryState() {
|
||||
@@ -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),
|
||||
@@ -11575,7 +11656,7 @@ void TestEmbeddedFetchPreservesSharedScalarLoad() {
|
||||
reinterpret_cast<uint64_t>(attributes.data())};
|
||||
std::memcpy(user_data.data() + 12, tables, sizeof(tables));
|
||||
ShaderVertexInputInfo input{};
|
||||
input.fetch_embedded = true;
|
||||
input.has_dynamic_vertex_fetch = true;
|
||||
input.fetch_buffer_reg = 12;
|
||||
input.fetch_attrib_reg = 14;
|
||||
input.resources_num = 1;
|
||||
@@ -17121,6 +17202,7 @@ int main() {
|
||||
using namespace Libs::Graphics;
|
||||
|
||||
EnsureConfigInitialized();
|
||||
TestVop3FloatConversionDecode();
|
||||
TestRayTracingInstructions();
|
||||
TestResourceDescriptorClassification();
|
||||
TestShaderBufferResourceSize();
|
||||
|
||||
Reference in new issue
Block a user