mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-30 01:01:21 +00:00
Compare commits
48
Commits
358333125d
...
main
| 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 | ||
|
|
69fdb03dc2 | ||
|
|
2efd31443f | ||
|
|
9eef2e1434 | ||
|
|
39faa62ebd | ||
|
|
7ff8aff5e6 | ||
|
|
64a554dea7 | ||
|
|
4b0282e343 | ||
|
|
ffc81377b2 | ||
|
|
0685bffb2f | ||
|
|
a5b3673a39 | ||
|
|
09753bf8ff | ||
|
|
9d8c458535 |
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
|
||||
|
||||
@@ -105,10 +105,18 @@ bool HideCursorEnabled() {
|
||||
return g_config->hide_cursor_enabled;
|
||||
}
|
||||
|
||||
bool StretchEnabled() {
|
||||
return g_config->stretch_enabled;
|
||||
}
|
||||
|
||||
bool VrEnabled() {
|
||||
return g_config->vr_enabled;
|
||||
}
|
||||
|
||||
bool Ps5ProEnabled() {
|
||||
return g_config->pro_enabled;
|
||||
}
|
||||
|
||||
bool AmdCpuEnabled() {
|
||||
return g_config->amd_cpu_enabled;
|
||||
}
|
||||
@@ -129,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;
|
||||
}
|
||||
|
||||
@@ -64,10 +64,13 @@ struct ConfigOptions {
|
||||
bool fullscreen_enabled = false;
|
||||
bool hdr_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
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;
|
||||
@@ -125,9 +128,12 @@ int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
bool HdrEnabled();
|
||||
bool HideCursorEnabled();
|
||||
bool StretchEnabled();
|
||||
bool VrEnabled();
|
||||
bool Ps5ProEnabled();
|
||||
bool AmdCpuEnabled();
|
||||
bool IsNetworkConnected();
|
||||
bool PsnEmulationEnabled();
|
||||
uint32_t GetVblankFrequency();
|
||||
uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
@@ -99,10 +99,6 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
||||
|
||||
if (Common::File::IsFileExisting(param_json)) {
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
flexible_memory_size != 0) {
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
if (cfg.splash_enabled) {
|
||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||
|
||||
@@ -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;
|
||||
@@ -814,6 +804,27 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
||||
write(registers.draw_index, draw_index);
|
||||
}
|
||||
|
||||
// Writes the GS user-data slots the packet patches; false when the shaders cannot read the
|
||||
// offsets from GPU records: a first-index or subdraw-index destination, or another stage's
|
||||
// user data.
|
||||
static bool IndirectDrawOffsets(IndirectDrawRegisters registers, ShaderIndirectOffsets& offsets) {
|
||||
if (registers.index_offset != Pm4::SH_NOP || registers.draw_index != Pm4::SH_NOP) {
|
||||
return false;
|
||||
}
|
||||
for (const auto [location, slot]: {std::pair {registers.vertex_offset, &offsets.vertex},
|
||||
std::pair {registers.instance_offset, &offsets.instance}}) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
continue;
|
||||
}
|
||||
const uint32_t index = location - Pm4::SPI_SHADER_USER_DATA_GS_0;
|
||||
if (index >= HW::UserSgprInfo::SGPRS_MAX) {
|
||||
return false;
|
||||
}
|
||||
*slot = static_cast<uint8_t>(index);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size =
|
||||
@@ -845,13 +856,10 @@ 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);
|
||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
||||
// while they are zero.
|
||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
||||
registers.index_offset == Pm4::SH_NOP && registers.draw_index == Pm4::SH_NOP &&
|
||||
(!indexed || m_index_buffer_size != 0) &&
|
||||
// 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(),
|
||||
{.index_count = m_index_buffer_size,
|
||||
.index_addr = IndexAddress(0),
|
||||
@@ -921,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) {
|
||||
@@ -941,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_
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -17,14 +18,19 @@ public:
|
||||
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
||||
it = std::prev(it);
|
||||
}
|
||||
uint64_t begin = address;
|
||||
uint64_t last = end;
|
||||
while (it != m_ranges.end() && it->first <= last) {
|
||||
begin = std::min(begin, it->first);
|
||||
last = std::max(last, it->second);
|
||||
it = m_ranges.erase(it);
|
||||
if (it == m_ranges.end() || it->first > address) {
|
||||
it = m_ranges.emplace_hint(it, address, end);
|
||||
} else {
|
||||
if (it->second >= end) {
|
||||
return;
|
||||
}
|
||||
it->second = end;
|
||||
}
|
||||
auto next = std::next(it);
|
||||
while (next != m_ranges.end() && next->first <= it->second) {
|
||||
it->second = std::max(it->second, next->second);
|
||||
next = m_ranges.erase(next);
|
||||
}
|
||||
m_ranges.emplace(begin, last);
|
||||
}
|
||||
|
||||
void Subtract(uint64_t address, uint64_t size) {
|
||||
|
||||
+29
-29
@@ -488,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) {
|
||||
}
|
||||
|
||||
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
const bool source_depth = source.info.IsDepth();
|
||||
const bool destination_depth = destination.info.IsDepth();
|
||||
if (source_depth == destination_depth) {
|
||||
// Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth.
|
||||
const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t);
|
||||
if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) {
|
||||
return false;
|
||||
}
|
||||
auto& depth = source_depth ? source : destination;
|
||||
auto& color = source_depth ? destination : source;
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format);
|
||||
auto& depth = source_wide ? source : destination;
|
||||
auto& narrow = source_wide ? destination : source;
|
||||
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
||||
color.info.bytes_per_block != sizeof(uint16_t) || transfer_bytes != sizeof(uint32_t)) {
|
||||
narrow.info.bytes_per_block != sizeof(uint16_t)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||
@@ -507,12 +506,10 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
|
||||
const auto layers = depth.info.resources.layers;
|
||||
const uint64_t depth_slice =
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * transfer_bytes;
|
||||
const uint64_t color_slice =
|
||||
static_cast<uint64_t>(color.info.pitch) * color.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers);
|
||||
const auto depth_size = depth_slice * layers;
|
||||
const auto color_size = color_slice * layers;
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t);
|
||||
const uint64_t narrow_slice =
|
||||
static_cast<uint64_t>(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers);
|
||||
const std::array depth_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
depth.info.pitch,
|
||||
@@ -520,29 +517,31 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||
{},
|
||||
depth.info.extent}};
|
||||
const std::array color_copies {
|
||||
const std::array narrow_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
color.info.pitch,
|
||||
color.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
||||
narrow.info.pitch,
|
||||
narrow.info.extent.height,
|
||||
{narrow.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor,
|
||||
0, 0, layers},
|
||||
{},
|
||||
color.info.extent}};
|
||||
narrow.info.extent}};
|
||||
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_size);
|
||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers);
|
||||
auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer);
|
||||
const TileManager::D16Layout promote_layout {
|
||||
.width = depth.info.extent.width,
|
||||
.height = depth.info.extent.height,
|
||||
.layers = layers,
|
||||
.source_row_stride = static_cast<uint64_t>(color.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * transfer_bytes,
|
||||
.source_slice_stride = color_slice,
|
||||
.source_row_stride = static_cast<uint64_t>(narrow.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * sizeof(uint32_t),
|
||||
.source_slice_stride = narrow_slice,
|
||||
.target_slice_stride = depth_slice,
|
||||
};
|
||||
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
||||
if (source_depth) {
|
||||
if (source_wide) {
|
||||
source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size);
|
||||
m_tiler.ConvertD16(depth_buffer, color_buffer, TileManager::D16Direction::Demote, d32,
|
||||
m_tiler.ConvertD16(depth_buffer, narrow_buffer, TileManager::D16Direction::Demote, d32,
|
||||
{.width = promote_layout.width,
|
||||
.height = promote_layout.height,
|
||||
.layers = promote_layout.layers,
|
||||
@@ -550,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
.target_row_stride = promote_layout.source_row_stride,
|
||||
.source_slice_stride = promote_layout.target_slice_stride,
|
||||
.target_slice_stride = promote_layout.source_slice_stride});
|
||||
destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset,
|
||||
color_buffer.size);
|
||||
destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
} else {
|
||||
source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size);
|
||||
m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
promote_layout);
|
||||
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
||||
depth_buffer.size);
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace {
|
||||
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
|
||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
||||
// Smaller tables are often written for one draw, so they enter on a second draw of a frame.
|
||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||
|
||||
@@ -133,12 +133,16 @@ ImageHeap::~ImageHeap() {
|
||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto size = request.size & ~uint64_t {3};
|
||||
if (size < MinTableBytes || request.count == 0u || request.step == 0u ||
|
||||
if (request.count == 0u || request.step == 0u ||
|
||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||
return false;
|
||||
}
|
||||
auto memory = std::ranges::find_if(
|
||||
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
||||
if (memory == m_memories.end() && size < MinTableBytes &&
|
||||
m_sightings.try_emplace(request.base, m_draw).first->second == m_draw) {
|
||||
return false;
|
||||
}
|
||||
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||
if (!m_context.IsMapped(request.base, size)) return false;
|
||||
@@ -593,14 +597,32 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
} else {
|
||||
m_unmapped.push_back({address, size});
|
||||
}
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||
return false;
|
||||
}
|
||||
Unwatch(*memory);
|
||||
for (auto& view: memory->views) {
|
||||
Drop(*memory);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void ImageHeap::RunGarbageCollector() {
|
||||
// A smaller table rebuilt each frame moves to a new base. Larger ones keep their dead words.
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (memory->size >= MinTableBytes || memory->draw > m_suspend_draw) return false;
|
||||
Drop(*memory);
|
||||
return true;
|
||||
});
|
||||
m_suspend_draw = m_draw;
|
||||
m_sightings.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Drop(Memory& memory) {
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
Unwatch(memory);
|
||||
for (auto& view: memory.views) {
|
||||
std::erase(m_active, view.get());
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
Unreference(m_elements[index]);
|
||||
@@ -611,8 +633,6 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -26,7 +26,7 @@ struct ImageHeapTestAccess;
|
||||
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||
// Image::heap_epoch.
|
||||
// Image::heap_epoch. A smaller table leaves at a suspend point if the frame did not acquire it.
|
||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||
public:
|
||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
// CPU write fault, on any thread.
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
// At a suspend point.
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||
@@ -107,6 +109,7 @@ private:
|
||||
template <bool track>
|
||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||
void Unwatch(const Memory& memory);
|
||||
void Drop(Memory& memory);
|
||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||
void UpdateProbe(View& view, uint64_t probe);
|
||||
void Assign(View& view, size_t slot, uint32_t entry);
|
||||
@@ -126,8 +129,10 @@ private:
|
||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||
std::vector<View*> m_active; // Acquired since the last commit.
|
||||
uint64_t m_draw = 1;
|
||||
uint64_t m_suspend_draw = 0; // m_draw at the last suspend point.
|
||||
std::vector<Element> m_elements;
|
||||
std::vector<uint32_t> m_free;
|
||||
std::unordered_map<uint64_t, uint64_t> m_sightings; // Smaller tables: base, first draw.
|
||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||
std::vector<uint32_t> m_scratch;
|
||||
|
||||
@@ -687,6 +687,10 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
||||
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
||||
}
|
||||
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.
|
||||
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
||||
|
||||
@@ -137,6 +137,9 @@ struct DrawRenderState {
|
||||
bool ps_active = false;
|
||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||
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};
|
||||
|
||||
@@ -48,6 +48,7 @@ struct DrawIndirectArgs {
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
ShaderIndirectOffsets offsets;
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
@@ -149,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);
|
||||
@@ -218,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;
|
||||
|
||||
@@ -147,6 +147,7 @@ void RenderContext::RunGarbageCollector() {
|
||||
m_fault_process_pending = false;
|
||||
m_buffer_cache.ProcessFaultBuffer();
|
||||
}
|
||||
m_image_heap.RunGarbageCollector();
|
||||
m_texture_cache.ProcessDownloadImages();
|
||||
m_texture_cache.RunGarbageCollector();
|
||||
m_buffer_cache.RunGarbageCollector();
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
@@ -1250,24 +1250,36 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
||||
// mesh and quad-list draws do. Indirect records carry their own offsets.
|
||||
// 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 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 ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
||||
if (!vs.indirect_draw ||
|
||||
vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||
vs.indirect_offsets != indirect.offsets) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DrawEmitInfo {.indirect = indirect};
|
||||
}
|
||||
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};
|
||||
@@ -1324,7 +1336,8 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
DrawRenderState state {};
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets,
|
||||
.indirect_draw = args.indirect.address != 0};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
@@ -1429,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 {};
|
||||
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>(
|
||||
|
||||
@@ -533,16 +533,16 @@ void DrawMemoryAndIo(const Snapshot& shown) {
|
||||
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
||||
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
||||
Row("Extended", Bytes(guest.automatic_allocated));
|
||||
// Past the pool size the guest sees no page-table entries available.
|
||||
const auto entries = std::max(guest.cpu_page_entries, guest.gpu_page_entries);
|
||||
// Past its total a side sees no page-table entries available.
|
||||
const auto percent = std::max(guest.cpu_page_entries * 100 / guest.cpu_page_entries_total,
|
||||
guest.gpu_page_entries * 100 / guest.gpu_page_entries_total);
|
||||
Row("Page table (2 MB)",
|
||||
Fmt("CPU %llu GPU %llu / %llu",
|
||||
Fmt("CPU %llu / %llu GPU %llu / %llu",
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries_total),
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.page_entries_total)),
|
||||
entries > guest.page_entries_total ? BAD
|
||||
: entries * 10 > guest.page_entries_total * 9 ? MEDIUM
|
||||
: GOOD);
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries_total)),
|
||||
LevelColor(static_cast<double>(percent), 90, 100));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
if (BeginSection("Emulator process")) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -290,7 +290,8 @@ public:
|
||||
private:
|
||||
vk::SurfaceFormatKHR SelectFormat();
|
||||
void Destroy();
|
||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
|
||||
const vk::Rect2D& rect);
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
@@ -637,7 +638,23 @@ bool Swapchain::PrepareSystemOverlay() {
|
||||
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||
}
|
||||
|
||||
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer) {
|
||||
static vk::Rect2D FitAspect(const vk::Extent3D& source, const vk::Extent2D& target) {
|
||||
const uint64_t src_w = std::max(source.width, 1u);
|
||||
const uint64_t src_h = std::max(source.height, 1u);
|
||||
uint32_t w = target.width;
|
||||
uint32_t h = target.height;
|
||||
if (src_w * h > src_h * w) {
|
||||
h = std::max<uint32_t>(1, static_cast<uint32_t>(src_h * w / src_w));
|
||||
} else {
|
||||
w = std::max<uint32_t>(1, static_cast<uint32_t>(src_w * h / src_h));
|
||||
}
|
||||
return {{static_cast<int32_t>((target.width - w) / 2),
|
||||
static_cast<int32_t>((target.height - h) / 2)},
|
||||
{w, h}};
|
||||
}
|
||||
|
||||
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
|
||||
const vk::Rect2D& rect) {
|
||||
auto device = m_window.graphic_ctx.device;
|
||||
if (m_overlay_pipeline == nullptr) {
|
||||
vk::SamplerCreateInfo sampler {};
|
||||
@@ -756,12 +773,14 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.beginRendering(&rendering);
|
||||
const vk::Viewport viewport {
|
||||
0.0f, 0.0f, static_cast<float>(m_extent.width), static_cast<float>(m_extent.height),
|
||||
0.0f, 1.0f};
|
||||
const vk::Rect2D scissor {{0, 0}, m_extent};
|
||||
const vk::Viewport viewport {static_cast<float>(rect.offset.x),
|
||||
static_cast<float>(rect.offset.y),
|
||||
static_cast<float>(rect.extent.width),
|
||||
static_cast<float>(rect.extent.height),
|
||||
0.0f,
|
||||
1.0f};
|
||||
command.setViewport(0, 1, &viewport);
|
||||
command.setScissor(0, 1, &scissor);
|
||||
command.setScissor(0, 1, &rect);
|
||||
command.draw(3, 1, 0, 0);
|
||||
command.endRendering();
|
||||
}
|
||||
@@ -799,6 +818,24 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
||||
nullptr, 0, nullptr, 1, &to_transfer);
|
||||
|
||||
const auto* aspect_source = source != nullptr ? source : overlay.frame;
|
||||
const auto game_rect = !Config::StretchEnabled() && aspect_source != nullptr
|
||||
? FitAspect(aspect_source->image.extent, m_extent)
|
||||
: vk::Rect2D {{0, 0}, m_extent};
|
||||
if (source == nullptr || game_rect.extent != m_extent) {
|
||||
const vk::ClearColorValue black {};
|
||||
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
|
||||
&black, 1, &to_transfer.subresourceRange);
|
||||
if (source != nullptr) {
|
||||
vk::ImageMemoryBarrier after_clear = to_transfer;
|
||||
after_clear.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
after_clear.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
|
||||
&after_clear);
|
||||
}
|
||||
}
|
||||
if (source != nullptr) {
|
||||
vk::ImageBlit region {};
|
||||
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
@@ -812,16 +849,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
||||
region.dstSubresource.mipLevel = 0;
|
||||
region.dstSubresource.baseArrayLayer = 0;
|
||||
region.dstSubresource.layerCount = 1;
|
||||
region.dstOffsets[1].x = static_cast<int>(m_extent.width);
|
||||
region.dstOffsets[1].y = static_cast<int>(m_extent.height);
|
||||
region.dstOffsets[0].x = game_rect.offset.x;
|
||||
region.dstOffsets[0].y = game_rect.offset.y;
|
||||
region.dstOffsets[1].x = game_rect.offset.x + static_cast<int>(game_rect.extent.width);
|
||||
region.dstOffsets[1].y = game_rect.offset.y + static_cast<int>(game_rect.extent.height);
|
||||
region.dstOffsets[1].z = 1;
|
||||
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
|
||||
®ion, vk::Filter::eLinear);
|
||||
} else {
|
||||
const vk::ClearColorValue black {};
|
||||
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
|
||||
&black, 1, &to_transfer.subresourceRange);
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier to_present {};
|
||||
@@ -848,7 +883,7 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
||||
&to_present);
|
||||
if (draw_attachment) {
|
||||
if (draw_overlay) {
|
||||
DrawOverlay(vk_command, overlay);
|
||||
DrawOverlay(vk_command, overlay, game_rect);
|
||||
if (draw_system_overlay) {
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
|
||||
@@ -51,6 +51,7 @@ struct VulkanExtensions {
|
||||
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan11Features features {};
|
||||
features.storageBuffer16BitAccess = VK_TRUE;
|
||||
features.shaderDrawParameters = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -278,6 +279,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
required_features12.samplerMirrorClampToEdge);
|
||||
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
||||
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
||||
check_feature(features11.shaderDrawParameters, "shaderDrawParameters",
|
||||
WindowContext::RequiredVulkan11Features().shaderDrawParameters);
|
||||
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
||||
required_features12.storageBuffer8BitAccess);
|
||||
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -63,7 +63,8 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
}
|
||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId ||
|
||||
kind == IR::StageInputKind::BaseVertex || kind == IR::StageInputKind::BaseInstance) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -476,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
||||
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
||||
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
||||
case IR::StageInputKind::BaseVertex: return spv::BuiltInBaseVertex;
|
||||
case IR::StageInputKind::BaseInstance: return spv::BuiltInBaseInstance;
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
@@ -526,7 +528,9 @@ void DefineInputs(EmitterState& state) {
|
||||
case IR::StageInputKind::PrimitiveId:
|
||||
case IR::StageInputKind::InstanceIndex:
|
||||
case IR::StageInputKind::Layer:
|
||||
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::SampleId:
|
||||
case IR::StageInputKind::BaseVertex:
|
||||
case IR::StageInputKind::BaseInstance: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::WorkgroupId:
|
||||
case IR::StageInputKind::NumWorkgroups:
|
||||
case IR::StageInputKind::LocalInvocationId:
|
||||
@@ -802,6 +806,10 @@ void DefineModule(EmitterState& state) {
|
||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||
}
|
||||
if (InputVariableForKind(state, IR::StageInputKind::BaseVertex) != 0 ||
|
||||
InputVariableForKind(state, IR::StageInputKind::BaseInstance) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilityDrawParameters);
|
||||
}
|
||||
if (state.program.info.image_gather_extended) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||
}
|
||||
|
||||
@@ -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,19 +1388,32 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
} else if (options.stage == ShaderType::Vertex) {
|
||||
// Vulkan owns primitive assembly; each vertex subgroup is one NGG wave.
|
||||
// ESVertCount describes input vertices, independently of lane ordinals.
|
||||
auto vertex_count = IR::U32(entry_ir.Emit(IR::ValueOpcode::BitCount32, {initial_lo}));
|
||||
if (options.wave_size == 64u) {
|
||||
vertex_count = entry_ir.IAdd(vertex_count, IR::U32(entry_ir.Emit(
|
||||
IR::ValueOpcode::BitCount32, {initial_hi})));
|
||||
}
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(2),
|
||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
||||
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
|
||||
entry_ir.BitwiseOr(active_lane_count(), IR::U32(IR::Value(1u << 28u))));
|
||||
// Vulkan adds an indirect record's bases to the indices; the hardware passes them
|
||||
// only in the user SGPRs the packet patches. SH_NOP leaves those SGPRs unchanged;
|
||||
// it does not make the record's bases zero.
|
||||
const auto& offsets = options.input_info.vertex->indirect_offsets;
|
||||
const auto index = [&](uint8_t slot, IR::StageInputKind kind,
|
||||
IR::StageInputKind base_kind) {
|
||||
if (!options.input_info.vertex->indirect_draw) {
|
||||
return builtin(kind);
|
||||
}
|
||||
const auto base = builtin(base_kind);
|
||||
if (slot < options.user_data_count) {
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(options.user_data_base + slot),
|
||||
base);
|
||||
}
|
||||
return entry_ir.ISub(builtin(kind), base);
|
||||
};
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
builtin(IR::StageInputKind::VertexIndex));
|
||||
index(offsets.vertex, IR::StageInputKind::VertexIndex,
|
||||
IR::StageInputKind::BaseVertex));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
builtin(IR::StageInputKind::InstanceIndex));
|
||||
index(offsets.instance, IR::StageInputKind::InstanceIndex,
|
||||
IR::StageInputKind::BaseInstance));
|
||||
}
|
||||
}
|
||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -251,6 +251,8 @@ enum class StageInputKind {
|
||||
LocalInvocationId,
|
||||
LocalInvocationIndex,
|
||||
GlobalInvocationId,
|
||||
BaseVertex,
|
||||
BaseInstance,
|
||||
Parameter,
|
||||
};
|
||||
|
||||
@@ -541,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;
|
||||
}
|
||||
|
||||
@@ -175,6 +175,8 @@ void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||
break;
|
||||
case StageInputKind::BaseVertex: AddInput(info, kind, 0, 1, "gl_BaseVertex"); break;
|
||||
case StageInputKind::BaseInstance: AddInput(info, kind, 0, 1, "gl_BaseInstance"); break;
|
||||
case StageInputKind::PackedAncillary:
|
||||
case StageInputKind::Parameter: break;
|
||||
}
|
||||
@@ -444,6 +446,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
case StageInputKind::InstanceIndex:
|
||||
case StageInputKind::FrontFacing:
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
case StageInputKind::BaseVertex:
|
||||
case StageInputKind::BaseInstance:
|
||||
if (component != 0u) {
|
||||
return Fail("typed scalar builtin component is out of range");
|
||||
}
|
||||
@@ -511,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,6 +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) |
|
||||
(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) {
|
||||
@@ -692,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);
|
||||
|
||||
@@ -124,6 +124,17 @@ struct ShaderTessellationInputInfo {
|
||||
uint32_t output_topology = 0;
|
||||
};
|
||||
|
||||
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance
|
||||
// to. A packet can leave both slots unused even when its record contains nonzero offsets.
|
||||
struct ShaderIndirectOffsets {
|
||||
static constexpr uint8_t None = UINT8_MAX;
|
||||
|
||||
uint8_t vertex = None;
|
||||
uint8_t instance = None;
|
||||
|
||||
bool operator==(const ShaderIndirectOffsets&) const = default;
|
||||
};
|
||||
|
||||
struct ShaderVertexInputInfo {
|
||||
static constexpr int RES_MAX = 32;
|
||||
|
||||
@@ -146,8 +157,11 @@ struct ShaderVertexInputInfo {
|
||||
ShaderClipSpaceTransform clip_space;
|
||||
ShaderMeshInputInfo mesh;
|
||||
ShaderTessellationInputInfo tess;
|
||||
bool fetch_external = false;
|
||||
bool fetch_embedded = false;
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
// 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 {
|
||||
|
||||
+444
-1063
File diff suppressed because it is too large.
Load diff
+7
-2
@@ -28,7 +28,8 @@ struct DebugStats {
|
||||
uint64_t pool_committed = 0;
|
||||
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
||||
uint64_t gpu_page_entries = 0;
|
||||
uint64_t page_entries_total = 0; // Per side.
|
||||
uint64_t cpu_page_entries_total = 0;
|
||||
uint64_t gpu_page_entries_total = 0;
|
||||
};
|
||||
[[nodiscard]] DebugStats GetDebugStats();
|
||||
|
||||
@@ -125,7 +126,6 @@ static_assert(sizeof(KernelMemoryPoolBlockStats) == 16,
|
||||
"KernelMemoryPoolBlockStats struct size is incorrect");
|
||||
|
||||
void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func);
|
||||
void SetFlexibleMemorySize(uint64_t size);
|
||||
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
|
||||
int memory_type, int64_t* phys_addr_out, bool automatic = false);
|
||||
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
||||
@@ -225,6 +225,11 @@ bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size);
|
||||
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
|
||||
bool TestGuestBackingOutsideAddressSpace();
|
||||
uint64_t TestGuestBackingSize();
|
||||
uint64_t TestFlexibleBackingHighWater();
|
||||
uint64_t TestGuestFreeRangeVisits();
|
||||
uint64_t TestDirectMappingCount();
|
||||
uint64_t TestFlexibleMappingCount();
|
||||
uint64_t TestBackingBytesZeroed();
|
||||
bool TestGuestFreeRangeBounds();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -203,6 +203,9 @@ public:
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::memset(m_backing_base + backing_offset, 0, size);
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
g_test_backing_bytes_zeroed += size;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -287,6 +290,18 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProtectViews(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||
std::shared_lock lock(m_mutex);
|
||||
bool ok = true;
|
||||
ForEachSparseRange(
|
||||
m_maps, vaddr, size,
|
||||
[&](const auto&, uint64_t start, uint64_t bytes) {
|
||||
ok = ok && VirtualMemory::Protect(start, bytes, mode);
|
||||
},
|
||||
[&](uint64_t, uint64_t) { ok = false; });
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool MapFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
||||
VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) {
|
||||
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
||||
@@ -324,15 +339,11 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Unmap(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
||||
bool Unmap(uint64_t vaddr, uint64_t size) {
|
||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = false;
|
||||
}
|
||||
|
||||
const auto end = vaddr + size;
|
||||
std::vector<MapEntry> targets;
|
||||
{
|
||||
@@ -350,42 +361,36 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<MapEntry> removed;
|
||||
removed.reserve(targets.size());
|
||||
size_t removed = 0;
|
||||
auto rollback = [&] {
|
||||
while (removed != 0) {
|
||||
const auto& previous = targets[--removed];
|
||||
EXIT_IF(!MapFixed(previous.vaddr, previous.size, previous.backing_offset,
|
||||
previous.mode));
|
||||
}
|
||||
};
|
||||
for (const auto& entry: targets) {
|
||||
bool part_placeholder_preserved = false;
|
||||
if (!UnmapOne(entry.vaddr, entry.size, &part_placeholder_preserved)) {
|
||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
||||
}
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = false;
|
||||
}
|
||||
if (!UnmapOne(entry.vaddr, entry.size)) {
|
||||
rollback();
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(!part_placeholder_preserved);
|
||||
removed.push_back(entry);
|
||||
++removed;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
|
||||
// records this contiguous guest span as one free range, so normalize fragmented backing
|
||||
// views into one placeholder before publishing it there.
|
||||
if (removed.size() > 1 && !CoalescePlaceholders(vaddr, size)) {
|
||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
||||
}
|
||||
if (removed > 1 && !CoalescePlaceholders(vaddr, size)) {
|
||||
rollback();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnmapOne(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
||||
bool UnmapOne(uint64_t vaddr, uint64_t size) {
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
if (g_test_backing_store_unmaps_before_failure == 0) {
|
||||
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
||||
@@ -428,9 +433,7 @@ public:
|
||||
m_maps.emplace(old.vaddr, old);
|
||||
return false;
|
||||
}
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -451,9 +454,6 @@ public:
|
||||
ok;
|
||||
}
|
||||
if (ok) {
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -481,9 +481,6 @@ public:
|
||||
std::lock_guard lock(m_mutex);
|
||||
RemoveMappingsUnlocked(vaddr, size);
|
||||
|
||||
if (placeholder_preserved != nullptr) {
|
||||
*placeholder_preserved = true;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
@@ -634,8 +631,6 @@ private:
|
||||
|
||||
HANDLE m_handle = nullptr;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
[[nodiscard]] static uint64_t GetMappableTestSize() { return 0x4000; }
|
||||
|
||||
static int GetProtect(VirtualMemory::Mode mode) {
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: return PROT_READ;
|
||||
@@ -760,7 +755,7 @@ public:
|
||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
||||
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
||||
});
|
||||
@@ -864,11 +859,7 @@ public:
|
||||
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
||||
return false;
|
||||
}
|
||||
bool placeholder_preserved = false;
|
||||
if (!m_backing->Unmap(vaddr, size, &placeholder_preserved)) {
|
||||
return false;
|
||||
}
|
||||
if (!placeholder_preserved) {
|
||||
if (!m_backing->Unmap(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
||||
@@ -876,12 +867,13 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reservations live in VirtualRanges; these free-range checks only read the host placeholders.
|
||||
uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
alignment = std::max<uint64_t>(alignment != 0 ? alignment : PageSize(), PageSize());
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
if (search_addr == 0) {
|
||||
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
||||
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
||||
@@ -898,7 +890,7 @@ public:
|
||||
return 0;
|
||||
}
|
||||
alignment = std::max<uint64_t>(alignment, PageSize());
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindFreeAlignedUnlocked(search_start, search_end, size, alignment);
|
||||
}
|
||||
|
||||
@@ -906,19 +898,19 @@ public:
|
||||
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindContaining(vaddr, size) != m_free.end();
|
||||
}
|
||||
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
bool TestContainsFree(uint64_t vaddr, uint64_t size) {
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindContaining(vaddr, size) != m_free.end();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ReleaseFree(uint64_t vaddr, uint64_t size) {
|
||||
std::lock_guard lock(m_mutex);
|
||||
std::shared_lock lock(m_mutex);
|
||||
return FindContaining(vaddr, size) != m_free.end();
|
||||
}
|
||||
|
||||
@@ -1041,10 +1033,6 @@ private:
|
||||
return (value & (PageSize() - 1u)) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGranularityAligned(uint64_t value) const {
|
||||
return (value & (m_granularity - 1u)) == 0;
|
||||
}
|
||||
|
||||
bool ProtectMappedUnlocked(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||
const auto end = vaddr + size;
|
||||
auto it = m_mapped.upper_bound(vaddr);
|
||||
@@ -1055,8 +1043,20 @@ private:
|
||||
const auto& region = it->second;
|
||||
const auto range_start = std::max(vaddr, region.vaddr);
|
||||
const auto range_end = std::min(end, region.vaddr + region.size);
|
||||
if (range_start < range_end &&
|
||||
!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
||||
if (range_start >= range_end) {
|
||||
continue;
|
||||
}
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
// Rollback can split a backing view without changing the owner interval.
|
||||
// Windows protection calls must stay within each actual view.
|
||||
if (region.type == MappingType::Backing) {
|
||||
if (!m_backing->ProtectViews(range_start, range_end - range_start, mode)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1312,11 +1312,20 @@ private:
|
||||
}
|
||||
|
||||
uint64_t FindFreeAlignedUnlocked(uint64_t search_start, uint64_t search_end, uint64_t size,
|
||||
uint64_t alignment) {
|
||||
uint64_t alignment) const {
|
||||
if (search_start >= search_end || size > search_end - search_start) {
|
||||
return 0;
|
||||
}
|
||||
for (const auto& [base, range_size]: m_free) {
|
||||
// Include the predecessor because search_start can lie inside a free range.
|
||||
auto it = m_free.upper_bound(search_start);
|
||||
if (it != m_free.begin()) {
|
||||
--it;
|
||||
}
|
||||
for (; it != m_free.end() && it->first < search_end; ++it) {
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
g_test_free_range_visits.fetch_add(1, std::memory_order_relaxed);
|
||||
#endif
|
||||
const auto& [base, range_size] = *it;
|
||||
const auto candidate = AlignUp(std::max(base, search_start), alignment);
|
||||
if (candidate < search_end && size <= search_end - candidate &&
|
||||
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
||||
@@ -1332,8 +1341,7 @@ private:
|
||||
return m_free.end();
|
||||
}
|
||||
auto it = std::prev(next);
|
||||
auto end = it->first + it->second;
|
||||
return (vaddr >= it->first && vaddr + size <= end ? it : m_free.end());
|
||||
return GuestFreeRangeContains(it->first, it->second, vaddr, size) ? it : m_free.end();
|
||||
}
|
||||
|
||||
bool SplitUnlocked(std::map<uint64_t, uint64_t>::iterator it, uint64_t vaddr, uint64_t size) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -115,7 +115,17 @@
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||
<property name="toolTip"><string>Window resolution</string></property>
|
||||
<property name="toolTip"><string>Window resolution as WIDTHxHEIGHT; pick a preset or type a custom size. Non-16:9 sizes such as 21:9 show the game with black bars unless Stretch to window is on.</string></property>
|
||||
<property name="editable"><bool>true</bool></property>
|
||||
<property name="insertPolicy"><enum>QComboBox::NoInsert</enum></property>
|
||||
<item><property name="text"><string notr="true">1280x720</string></property></item>
|
||||
<item><property name="text"><string notr="true">1920x1080</string></property></item>
|
||||
<item><property name="text"><string notr="true">2560x1440</string></property></item>
|
||||
<item><property name="text"><string notr="true">3840x2160</string></property></item>
|
||||
<item><property name="text"><string notr="true">2560x1080</string></property></item>
|
||||
<item><property name="text"><string notr="true">3440x1440</string></property></item>
|
||||
<item><property name="text"><string notr="true">3840x1600</string></property></item>
|
||||
<item><property name="text"><string notr="true">5120x2160</string></property></item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
@@ -244,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>
|
||||
@@ -256,6 +272,12 @@
|
||||
<property name="text"><string>Auto-hide cursor</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_stretch">
|
||||
<property name="toolTip"><string>Stretch the game image to fill the window instead of keeping its aspect ratio with black bars</string></property>
|
||||
<property name="text"><string>Stretch to window</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_perf_overlay">
|
||||
<property name="toolTip"><string>Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view.</string></property>
|
||||
@@ -390,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>
|
||||
|
||||
@@ -21,10 +21,7 @@ inline QStringList EnumToList() {
|
||||
auto me = QMetaEnum::fromType<T>();
|
||||
int count = me.keyCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto key = QString(me.key(i));
|
||||
ret << (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit()
|
||||
? key.remove('R').toLower()
|
||||
: key);
|
||||
ret << QString(me.key(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -32,16 +29,12 @@ inline QStringList EnumToList() {
|
||||
template <class T>
|
||||
T TextToEnum(const QString& text) {
|
||||
auto me = QMetaEnum::fromType<T>();
|
||||
return static_cast<T>(me.keyToValue(
|
||||
((text.size() > 1 && text.at(0).isDigit()) ? 'R' + text.toUpper() : text).toUtf8().data()));
|
||||
return static_cast<T>(me.keyToValue(text.toUtf8().constData()));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
QString EnumToText(T value) {
|
||||
auto me = QMetaEnum::fromType<T>();
|
||||
auto key = QString(me.valueToKey(static_cast<int>(value)));
|
||||
return (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit() ? key.remove('R').toLower()
|
||||
: key);
|
||||
return QString(QMetaEnum::fromType<T>().valueToKey(static_cast<int>(value)));
|
||||
}
|
||||
|
||||
struct ControllerSettings {
|
||||
@@ -80,13 +73,10 @@ public:
|
||||
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
||||
|
||||
enum class Resolution {
|
||||
R1280X720,
|
||||
R1920X1080,
|
||||
R2560X1440,
|
||||
R3840X2160,
|
||||
};
|
||||
Q_ENUM(Resolution)
|
||||
static constexpr int MIN_SCREEN_SIZE = 320;
|
||||
static constexpr int MAX_SCREEN_SIZE = 16384;
|
||||
|
||||
static QString NormalizeResolution(const QString& text);
|
||||
|
||||
enum class ShaderOptimizationType { None, Size, Performance };
|
||||
Q_ENUM(ShaderOptimizationType)
|
||||
@@ -117,7 +107,7 @@ public:
|
||||
// Controller preferences always come from the global configuration.
|
||||
ControllerSettings controller;
|
||||
|
||||
Resolution screen_resolution = Resolution::R1280X720;
|
||||
QString screen_resolution = QStringLiteral("1280x720");
|
||||
QString user_name = "Kyty";
|
||||
int user_id = Config::DEFAULT_USER_ID;
|
||||
QString audio_input_device;
|
||||
@@ -126,6 +116,7 @@ public:
|
||||
bool fullscreen_enabled = false;
|
||||
bool hdr_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool stretch_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool readback_tiled_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
@@ -137,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;
|
||||
@@ -150,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;
|
||||
@@ -168,6 +161,7 @@ public:
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
hdr_enabled = other.hdr_enabled;
|
||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||
stretch_enabled = other.stretch_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
readback_tiled_images = other.readback_tiled_images;
|
||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||
@@ -179,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;
|
||||
@@ -192,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;
|
||||
|
||||
@@ -46,11 +46,15 @@ private:
|
||||
QLineEdit* m_addons_dir_edit = nullptr;
|
||||
QToolButton* m_remove_game_dir_button = nullptr;
|
||||
bool m_global_settings = false;
|
||||
bool m_geometry_restored = false;
|
||||
|
||||
protected:
|
||||
void Init(const Configuration& info);
|
||||
void InitGameDirectories();
|
||||
void AddGameDirectoryItem(const QString& dir);
|
||||
void MakeTabsScrollable();
|
||||
void GrowToFitContents();
|
||||
void ShowTab(QWidget* page);
|
||||
|
||||
void moveEvent(QMoveEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
QString Configuration::NormalizeResolution(const QString& text) {
|
||||
// Older settings stored the enum key, e.g. "R1920X1080".
|
||||
static const QRegularExpression re(QStringLiteral("^\\s*R?(\\d+)\\s*[xX]\\s*(\\d+)\\s*$"));
|
||||
const auto match = re.match(text);
|
||||
if (!match.hasMatch()) {
|
||||
return {};
|
||||
}
|
||||
const int width = match.captured(1).toInt();
|
||||
const int height = match.captured(2).toInt();
|
||||
if (width < MIN_SCREEN_SIZE || height < MIN_SCREEN_SIZE || width > MAX_SCREEN_SIZE ||
|
||||
height > MAX_SCREEN_SIZE) {
|
||||
return {};
|
||||
}
|
||||
return QStringLiteral("%1x%2").arg(width).arg(height);
|
||||
}
|
||||
|
||||
QVariantMap Configuration::GameSettings() const {
|
||||
QVariantMap values;
|
||||
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
|
||||
@@ -12,6 +30,7 @@ QVariantMap Configuration::GameSettings() const {
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
KYTY_CFG_SET(hdr_enabled);
|
||||
KYTY_CFG_SET(hide_cursor_enabled);
|
||||
KYTY_CFG_SET(stretch_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(readback_tiled_images);
|
||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||
@@ -23,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);
|
||||
@@ -36,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);
|
||||
@@ -48,7 +69,8 @@ QVariantMap Configuration::GameSettings() const {
|
||||
template <class Settings>
|
||||
void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
|
||||
KYTY_CFG_GET(screen_resolution);
|
||||
const auto resolution = NormalizeResolution(s.value("screen_resolution").toString());
|
||||
screen_resolution = resolution.isEmpty() ? QStringLiteral("1280x720") : resolution;
|
||||
user_name = s.value("user_name", user_name).toString();
|
||||
bool user_id_ok = false;
|
||||
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
|
||||
@@ -64,6 +86,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||
KYTY_CFG_GET(fullscreen_enabled);
|
||||
KYTY_CFG_GET(hdr_enabled);
|
||||
KYTY_CFG_GET(hide_cursor_enabled);
|
||||
KYTY_CFG_GET(stretch_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(readback_tiled_images);
|
||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||
@@ -75,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) {
|
||||
@@ -91,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 =
|
||||
@@ -134,7 +159,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error)
|
||||
if (!it.value().isString()) {
|
||||
return fail(tr("Game setting %1 must be a string.").arg(it.key()));
|
||||
}
|
||||
values.insert(it.key(), it.value().toString());
|
||||
const auto value = it.value().toString();
|
||||
values.insert(it.key(), it.key() == "screen_resolution" ? NormalizeResolution(value)
|
||||
: value);
|
||||
}
|
||||
|
||||
ReadGameSettingsValues(values);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QResizeEvent>
|
||||
#include <QScreen>
|
||||
#include <QScrollArea>
|
||||
#include <QSettings>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
@@ -122,10 +124,10 @@ static QString GameDirectoryKey(const QString& dir) {
|
||||
}
|
||||
|
||||
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog),
|
||||
m_info(info) {
|
||||
: QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
|
||||
m_ui(new Ui::ConfigurationEditDialog), m_info(info) {
|
||||
m_ui->setupUi(this);
|
||||
setMinimumWidth(width());
|
||||
MakeTabsScrollable();
|
||||
InitGameDirectories();
|
||||
m_ui->controller_group->setVisible(false);
|
||||
m_ui->settings_button->setVisible(false);
|
||||
@@ -170,9 +172,11 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
||||
|
||||
setSizeGripEnabled(true);
|
||||
|
||||
restoreGeometry(g_last_geometry);
|
||||
|
||||
Init(info);
|
||||
m_geometry_restored = restoreGeometry(g_last_geometry);
|
||||
if (!m_geometry_restored) {
|
||||
GrowToFitContents();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray ConfigurationEditDialog::g_last_geometry;
|
||||
@@ -248,7 +252,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
info.audio_input_device);
|
||||
}
|
||||
microphone->setCurrentIndex(microphone->findData(info.audio_input_device));
|
||||
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
|
||||
m_ui->comboBox_screen_resolution->setCurrentText(info.screen_resolution);
|
||||
ListInit(m_ui->comboBox_present_mode, info.present_mode);
|
||||
m_ui->comboBox_gpu->clear();
|
||||
m_ui->comboBox_gpu->addItem(tr("Auto"));
|
||||
@@ -285,6 +289,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||
m_ui->checkBox_stretch->setChecked(info.stretch_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||
@@ -293,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);
|
||||
@@ -306,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);
|
||||
@@ -413,8 +420,46 @@ void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs, const Q
|
||||
m_game_dirs_group->setVisible(true);
|
||||
m_ui->controller_group->setVisible(true);
|
||||
update_game_directory_buttons();
|
||||
if (!m_geometry_restored) {
|
||||
GrowToFitContents();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::MakeTabsScrollable() {
|
||||
auto* tabs = m_ui->settings_tabs;
|
||||
for (int i = 0; i < tabs->count(); i++) {
|
||||
auto* page = tabs->widget(i);
|
||||
auto title = tabs->tabText(i);
|
||||
auto* scroll = new QScrollArea;
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
tabs->removeTab(i);
|
||||
scroll->setWidget(page);
|
||||
tabs->insertTab(i, scroll, title);
|
||||
}
|
||||
tabs->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::GrowToFitContents() {
|
||||
layout()->activate();
|
||||
resize(size().expandedTo(minimumSizeHint()));
|
||||
auto* tabs = m_ui->settings_tabs;
|
||||
QSize content;
|
||||
for (int i = 0; i < tabs->count(); i++) {
|
||||
content = content.expandedTo(
|
||||
static_cast<QScrollArea*>(tabs->widget(i))->widget()->minimumSizeHint());
|
||||
}
|
||||
const QSize chrome = size() - tabs->currentWidget()->size();
|
||||
resize(size().expandedTo(content + chrome).boundedTo(screen()->availableGeometry().size()));
|
||||
}
|
||||
|
||||
void ConfigurationEditDialog::ShowTab(QWidget* page) {
|
||||
auto* tabs = m_ui->settings_tabs;
|
||||
for (int i = 0; i < tabs->count(); i++) {
|
||||
if (tabs->widget(i)->isAncestorOf(page)) {
|
||||
tabs->setCurrentIndex(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ConfigurationEditDialog::GetAddonsDirectory() const {
|
||||
@@ -474,13 +519,14 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
||||
info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt();
|
||||
}
|
||||
info.screen_resolution =
|
||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
||||
Configuration::NormalizeResolution(ui.comboBox_screen_resolution->currentText());
|
||||
info.present_mode =
|
||||
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||
info.stretch_enabled = ui.checkBox_stretch->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||
@@ -489,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();
|
||||
@@ -497,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();
|
||||
@@ -518,7 +566,7 @@ void ConfigurationEditDialog::save() {
|
||||
for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file,
|
||||
m_ui->lineEdit_cmd_dump_folder}) {
|
||||
if (field->isEnabled() && field->text().isEmpty()) {
|
||||
m_ui->settings_tabs->setCurrentWidget(m_ui->debug_tab);
|
||||
ShowTab(m_ui->debug_tab);
|
||||
field->setFocus();
|
||||
break;
|
||||
}
|
||||
@@ -540,6 +588,16 @@ void ConfigurationEditDialog::save() {
|
||||
tr("User ID cannot be 254 (everyone) or 255 (system)"));
|
||||
return;
|
||||
}
|
||||
if (Configuration::NormalizeResolution(m_ui->comboBox_screen_resolution->currentText())
|
||||
.isEmpty()) {
|
||||
ShowTab(m_ui->settings_tab);
|
||||
m_ui->comboBox_screen_resolution->setFocus();
|
||||
QMessageBox::critical(this, tr("Save failed"),
|
||||
tr("Screen resolution must be WIDTHxHEIGHT, each between %1 and %2")
|
||||
.arg(Configuration::MIN_SCREEN_SIZE)
|
||||
.arg(Configuration::MAX_SCREEN_SIZE));
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateInfo(m_info, *m_ui, m_global_settings);
|
||||
|
||||
|
||||
@@ -134,7 +134,8 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal.
|
||||
#endif
|
||||
|
||||
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/);
|
||||
main_dialog->setWindowFlags(Qt::Dialog | Qt::WindowMinMaxButtonsHint |
|
||||
Qt::WindowCloseButtonHint);
|
||||
|
||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||
Qt::QueuedConnection);
|
||||
@@ -240,7 +241,7 @@ static void AddFlag(QStringList& args, bool enabled, const char* name) {
|
||||
|
||||
static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
QStringList args;
|
||||
auto r = EnumToText(info.screen_resolution).split('x');
|
||||
auto r = Configuration::NormalizeResolution(info.screen_resolution).split('x');
|
||||
|
||||
if (r.size() != 2) {
|
||||
return {};
|
||||
@@ -266,6 +267,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
AddFlag(args, info.fullscreen_enabled, "--fullscreen");
|
||||
AddFlag(args, info.hdr_enabled, "--hdr");
|
||||
AddFlag(args, info.hide_cursor_enabled, "--hide-cursor");
|
||||
AddFlag(args, info.stretch_enabled, "--stretch");
|
||||
AddFlag(args, info.readback_linear_images, "--readback-linear-images");
|
||||
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
|
||||
AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers");
|
||||
@@ -274,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");
|
||||
@@ -289,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
|
||||
@@ -519,7 +519,20 @@ void GameController::SetDevices(int active_id) {
|
||||
m_trigger_state = {};
|
||||
}
|
||||
|
||||
// Input that changes no button or touch contact updates the newest unread sample, so a burst of
|
||||
// stick or sensor events between two reads cannot push unread button or touch changes out.
|
||||
void GameController::AddState() {
|
||||
if (m_states_num != 0) {
|
||||
const auto newest = (m_first_state + m_states_num - 1) % STATES_MAX;
|
||||
auto& last = m_states[newest];
|
||||
if (!m_obtained[newest] && last.buttons == m_state.buttons &&
|
||||
last.touch[0].down == m_state.touch[0].down &&
|
||||
last.touch[1].down == m_state.touch[1].down) {
|
||||
last = m_state;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_states_num >= STATES_MAX) {
|
||||
m_states_num = STATES_MAX - 1;
|
||||
m_first_state = (m_first_state + 1) % STATES_MAX;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+98
-67
@@ -13,6 +13,7 @@
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Audio {
|
||||
@@ -170,15 +171,10 @@ static std::atomic_uint64_t g_audioout2_next_context {1};
|
||||
static std::atomic_uint64_t g_audioout2_next_port {1};
|
||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||
|
||||
struct AudioOut2ContextState {
|
||||
bool used = false;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
uint32_t queue_depth = 4;
|
||||
uint32_t queued = 0;
|
||||
uint32_t num_grains = 512;
|
||||
uint64_t last_update = 0;
|
||||
int object_audio_handle = 0;
|
||||
};
|
||||
static constexpr uint32_t AUDIOOUT2_CHANNEL_PORTS_MAX = 32;
|
||||
static constexpr uint32_t AUDIOOUT2_OBJECT_PORTS_MAX = 512;
|
||||
static constexpr uint32_t AUDIOOUT2_PORT_SLOT_BITS = 10;
|
||||
static constexpr uint32_t AUDIOOUT2_CONTEXT_SLOT_BITS = 4;
|
||||
|
||||
struct AudioOut2PortStateEntry {
|
||||
bool used = false;
|
||||
@@ -196,6 +192,19 @@ struct AudioOut2PortStateEntry {
|
||||
bool attributes_locked = false;
|
||||
std::vector<uint8_t> pcm_data;
|
||||
};
|
||||
|
||||
struct AudioOut2ContextState {
|
||||
bool used = false;
|
||||
AudioOut2ContextHandle handle = 0;
|
||||
uint32_t queue_depth = 4;
|
||||
uint32_t queued = 0;
|
||||
uint32_t num_grains = 512;
|
||||
uint64_t last_update = 0;
|
||||
int object_audio_handle = 0;
|
||||
uint32_t max_ports = 0;
|
||||
// Channel slots precede object slots; each partition has its own creation limit.
|
||||
std::vector<AudioOut2PortStateEntry> ports;
|
||||
};
|
||||
struct AudioOut2SpeakerArrayState {
|
||||
bool used = false;
|
||||
uint32_t num_speakers = 0;
|
||||
@@ -223,13 +232,16 @@ struct AudioOut2Submission {
|
||||
static Common::Mutex g_audioout2_state_mutex;
|
||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
||||
static Common::Mutex g_audioout2_latency_mutex;
|
||||
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
||||
|
||||
static_assert(AUDIOOUT2_CHANNEL_PORTS_MAX + AUDIOOUT2_OBJECT_PORTS_MAX <=
|
||||
(1u << AUDIOOUT2_PORT_SLOT_BITS));
|
||||
static_assert(g_audioout2_contexts.size() == (1u << AUDIOOUT2_CONTEXT_SLOT_BITS));
|
||||
|
||||
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
||||
@@ -262,6 +274,11 @@ static AudioOut2Submission& audioout2_submission(const AudioOut2ContextState& co
|
||||
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
|
||||
}
|
||||
|
||||
// Ticks of a sixth of a microsecond: one 48 kHz sample lasts exactly 125 of them.
|
||||
static uint64_t audioout2_clock() {
|
||||
return LibKernel::KernelGetProcessTime() * 6;
|
||||
}
|
||||
|
||||
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
||||
const auto sample_count = (grains == 0 ? 512u : grains);
|
||||
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
||||
@@ -331,49 +348,51 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = LibKernel::KernelGetProcessTime();
|
||||
const auto now = audioout2_clock();
|
||||
if (state->last_update == 0 || state->queued == 0) {
|
||||
state->last_update = now;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto grain_micros = static_cast<uint64_t>(audioout2_grain_micros(state->num_grains));
|
||||
if (now <= state->last_update || grain_micros == 0) {
|
||||
if (now <= state->last_update) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto elapsed = now - state->last_update;
|
||||
const auto drained = std::min<uint64_t>(state->queued, elapsed / grain_micros);
|
||||
const auto grain = uint64_t {state->num_grains} * 125;
|
||||
const auto drained = std::min<uint64_t>(state->queued, (now - state->last_update) / grain);
|
||||
if (drained == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->queued -= static_cast<uint32_t>(drained);
|
||||
state->last_update += drained * grain_micros;
|
||||
state->last_update += drained * grain;
|
||||
if (state->queued == 0) {
|
||||
state->last_update = now;
|
||||
}
|
||||
}
|
||||
|
||||
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
||||
for (auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.handle == port) {
|
||||
return &state;
|
||||
}
|
||||
const auto context_slot =
|
||||
(port >> AUDIOOUT2_PORT_SLOT_BITS) & ((1u << AUDIOOUT2_CONTEXT_SLOT_BITS) - 1u);
|
||||
const auto port_slot = port & ((1u << AUDIOOUT2_PORT_SLOT_BITS) - 1u);
|
||||
auto& context = g_audioout2_contexts[context_slot];
|
||||
if (context.used && port_slot < context.ports.size()) {
|
||||
auto& state = context.ports[port_slot];
|
||||
if (state.used && state.handle == port) return &state;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
||||
const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||
const auto bytes_per_sample =
|
||||
(state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||
return static_cast<size_t>(state.samples_num) *
|
||||
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||
}
|
||||
|
||||
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.context == context.handle && !state.pcm_data.empty()) {
|
||||
for (const auto& state: context.ports) {
|
||||
if (state.used && !state.pcm_data.empty()) {
|
||||
const int handle = audioout2_port_type_is_object(state.port_type)
|
||||
? context.object_audio_handle
|
||||
: state.audio_handle;
|
||||
@@ -427,18 +446,21 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2S
|
||||
out.object_mix.clear();
|
||||
std::vector<AudioInternal::OutputParam> params;
|
||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (!state.used || state.context != ctx || state.pcm_data.empty()) {
|
||||
// A port's PCM covers one grain: the push consumes it, so a port the game stops feeding
|
||||
// falls silent.
|
||||
for (auto& state: context->ports) {
|
||||
if (!state.used || state.pcm_data.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (audioout2_port_type_is_object(state.port_type)) {
|
||||
audioout2_mix_object(out.object_mix, context->num_grains, state);
|
||||
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
const auto i = params.size();
|
||||
out.pcm[i] = state.pcm_data;
|
||||
out.pcm[i].swap(state.pcm_data);
|
||||
out.gains[i] = state.gains;
|
||||
params.push_back({state.audio_handle, out.pcm[i].data(), out.gains[i].data()});
|
||||
}
|
||||
state.pcm_data.clear();
|
||||
}
|
||||
if (!out.object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back({context->object_audio_handle, out.object_mix.data()});
|
||||
@@ -467,22 +489,28 @@ int KYTY_SYSV_ABI AudioOut2ContextResetParam(AudioOut2ContextParam* params) {
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
|
||||
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
||||
params->max_ports = 256;
|
||||
params->max_object_ports = 256;
|
||||
params->guarantee_object_ports = 0;
|
||||
params->queue_depth = 4;
|
||||
params->num_grains = 512;
|
||||
params->flags = 1;
|
||||
params->max_ports = 8;
|
||||
params->queue_depth = 1;
|
||||
params->num_grains = 256;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static bool audioout2_valid_context_capacity(const AudioOut2ContextParam& params) {
|
||||
return params.max_ports <= AUDIOOUT2_CHANNEL_PORTS_MAX &&
|
||||
params.max_object_ports <= AUDIOOUT2_OBJECT_PORTS_MAX &&
|
||||
params.guarantee_object_ports <= params.max_object_ports;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
||||
size_t* memory_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
||||
if (!audioout2_valid_context_capacity(*params)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
||||
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
||||
@@ -498,6 +526,9 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
||||
if (!audioout2_valid_context_capacity(*params)) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
||||
const int object_audio_handle =
|
||||
@@ -524,8 +555,10 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
||||
state->handle = *ctx;
|
||||
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
||||
state->num_grains = num_grains;
|
||||
state->last_update = LibKernel::KernelGetProcessTime();
|
||||
state->last_update = audioout2_clock();
|
||||
state->object_audio_handle = object_audio_handle;
|
||||
state->max_ports = params->max_ports;
|
||||
state->ports.resize(params->max_ports + params->max_object_ports);
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
||||
@@ -541,7 +574,7 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
||||
|
||||
std::array<int, 257> audio_handles {};
|
||||
std::array<int, AUDIOOUT2_CHANNEL_PORTS_MAX + 1> audio_handles {};
|
||||
size_t audio_handles_num = 0;
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
@@ -550,15 +583,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
||||
if (state->object_audio_handle > 0) {
|
||||
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
||||
}
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
for (auto& port_state: g_audioout2_ports) {
|
||||
if (port_state.used && port_state.context == ctx) {
|
||||
if (port_state.audio_handle > 0 && audio_handles_num < audio_handles.size()) {
|
||||
for (const auto& port_state: state->ports) {
|
||||
if (port_state.used && port_state.audio_handle > 0) {
|
||||
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
||||
}
|
||||
port_state = AudioOut2PortStateEntry {};
|
||||
}
|
||||
*state = AudioOut2ContextState {};
|
||||
}
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
@@ -586,9 +616,7 @@ int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
||||
g_audioout2_state_mutex.Lock();
|
||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||
audioout2_update_context_locked(state);
|
||||
}
|
||||
for (auto& port: g_audioout2_ports) {
|
||||
if (port.used && port.context == ctx) {
|
||||
for (auto& port: state->ports) {
|
||||
port.attributes_locked = false;
|
||||
}
|
||||
}
|
||||
@@ -612,7 +640,7 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
|
||||
sleep_micros = audioout2_grain_micros(state->num_grains);
|
||||
if (state->queued < state->queue_depth || use_device_clock) {
|
||||
if (state->queued == 0) {
|
||||
state->last_update = LibKernel::KernelGetProcessTime();
|
||||
state->last_update = audioout2_clock();
|
||||
}
|
||||
if (state->queued < state->queue_depth) {
|
||||
state->queued++;
|
||||
@@ -663,19 +691,17 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(port == nullptr);
|
||||
|
||||
const auto next_port = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||
const bool is_object = audioout2_port_type_is_object(params->port_type);
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
const auto* context_state = audioout2_find_context_locked(ctx);
|
||||
auto* context_state = audioout2_find_context_locked(ctx);
|
||||
if (context_state == nullptr) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
if (audioout2_port_type_is_object(params->port_type) &&
|
||||
(params->port_type != 0x100 || context_state->object_audio_handle == 0 ||
|
||||
params->sampling_freq != 48000 ||
|
||||
if (is_object && (params->port_type != 0x100 || params->sampling_freq != 48000 ||
|
||||
(audio_format != AudioInternal::Format::FloatMono &&
|
||||
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
@@ -688,14 +714,21 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
AudioOut2PortStateEntry* port_state = nullptr;
|
||||
for (auto& candidate: g_audioout2_ports) {
|
||||
if (!candidate.used) {
|
||||
port_state = &candidate;
|
||||
break;
|
||||
auto slots = std::span(context_state->ports);
|
||||
slots =
|
||||
is_object ? slots.subspan(context_state->max_ports) : slots.first(context_state->max_ports);
|
||||
const auto available = std::find_if(slots.begin(), slots.end(),
|
||||
[](const auto& candidate) { return !candidate.used; });
|
||||
if (available == slots.end()) {
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||
}
|
||||
}
|
||||
if (port_state != nullptr) {
|
||||
const auto serial = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||
const auto context_slot = static_cast<uint64_t>(context_state - g_audioout2_contexts.data());
|
||||
const auto port_slot = static_cast<uint64_t>(&*available - context_state->ports.data());
|
||||
const auto next_port = (serial << (AUDIOOUT2_PORT_SLOT_BITS + AUDIOOUT2_CONTEXT_SLOT_BITS)) |
|
||||
(context_slot << AUDIOOUT2_PORT_SLOT_BITS) | port_slot;
|
||||
auto* port_state = &*available;
|
||||
*port_state = AudioOut2PortStateEntry {};
|
||||
port_state->used = true;
|
||||
port_state->handle = next_port;
|
||||
@@ -707,23 +740,19 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
port_state->samples_num = samples_num;
|
||||
port_state->audio_format = audio_format;
|
||||
port_state->gains.fill(1.0f);
|
||||
}
|
||||
g_audioout2_state_mutex.Unlock();
|
||||
|
||||
if (port_state == nullptr) {
|
||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
int audio_handle = 0;
|
||||
|
||||
if (audio_format != AudioInternal::Format::Unknown &&
|
||||
!audioout2_port_type_is_object(params->port_type)) {
|
||||
if (audio_format != AudioInternal::Format::Unknown && !is_object) {
|
||||
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
||||
audio_format);
|
||||
}
|
||||
|
||||
g_audioout2_state_mutex.Lock();
|
||||
const bool reserved = port_state->used && port_state->handle == next_port;
|
||||
// Context destruction may have freed or replaced the slots while the backend opened.
|
||||
port_state = audioout2_find_port_locked(next_port);
|
||||
const bool reserved = port_state != nullptr;
|
||||
if (reserved) {
|
||||
port_state->audio_handle = audio_handle;
|
||||
}
|
||||
@@ -735,7 +764,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
||||
|
||||
*port = next_port;
|
||||
|
||||
if (next_port <= 16 || (next_port % 600) == 0) {
|
||||
if (serial <= 16 || (serial % 600) == 0) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
||||
"\t port = 0x%016" PRIx64 "\n"
|
||||
@@ -905,10 +934,12 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
||||
if (it == g_audioout2_users.end()) {
|
||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
}
|
||||
if (std::any_of(g_audioout2_ports.begin(), g_audioout2_ports.end(),
|
||||
for (const auto& context: g_audioout2_contexts) {
|
||||
if (std::any_of(context.ports.begin(), context.ports.end(),
|
||||
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
||||
return AUDIO_OUT2_ERROR_BUSY;
|
||||
}
|
||||
}
|
||||
g_audioout2_users.erase(it);
|
||||
return OK;
|
||||
}
|
||||
|
||||
+24
-6
@@ -39,7 +39,7 @@ struct Global {
|
||||
|
||||
struct Shadow {
|
||||
uintptr_t start = 0;
|
||||
int count = 0;
|
||||
uint64_t covered_bytes = 0;
|
||||
};
|
||||
|
||||
struct AsanContext {
|
||||
@@ -50,6 +50,14 @@ struct AsanContext {
|
||||
static uint32_t g_fake_stack = 0;
|
||||
static AsanContext* g_ctx = nullptr;
|
||||
|
||||
// Allocation and release callbacks can partition the same guest range differently.
|
||||
static uint64_t ShadowCoverage(uintptr_t page, uintptr_t min_addr, uintptr_t max_addr) {
|
||||
const auto guest_start = (page - 0x10000000000ull) << 3u;
|
||||
const auto guest_end = guest_start + 0x80000ull - 1;
|
||||
return std::min<uintptr_t>(max_addr, guest_end) - std::max<uintptr_t>(min_addr, guest_start) +
|
||||
1;
|
||||
}
|
||||
|
||||
static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
EXIT_IF(g_ctx == nullptr);
|
||||
|
||||
@@ -67,10 +75,11 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
static_cast<uint64_t>(min_shadow), static_cast<uint64_t>(size));
|
||||
|
||||
for (uintptr_t page = min_shadow; page <= max_shadow; page += page_size) {
|
||||
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||
[page](const Shadow& s) { return s.start == page; });
|
||||
if (it != g_ctx->shadows.end()) {
|
||||
it->count++;
|
||||
it->covered_bytes += bytes;
|
||||
} else {
|
||||
if (!Common::VirtualMemory::AllocFixed(page, page_size,
|
||||
Common::VirtualMemory::Mode::ReadWrite)) {
|
||||
@@ -79,7 +88,7 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
|
||||
Shadow s;
|
||||
s.start = page;
|
||||
s.count = 1;
|
||||
s.covered_bytes = bytes;
|
||||
g_ctx->shadows.push_back(s);
|
||||
}
|
||||
}
|
||||
@@ -105,14 +114,15 @@ static void FreeShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||
[page](const Shadow& s) { return s.start == page; });
|
||||
if (it != g_ctx->shadows.end()) {
|
||||
it->count--;
|
||||
|
||||
if (it->count <= 0) {
|
||||
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||
if (bytes >= it->covered_bytes) {
|
||||
if (!Common::VirtualMemory::Free(page)) {
|
||||
EXIT("can't free shadow memory\n");
|
||||
}
|
||||
|
||||
g_ctx->shadows.erase(it);
|
||||
} else {
|
||||
it->covered_bytes -= bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +136,14 @@ static void KernelFree(uintptr_t addr, size_t size) {
|
||||
FreeShadow(addr, addr + size - 1);
|
||||
}
|
||||
|
||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||
void TestRegisterMemoryCallbacks() {
|
||||
EXIT_IF(g_ctx != nullptr);
|
||||
g_ctx = new AsanContext;
|
||||
LibKernel::Memory::RegisterCallbacks(KernelAlloc, KernelFree);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void KYTY_SYSV_ABI asan_init() {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
+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
|
||||
+31
-14
@@ -615,7 +615,12 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id();
|
||||
const auto root = save_user_directory(title_id, cond->user_id);
|
||||
std::vector<std::string> dir_list;
|
||||
struct Found {
|
||||
uint64_t key = 0;
|
||||
std::string name;
|
||||
auto operator<=>(const Found&) const = default;
|
||||
};
|
||||
std::vector<Found> dir_list;
|
||||
|
||||
if (Common::File::IsDirectoryExisting(root)) {
|
||||
for (const auto& entry: Common::File::GetDirEntries(root)) {
|
||||
@@ -625,22 +630,34 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' ||
|
||||
dir_name_match(Common::ToLower(entry.name).c_str(),
|
||||
Common::ToLower(std::string(cond->dir_name->data)).c_str())) {
|
||||
dir_list.push_back(entry.name);
|
||||
dir_list.push_back({.name = entry.name});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(dir_list.begin(), dir_list.end(), [](const std::string& a, const std::string& b) {
|
||||
return std::strcmp(a.c_str(), b.c_str()) < 0;
|
||||
});
|
||||
|
||||
// A save whose metadata cannot be read sorts as 0.
|
||||
for (auto& found: dir_list) {
|
||||
const auto directory = root / found.name;
|
||||
switch (cond->key) {
|
||||
case SaveDataSortKey::UserParam: {
|
||||
SaveDataParam param {};
|
||||
read_save_param(save_param_path(directory), ¶m);
|
||||
found.key = param.user_param;
|
||||
break;
|
||||
}
|
||||
case SaveDataSortKey::Blocks:
|
||||
case SaveDataSortKey::FreeBlocks: read_save_blocks(directory, &found.key); break;
|
||||
case SaveDataSortKey::Mtime:
|
||||
found.key = static_cast<uint64_t>(newest_save_time(directory));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
// Directory names break ties; descending order reverses both.
|
||||
std::sort(dir_list.begin(), dir_list.end());
|
||||
if (cond->order == SaveDataSortOrder::Descent) {
|
||||
std::vector<std::string> reversed;
|
||||
for (size_t i = dir_list.size(); i > 0; i--) {
|
||||
reversed.push_back(dir_list[i - 1]);
|
||||
}
|
||||
dir_list = reversed;
|
||||
std::reverse(dir_list.begin(), dir_list.end());
|
||||
}
|
||||
|
||||
auto max_count =
|
||||
@@ -651,9 +668,9 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
|
||||
for (size_t i = 0; i < max_count; i++) {
|
||||
std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s",
|
||||
dir_list[i].c_str());
|
||||
dir_list[i].name.c_str());
|
||||
if (result->params != nullptr) {
|
||||
const int status = load_save_param(root / dir_list[i], &result->params[i]);
|
||||
const int status = load_save_param(root / dir_list[i].name, &result->params[i]);
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
@@ -661,7 +678,7 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
if (result->infos != nullptr) {
|
||||
auto& info = result->infos[i];
|
||||
info = {};
|
||||
const int status = read_save_blocks(root / dir_list[i], &info.blocks);
|
||||
const int status = read_save_blocks(root / dir_list[i].name, &info.blocks);
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
+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"
|
||||
@@ -35,7 +36,7 @@ public:
|
||||
bool GetParamInt(const char* name, int32_t* value);
|
||||
bool GetParamString(const char* name, std::string* value);
|
||||
bool GetParamString(const char* name, char* value, size_t value_size);
|
||||
uint64_t GetFlexibleMemorySize();
|
||||
MemoryParams GetMemoryParams();
|
||||
|
||||
void DbgPrint();
|
||||
|
||||
@@ -61,7 +62,7 @@ private:
|
||||
Common::Mutex m_mutex;
|
||||
bool m_opened = false;
|
||||
std::vector<Param> m_params;
|
||||
uint64_t m_flexible_memory_size = 0;
|
||||
MemoryParams m_memory_params;
|
||||
};
|
||||
|
||||
class PlayGo {
|
||||
@@ -90,6 +91,7 @@ private:
|
||||
};
|
||||
|
||||
struct SystemContent {
|
||||
bool trinity_mode = false;
|
||||
std::filesystem::path psf_path;
|
||||
Psf psf;
|
||||
std::filesystem::path playgo_path;
|
||||
@@ -172,11 +174,21 @@ void Psf::OpenJson(const std::filesystem::path& file_name) {
|
||||
add_int_param("userDefinedParam3", "USER_DEFINED_PARAM_3");
|
||||
add_int_param("userDefinedParam4", "USER_DEFINED_PARAM_4");
|
||||
|
||||
auto read_size = [](const nlohmann::ordered_json& object, const char* key, uint64_t* size,
|
||||
uint64_t unit = 1) {
|
||||
if (auto it = object.find(key); it != object.end()) {
|
||||
*size = it->get<uint64_t>() * unit;
|
||||
}
|
||||
};
|
||||
if (auto kernel = param_json.find("kernel");
|
||||
kernel != param_json.end() && kernel->is_object()) {
|
||||
if (auto it = kernel->find("flexibleMemorySize"); it != kernel->end()) {
|
||||
m_flexible_memory_size = it->get<uint64_t>();
|
||||
read_size(*kernel, "flexibleMemorySize", &m_memory_params.flexible_size);
|
||||
read_size(*kernel, "cpuPageTableSize", &m_memory_params.cpu_table_memory);
|
||||
read_size(*kernel, "gpuPageTableSize", &m_memory_params.gpu_table_memory);
|
||||
}
|
||||
if (auto amm = param_json.find("amm"); amm != param_json.end() && amm->is_object()) {
|
||||
read_size(*amm, "pagetableMemorySizeInMib", &m_memory_params.amm_table_memory,
|
||||
1024ull * 1024ull);
|
||||
}
|
||||
|
||||
auto localized = param_json.find("localizedParameters");
|
||||
@@ -227,7 +239,7 @@ void Psf::Close() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
m_params.clear();
|
||||
m_flexible_memory_size = 0;
|
||||
m_memory_params = {};
|
||||
m_opened = false;
|
||||
}
|
||||
|
||||
@@ -339,10 +351,10 @@ bool Psf::GetParamString(const char* name, char* value, size_t value_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t Psf::GetFlexibleMemorySize() {
|
||||
MemoryParams Psf::GetMemoryParams() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
return m_flexible_memory_size;
|
||||
return m_memory_params;
|
||||
}
|
||||
|
||||
PlayGo::~PlayGo() {
|
||||
@@ -593,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";
|
||||
|
||||
@@ -616,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;
|
||||
@@ -646,10 +670,10 @@ bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_
|
||||
return sc->psf.GetParamString(name, value, value_size);
|
||||
}
|
||||
|
||||
uint64_t SystemContentGetFlexibleMemorySize() {
|
||||
MemoryParams SystemContentGetMemoryParams() {
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
return sc->psf.GetFlexibleMemorySize();
|
||||
return sc->psf.GetMemoryParams();
|
||||
}
|
||||
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path) {
|
||||
|
||||
@@ -11,10 +11,20 @@
|
||||
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);
|
||||
uint64_t SystemContentGetFlexibleMemorySize();
|
||||
// Memory sizes a game's param.json sets, in bytes; 0 where it sets none.
|
||||
struct MemoryParams {
|
||||
uint64_t flexible_size = 0;
|
||||
uint64_t cpu_table_memory = 0;
|
||||
uint64_t gpu_table_memory = 0;
|
||||
uint64_t amm_table_memory = 0;
|
||||
};
|
||||
|
||||
MemoryParams SystemContentGetMemoryParams();
|
||||
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path);
|
||||
bool SystemContentGetChunksNum(uint32_t* num);
|
||||
|
||||
|
||||
@@ -70,13 +70,18 @@ static void PrintUsage() {
|
||||
::printf(" --hdr Enable HDR10 output when supported.\n");
|
||||
::printf(
|
||||
" --hide-cursor Hide the cursor after 2 s idle. Default: off.\n");
|
||||
::printf(
|
||||
" --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");
|
||||
@@ -199,10 +204,13 @@ static constexpr Flag FLAGS[] = {
|
||||
{"--fullscreen", &ConfigOptions::fullscreen_enabled},
|
||||
{"--hdr", &ConfigOptions::hdr_enabled},
|
||||
{"--hide-cursor", &ConfigOptions::hide_cursor_enabled},
|
||||
{"--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},
|
||||
|
||||
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user