mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e51f252b75 | ||
|
|
0f1ff37f23 | ||
|
|
da33a1c4a3 | ||
|
|
f53f0ca483 | ||
|
|
7b6bfdec64 | ||
|
|
a88181b897 | ||
|
|
041874f663 | ||
|
|
496432bbb4 | ||
|
|
bec67b7863 | ||
|
|
a22a9d9348 | ||
|
|
81f312638f | ||
|
|
f1f930e06c | ||
|
|
e0b682e4b2 | ||
|
|
16b83a034c | ||
|
|
cf268af839 | ||
|
|
fd2e15ee73 | ||
|
|
4e8d8c849d | ||
|
|
1e438136fa | ||
|
|
313b60e922 | ||
|
|
cda236dbb3 | ||
|
|
1216fa36cf | ||
|
|
497166009c | ||
|
|
0fb3ac5605 | ||
|
|
e056d55dd5 | ||
|
|
1371219f5c | ||
|
|
cf423df624 | ||
|
|
5ce4f08364 | ||
|
|
7e1c2d1532 | ||
|
|
0ec43a8d3d | ||
|
|
bfdbba3aa4 | ||
|
|
f7ae6e0a83 | ||
|
|
3585cd8f03 | ||
|
|
ff54608569 | ||
|
|
6bb68a3fd9 | ||
|
|
93185953f1 | ||
|
|
7855c980cb | ||
|
|
5a705dd15f | ||
|
|
e73324c2e0 |
No files matched your search
@@ -112,12 +112,15 @@ jobs:
|
||||
- name: Build
|
||||
shell: cmd
|
||||
run: |
|
||||
cmake --build _Build/windows --target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests --parallel
|
||||
cmake --build _Build/windows --target launcher ^
|
||||
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
|
||||
resource_tracking_tests scalar_provenance_tests ^
|
||||
resource_materialization_tests shader_vertex_metadata_tests --parallel
|
||||
|
||||
- name: Test
|
||||
shell: cmd
|
||||
run: |
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|virtual_memory_allocation)$"
|
||||
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$"
|
||||
|
||||
- name: Install
|
||||
shell: cmd
|
||||
@@ -198,14 +201,16 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --build _Build/macos \
|
||||
--target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
|
||||
--target launcher audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/macos --output-on-failure \
|
||||
-R '^(audio_out2_port|ime_dialog|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
@@ -379,14 +384,16 @@ jobs:
|
||||
run: |
|
||||
cmake --build _Build/linux \
|
||||
--target launcher page_manager_tests memory_tracker_tests \
|
||||
audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
|
||||
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
|
||||
resource_tracking_tests scalar_provenance_tests \
|
||||
resource_materialization_tests shader_vertex_metadata_tests \
|
||||
--parallel
|
||||
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
ctest --test-dir _Build/linux --output-on-failure \
|
||||
-R '^(audio_out2_port|ime_dialog|page_manager|memory_tracker|virtual_memory_allocation)$'
|
||||
-R '^(audio_out2_port|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
|
||||
|
||||
- name: Install
|
||||
shell: bash
|
||||
|
||||
@@ -301,6 +301,7 @@ set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||
set(gpu_blit_shader_sources
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
||||
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
|
||||
"${gpu_tiler_shader_dir}/gpu_video_out_overlay.frag"
|
||||
)
|
||||
foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
||||
get_filename_component(shader_name "${shader_source}" NAME_WE)
|
||||
@@ -411,6 +412,11 @@ add_executable(bit_array_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(lru_cache_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/LruCacheTests.cpp"
|
||||
)
|
||||
target_include_directories(lru_cache_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
||||
@@ -465,6 +471,19 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/Ngs2SamplerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
||||
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
||||
)
|
||||
target_link_libraries(pad_haptics_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(pad_haptics_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||
)
|
||||
@@ -548,6 +567,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||
@@ -555,6 +575,8 @@ if(BUILD_TESTING)
|
||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
@@ -611,6 +633,7 @@ if(BUILD_TESTING)
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
bit_array_tests
|
||||
lru_cache_tests
|
||||
shader_vertex_metadata_tests
|
||||
resource_materialization_tests
|
||||
resource_tracking_tests
|
||||
@@ -620,6 +643,9 @@ if(BUILD_TESTING)
|
||||
shader_recompiler_compute_tests
|
||||
virtual_memory_allocation_tests
|
||||
audio_out2_port_tests
|
||||
ngs2_sampler_tests
|
||||
pad_haptics_tests
|
||||
save_data_memory_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
## Contributing
|
||||
|
||||
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
|
||||
issues first, then use the **Game Emulation Bug Report** template and attach the complete log file.
|
||||
issues first, then use the **Game Emulation Status Report** template and attach the complete log file.
|
||||
|
||||
Code contributions should be focused, build successfully on the platforms they touch, and include
|
||||
relevant tests where practical. Windows is the primary target, so a change that alters shared code
|
||||
@@ -132,7 +132,7 @@ the Vulkan/SPIR-V validation rules.
|
||||
### Build requirements (Windows)
|
||||
|
||||
- Git
|
||||
- CMake 3.12 or newer
|
||||
- CMake 3.22.1 or newer
|
||||
- Ninja
|
||||
- Visual Studio 2022 or Build Tools 2022 with the **Desktop development with C++** workload and
|
||||
**C++ Clang tools for Windows** component
|
||||
@@ -178,7 +178,7 @@ sudo apt-get install --no-install-recommends \
|
||||
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
|
||||
```
|
||||
|
||||
Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution packages
|
||||
Qt 6 (Concurrent, Network, Widgets) is required for the launcher — either the distribution packages
|
||||
(`qt6-base-dev`) or an official Qt installation.
|
||||
|
||||
```bash
|
||||
@@ -197,6 +197,18 @@ The install step copies the Qt libraries and plugins next to the binaries, so
|
||||
from the pinned [KytyPS5 FFmpeg core](https://github.com/KytyPS5/ext-ffmpeg-core)
|
||||
release, including VP9 and WebM support. System FFmpeg packages are not required.
|
||||
|
||||
To build `kyty_emulator` and the `kyty_tests` target without Qt, use a separate build directory:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake -S . -B _Build/linux-no-qt -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DKYTY_BUILD_LAUNCHER=OFF
|
||||
|
||||
cmake --build _Build/linux-no-qt --target kyty_emulator kyty_tests --parallel
|
||||
```
|
||||
|
||||
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
|
||||
time.
|
||||
|
||||
|
||||
@@ -1205,8 +1205,9 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
}
|
||||
break;
|
||||
case 0x02:
|
||||
case 0x04:
|
||||
if constexpr (sizeof(T) == sizeof(uint32_t)) {
|
||||
if (eop_event_type == 0x2f && event_index == 0x06) {
|
||||
if (event_write_source == 0x02 && eop_event_type == 0x2f && event_index == 0x06) {
|
||||
switch (cache_action) {
|
||||
case 0x00: write32(false); return;
|
||||
case 0x38: write32(true); return;
|
||||
@@ -1214,6 +1215,9 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (event_write_source == 0x04) {
|
||||
value = Sync::ReadReferenceClock();
|
||||
}
|
||||
auto write64 = [&](bool with_writeback) {
|
||||
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
|
||||
std::memcpy(dst, &value, sizeof(value));
|
||||
@@ -1270,7 +1274,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
case 0x14:
|
||||
case 0x28:
|
||||
if (((eop_event_type == 0x04 || eop_event_type == 0x28) &&
|
||||
event_index == 0x05 && !with_interrupt) ||
|
||||
event_index == 0x05) ||
|
||||
(event_index == 0x00)) {
|
||||
write64(true);
|
||||
return;
|
||||
@@ -1302,45 +1306,6 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x04:
|
||||
if constexpr (sizeof(T) == sizeof(uint64_t)) {
|
||||
const auto clock = Sync::ReadReferenceClock();
|
||||
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
|
||||
std::memcpy(dst, &clock, sizeof(clock));
|
||||
switch (cache_action) {
|
||||
case 0x00:
|
||||
if ((eop_event_type == 0x04 && event_index == 0x05) ||
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterrupt64(
|
||||
m_submit_id, command, dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounter(m_submit_id, command,
|
||||
dst, clock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x38:
|
||||
if ((eop_event_type == 0x04 &&
|
||||
(event_index == 0x00 || event_index == 0x05)) ||
|
||||
(eop_event_type == 0x28 && event_index == 0x00)) {
|
||||
if (with_interrupt) {
|
||||
Sync::WriteAtEndOfPipeWithInterruptWriteBack64(
|
||||
m_submit_id, command, dst, clock, m_interrupt_event_id,
|
||||
interrupt_context_id);
|
||||
} else {
|
||||
Sync::WriteAtEndOfPipeClockCounterWithWriteBack(
|
||||
m_submit_id, command, dst, clock);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
@@ -1537,8 +1502,9 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
ProcessorScope processor_scope(*this);
|
||||
|
||||
m_renderer.GetVideoOut().PrepareFlip(request_id, command);
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[this, request_id] { m_renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
GetScheduler().Flush();
|
||||
m_renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
}
|
||||
|
||||
void CommandProcessor::SynchronizeGpu() {
|
||||
|
||||
@@ -1213,20 +1213,12 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
|
||||
total_size = {};
|
||||
if (width == 0 || height == 0 || slices == 0 || levels != 1 || num_fragments_log2 != 0 ||
|
||||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
|
||||
static bool TileGetColorMetadataSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t block_width, uint32_t block_height,
|
||||
TileSizeAlign& total_size) {
|
||||
if (width == 0 || height == 0 || slices == 0) {
|
||||
return false;
|
||||
}
|
||||
// Gen5 color metadata uses 4 KiB blocks, with one byte per 256 bytes of color data.
|
||||
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
|
||||
const uint32_t block_width = 1u << ((coverage_bits + 1u) / 2u);
|
||||
const uint32_t block_height = 1u << (coverage_bits / 2u);
|
||||
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
|
||||
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
|
||||
const uint64_t blocks = blocks_x * blocks_y;
|
||||
@@ -1238,36 +1230,27 @@ bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size) {
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
|
||||
total_size = {};
|
||||
if (width == 0 || height == 0 || levels == 0 || levels > 16 ||
|
||||
pitch != TileGetRenderTargetPitch(width, bytes_per_element)) {
|
||||
if (levels != 1 || num_fragments_log2 != 0 ||
|
||||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t max_levels = 1;
|
||||
uint32_t max_dimension = std::max(width, height);
|
||||
while (max_dimension > 1) {
|
||||
max_dimension >>= 1u;
|
||||
max_levels++;
|
||||
}
|
||||
if (levels > max_levels) {
|
||||
return false;
|
||||
}
|
||||
auto format = Prospero::BufferFormat::kInvalid;
|
||||
switch (bytes_per_element) {
|
||||
case 1: format = Prospero::BufferFormat::k8UNorm; break;
|
||||
case 2: format = Prospero::BufferFormat::k16UNorm; break;
|
||||
case 4: format = Prospero::BufferFormat::k32Float; break;
|
||||
case 8: format = Prospero::BufferFormat::k16_16_16_16Float; break;
|
||||
case 16: format = Prospero::BufferFormat::k32_32_32_32Float; break;
|
||||
default: return false;
|
||||
}
|
||||
TileGetTextureSize(format, width, height, levels, Prospero::TileMode::kRenderTarget,
|
||||
&total_size, level_sizes, padded_size);
|
||||
return total_size.size != 0 && total_size.align == 65536;
|
||||
// Gen5 DCC uses 4 KiB blocks, with one byte per 256 bytes of color data.
|
||||
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
|
||||
return TileGetColorMetadataSize(width, height, slices, 1u << ((coverage_bits + 1u) / 2u),
|
||||
1u << (coverage_bits / 2u), total_size);
|
||||
}
|
||||
|
||||
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
|
||||
TileSizeAlign& total_size) {
|
||||
total_size = {};
|
||||
// Gen5 CMASK stores four bits per 8x8 tile in 4 KiB blocks covering 1024x512 pixels.
|
||||
return levels == 1 && TileGetColorMetadataSize(width, height, slices, 1024, 512, total_size);
|
||||
}
|
||||
|
||||
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
|
||||
|
||||
@@ -126,10 +126,8 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0);
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size);
|
||||
bool TileGetCmaskSize(uint32_t width, uint32_t height, uint32_t slices, uint32_t levels,
|
||||
TileSizeAlign& total_size);
|
||||
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
|
||||
uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size,
|
||||
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
|
||||
|
||||
@@ -29,6 +29,7 @@ struct GraphicContext {
|
||||
bool memory_budget_ext_enabled = false;
|
||||
bool compute_subgroup_size_control_enabled = false;
|
||||
bool sample_rate_shading_enabled = false;
|
||||
// bool fp64_denorm_preserve = false; // Temporarily disabled.
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
|
||||
+47
-72
@@ -33,24 +33,27 @@ namespace {
|
||||
|
||||
constexpr uint64_t NumFramesBeforeRemoval = 32;
|
||||
|
||||
[[nodiscard]] bool DecodeDccClear(const TextureCache::ImageDesc& desc, uint8_t code,
|
||||
[[nodiscard]] bool DecodeColorClear(const TextureCache::ImageDesc& desc, uint8_t code,
|
||||
vk::ClearColorValue& clear) {
|
||||
const auto& metadata = desc.info.metadata;
|
||||
const auto format = desc.view_info.format;
|
||||
const bool cmask = metadata.kind == ImageMetadataKind::Cmask;
|
||||
if (cmask ? code == 0 : code == 0x20) {
|
||||
// Register clears belong to the color buffer; the texture pipe cannot decode them.
|
||||
return desc.type == TextureCache::BindingType::RenderTarget &&
|
||||
metadata.clear_register_valid &&
|
||||
DecodePackedColorClear(format, metadata.clear_word, clear);
|
||||
}
|
||||
if (cmask) {
|
||||
return false;
|
||||
}
|
||||
switch (code) {
|
||||
case 0x00:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
case 0x80:
|
||||
case 0xc0: break;
|
||||
default: return false;
|
||||
}
|
||||
const auto& metadata = desc.info.metadata;
|
||||
const auto format = desc.view_info.format;
|
||||
if (code == 0x20) {
|
||||
// Clear-to-register is a color-buffer operation; the texture pipe cannot decode it.
|
||||
return desc.type == TextureCache::BindingType::RenderTarget &&
|
||||
metadata.dcc_clear_register_valid &&
|
||||
DecodePackedColorClear(format, metadata.dcc_clear_word, clear);
|
||||
}
|
||||
clear = {};
|
||||
if (code == 0x00) {
|
||||
return true;
|
||||
@@ -107,36 +110,6 @@ constexpr uint64_t NumFramesBeforeRemoval = 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* BindingTypeName(TextureCache::BindingType type) {
|
||||
switch (type) {
|
||||
case TextureCache::BindingType::Texture: return "Texture";
|
||||
case TextureCache::BindingType::Storage: return "StorageTexture";
|
||||
case TextureCache::BindingType::RenderTarget: return "ColorTarget";
|
||||
case TextureCache::BindingType::DepthTarget: return "DepthTarget";
|
||||
case TextureCache::BindingType::VideoOut: return "VideoOut";
|
||||
}
|
||||
return "Image";
|
||||
}
|
||||
|
||||
void NameImageBinding(GraphicContext& graphics, Image& image, vk::ImageView view,
|
||||
TextureCache::BindingType type, const ImageViewInfo& view_info) {
|
||||
const auto* role = BindingTypeName(type);
|
||||
SetVulkanObjectNameF(
|
||||
graphics.device, image.backing.image,
|
||||
"Kyty.{}.Image[guest=0x{:016x} size=0x{:x} extent={}x{}x{} format={} mips={} layers={} "
|
||||
"samples={}]",
|
||||
role, image.info.data.address, image.info.data.size, image.info.extent.width,
|
||||
image.info.extent.height, image.info.extent.depth,
|
||||
static_cast<uint32_t>(image.info.pixel_format), image.info.resources.levels,
|
||||
image.info.resources.layers, image.info.samples);
|
||||
SetVulkanObjectNameF(
|
||||
graphics.device, view,
|
||||
"Kyty.{}.View[guest=0x{:016x} format={} aspect=0x{:x} mip={}+{} layer={}+{}]", role,
|
||||
image.info.data.address, static_cast<uint32_t>(view_info.format),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(view_info.aspect), view_info.base_level,
|
||||
view_info.level_count, view_info.base_layer, view_info.layer_count);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<vk::BufferImageCopy> BuildDepthCopies(const ImageInfo& info,
|
||||
uint64_t slice_stride,
|
||||
vk::ImageAspectFlags aspect) {
|
||||
@@ -223,7 +196,7 @@ bool TextureCache::SameBacking(const ImageInfo& cached, const ImageInfo& request
|
||||
return false;
|
||||
}
|
||||
if (!ImageViewOps::FormatsCompatible(cached.pixel_format, requested.pixel_format) ||
|
||||
(cached.type != requested.type && requested.extent != vk::Extent3D {1, 1, 1})) {
|
||||
cached.type != requested.type) {
|
||||
return false;
|
||||
}
|
||||
if (exact_format && cached.pixel_format != requested.pixel_format) {
|
||||
@@ -669,9 +642,12 @@ void TextureCache::CopyImage(ImageId destination_id, ImageId source_id) {
|
||||
const bool source_depth = source.info.IsDepth();
|
||||
const bool dest_depth = destination.info.IsDepth();
|
||||
const bool direct_copy =
|
||||
source.backing.format == destination.backing.format ||
|
||||
(!source_depth && !dest_depth &&
|
||||
vk::blockSize(source.backing.format) == vk::blockSize(destination.backing.format));
|
||||
(source.backing.image_type == destination.backing.image_type ||
|
||||
(source.backing.image_type != vk::ImageType::e1D &&
|
||||
destination.backing.image_type != vk::ImageType::e1D)) &&
|
||||
(source.backing.format == destination.backing.format ||
|
||||
(!source_depth && !dest_depth &&
|
||||
vk::blockSize(source.backing.format) == vk::blockSize(destination.backing.format)));
|
||||
if (direct_copy) {
|
||||
destination.CopyImage(source);
|
||||
} else if (!CopyD16(destination, source)) {
|
||||
@@ -705,7 +681,8 @@ void TextureCache::CopyImageMip(ImageId destination_id, ImageId source_id, uint3
|
||||
ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
|
||||
ImageId cached_id) {
|
||||
auto& cached = m_slot_images[cached_id];
|
||||
if (!cached.info.IsDepth() && !requested.IsDepth()) {
|
||||
if ((!cached.info.IsDepth() && !requested.IsDepth()) ||
|
||||
cached.info.tile_mode != requested.tile_mode) {
|
||||
return {};
|
||||
}
|
||||
const bool stencil_match = requested.HasStencil() == cached.info.HasStencil();
|
||||
@@ -719,8 +696,8 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
requested.samples == 1 && requested.data == cached.info.data &&
|
||||
requested.extent == cached.info.extent && requested.resources == cached.info.resources &&
|
||||
requested.type == cached.info.type && requested.pitch == cached.info.pitch &&
|
||||
requested.tile_mode == cached.info.tile_mode && !requested.HasStencil() &&
|
||||
!cached.info.HasStencil() && !requested.HasMetadata() && !cached.info.HasMetadata();
|
||||
!requested.HasStencil() && !cached.info.HasStencil() && !requested.HasMetadata() &&
|
||||
!cached.info.HasMetadata();
|
||||
// PPSA04264
|
||||
const bool retain_cached_layout =
|
||||
requested.samples == 1 && cached.info.samples == 1 && cached.backing.samples == 1 &&
|
||||
@@ -731,7 +708,7 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
|
||||
requested.resources.layers != 0 && cached.info.resources.layers != 0 &&
|
||||
requested.resources.layers < cached.info.resources.layers &&
|
||||
requested.type == cached.info.type && requested.pitch == cached.info.pitch &&
|
||||
requested.tile_mode == cached.info.tile_mode && requested.mip_layout[0].offset == 0 &&
|
||||
requested.mip_layout[0].offset == 0 &&
|
||||
cached.info.mip_layout[0].offset == 0 &&
|
||||
requested.mip_layout[0].size == requested.data.size &&
|
||||
cached.info.mip_layout[0].size == cached.info.data.size &&
|
||||
@@ -840,12 +817,15 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
return {merged_id};
|
||||
}
|
||||
// PPSA08394
|
||||
// A view cannot change the native image type or grow its extent.
|
||||
if (requested.data.size == cached.info.data.size &&
|
||||
requested.resources == cached.info.resources && requested.type == cached.info.type &&
|
||||
requested.extent.width > cached.info.extent.width &&
|
||||
requested.extent.height >= cached.info.extent.height &&
|
||||
requested.extent.depth >= cached.info.extent.depth &&
|
||||
ImageViewOps::FormatsCompatible(cached.info.pixel_format, requested.pixel_format)) {
|
||||
requested.resources == cached.info.resources &&
|
||||
ImageViewOps::FormatsCompatible(cached.info.pixel_format, requested.pixel_format) &&
|
||||
(requested.type != cached.info.type
|
||||
? requested.extent == cached.info.extent
|
||||
: requested.extent.width > cached.info.extent.width &&
|
||||
requested.extent.height >= cached.info.extent.height &&
|
||||
requested.extent.depth >= cached.info.extent.depth)) {
|
||||
return {ExpandImage(requested, cached_id)};
|
||||
}
|
||||
// PS5 mip tails can expose more levels without increasing the guest allocation.
|
||||
@@ -1148,9 +1128,10 @@ void TextureCache::InitializeImage(ImageId id) {
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer) {
|
||||
if (desc.info.metadata.kind != ImageMetadataKind::Dcc) {
|
||||
if (desc.info.metadata.kind != ImageMetadataKind::Dcc &&
|
||||
desc.info.metadata.kind != ImageMetadataKind::Cmask) {
|
||||
return;
|
||||
}
|
||||
const auto range = desc.info.metadata.range;
|
||||
@@ -1158,18 +1139,18 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
image.info.metadata = desc.info.metadata;
|
||||
// A native DCC allocation must not retain a reused HTile/CMask/FMask interpretation.
|
||||
// Native color metadata must not retain a reused HTile/CMask/FMask clear flag.
|
||||
m_surface_metas.erase(range.address);
|
||||
if (range.size == 0 || desc.info.resources.levels != 1 || image.info.resources.levels != 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto layers = desc.info.TransferLayers();
|
||||
// These one-mip surfaces use complete 4 KiB DCC metadata blocks.
|
||||
// These one-mip surfaces use complete 4 KiB color metadata blocks.
|
||||
constexpr uint64_t MetadataBlockSize = 0x1000;
|
||||
if (!range.Valid() || range.address % MetadataBlockSize != 0 || layers == 0 ||
|
||||
range.size % layers != 0 || (range.size / layers) % MetadataBlockSize != 0) {
|
||||
EXIT("TextureCache: DCC slices must contain aligned 4 KiB blocks\n");
|
||||
EXIT("TextureCache: color metadata slices must contain aligned 4 KiB blocks\n");
|
||||
}
|
||||
const auto& view = desc.view_info;
|
||||
const bool volume_texture = desc.info.IsVolume() && view.type == vk::ImageViewType::e3D;
|
||||
@@ -1177,7 +1158,7 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
const auto image_first = volume_texture ? 0u : view.base_layer;
|
||||
const auto count = volume_texture ? desc.info.extent.depth : view.layer_count;
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: DCC view exceeds its native metadata slices\n");
|
||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
|
||||
// so discovery runs before final draw uploads and never holds the texture lock across it.
|
||||
@@ -1189,15 +1170,15 @@ void TextureCache::MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
uint8_t code = 0;
|
||||
if (!LibKernel::Memory::TryReadBacking(address, &code, sizeof(code))) {
|
||||
EXIT("TextureCache: failed to read DCC metadata backing\n");
|
||||
EXIT("TextureCache: failed to read color metadata backing\n");
|
||||
}
|
||||
vk::ClearValue clear {};
|
||||
if (!DecodeDccClear(desc, code, clear.color)) {
|
||||
if (!DecodeColorClear(desc, code, clear.color)) {
|
||||
continue;
|
||||
}
|
||||
std::vector<uint8_t> bytes(slice_size);
|
||||
if (!LibKernel::Memory::TryReadBacking(address, bytes.data(), bytes.size())) {
|
||||
EXIT("TextureCache: failed to read DCC metadata slice\n");
|
||||
EXIT("TextureCache: failed to read color metadata slice\n");
|
||||
}
|
||||
if (!std::all_of(bytes.begin(), bytes.end(), [code](uint8_t byte) { return byte == code; })) {
|
||||
continue;
|
||||
@@ -1342,7 +1323,7 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
image.tick_accessed_last = m_scheduler.CurrentTick();
|
||||
TouchImage(image);
|
||||
}
|
||||
MaterializeDccClear(result, desc, metadata_base_layer);
|
||||
MaterializeColorClear(result, desc, metadata_base_layer);
|
||||
if (desc.type == BindingType::VideoOut &&
|
||||
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
|
||||
std::scoped_lock lock {m_lock};
|
||||
@@ -1443,9 +1424,7 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
||||
break;
|
||||
default: EXIT("TextureCache: invalid texture binding\n");
|
||||
}
|
||||
const auto view = image.FindView(desc.view_info);
|
||||
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
|
||||
return view;
|
||||
return image.FindView(desc.view_info);
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc) {
|
||||
@@ -1463,9 +1442,7 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
|
||||
RefreshImage(id);
|
||||
CommitGpuWrite(image);
|
||||
TrackImageDownload(id, image);
|
||||
const auto view = image.FindView(desc.view_info);
|
||||
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
|
||||
return view;
|
||||
return image.FindView(desc.view_info);
|
||||
}
|
||||
|
||||
vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
@@ -1492,9 +1469,7 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
|
||||
if (desc.info.HasStencil()) {
|
||||
RefreshImage(AssociateStencil(id, desc.info.stencil));
|
||||
}
|
||||
const auto view = image.FindView(desc.view_info);
|
||||
NameImageBinding(m_graphics, image, view, desc.type, desc.view_info);
|
||||
return view;
|
||||
return image.FindView(desc.view_info);
|
||||
}
|
||||
|
||||
void TextureCache::MarkGpuWritten(ImageId id) {
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ private:
|
||||
ImageId cached);
|
||||
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
|
||||
void RefreshImage(ImageId id);
|
||||
void MaterializeDccClear(ImageId id, const ImageDesc& desc,
|
||||
void MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
uint32_t metadata_base_layer);
|
||||
void InitializeImage(ImageId id);
|
||||
[[nodiscard]] TextureTransfer
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -138,8 +139,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
// Color-control state selects the color-buffer operation and logical blend operation.
|
||||
// The normal copy operation is a regular color write, not an attachment clear.
|
||||
// Nonlinear clear values are still stored as normalized components.
|
||||
// Fast color clears are metadata driven and must be handled explicitly when
|
||||
// that metadata path is implemented; render-pass load must preserve contents.
|
||||
// Metadata clears are materialized during image discovery; render-pass loads preserve contents.
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pitch = 0;
|
||||
@@ -160,9 +160,6 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
break;
|
||||
default: EXIT("unknown tile mode: %u\n", static_cast<uint32_t>(rt.attrib3.tile_mode));
|
||||
}
|
||||
if (!tile && levels > 1) {
|
||||
EXIT("linear mipmapped render targets are unsupported\n");
|
||||
}
|
||||
if (samples > 1 && (!tile || levels != 1)) {
|
||||
EXIT("multisampled render targets require a single-mip tiled surface\n");
|
||||
}
|
||||
@@ -203,8 +200,7 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
" layer=%u/%u\n",
|
||||
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
|
||||
}
|
||||
// PPSA28068: the linear EULA target and its sampled view must share the padded pitch.
|
||||
if (!tile || volume || texture_tile) {
|
||||
if (samples == 1) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
|
||||
} else {
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
@@ -232,37 +228,26 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
}
|
||||
size = volume_layout.block_slice_size;
|
||||
backing_size = volume_layout.total_size;
|
||||
} else if (tile) {
|
||||
} else {
|
||||
TileSizeAlign layout {};
|
||||
bool valid_layout = false;
|
||||
if (texture_tile) {
|
||||
if (samples == 1) {
|
||||
TileGetTextureSize(transfer_format, width, height, levels, rt.attrib3.tile_mode,
|
||||
&layout, mip_sizes, mip_padded);
|
||||
valid_layout = layout.size != 0 && layout.align == texture_tile_layout.block.block_size;
|
||||
valid_layout = layout.size != 0 && layout.align != 0 &&
|
||||
(rt.attrib3.tile_mode != Prospero::TileMode::kRenderTarget ||
|
||||
levels <= std::bit_width(std::max(width, height)));
|
||||
} else {
|
||||
valid_layout =
|
||||
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
|
||||
layout, rt.attrib.num_fragments)
|
||||
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
|
||||
levels, layout, mip_sizes, mip_padded);
|
||||
valid_layout = TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
|
||||
layout, rt.attrib.num_fragments);
|
||||
mip_sizes[0] = {layout.size, 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (!valid_layout) {
|
||||
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
|
||||
height, pitch, bytes_per_element, levels);
|
||||
}
|
||||
size = layout.size;
|
||||
EXIT_IF(size > UINT32_MAX);
|
||||
if (levels == 1) {
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
} else {
|
||||
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
|
||||
if (size > UINT32_MAX) {
|
||||
EXIT("linear render-target slice exceeds the supported layout size\n");
|
||||
}
|
||||
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
|
||||
mip_padded[0] = {pitch, height};
|
||||
}
|
||||
if (size == 0 || (!volume && size > UINT64_MAX / view.image_layers)) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
@@ -304,15 +289,25 @@ void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColor
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
|
||||
if (has_dcc) {
|
||||
const bool has_cmask = !rt.info.dcc_compression_enable && rt.info.cmask_fast_clear_enable &&
|
||||
rt.cmask.addr != 0 && samples == 1 &&
|
||||
!rt.info.fmask_compression_enable &&
|
||||
!rt.attrib3.write_vrs_rate_hint_to_cmask;
|
||||
if (has_dcc || has_cmask) {
|
||||
TileSizeAlign metadata_size {};
|
||||
(void)TileGetDccSize(width, height, volume ? depth : view.image_layers, bytes_per_element,
|
||||
levels, rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments);
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range = {rt.dcc_addr.addr, metadata_size.size};
|
||||
desc.info.metadata.dcc_clear_word = rt.clear_word0.word0;
|
||||
desc.info.metadata.dcc_clear_register_valid = true;
|
||||
desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info);
|
||||
const auto layers = volume ? depth : view.image_layers;
|
||||
if (has_dcc) {
|
||||
(void)TileGetDccSize(width, height, layers, bytes_per_element, levels,
|
||||
rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments);
|
||||
desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info);
|
||||
} else {
|
||||
(void)TileGetCmaskSize(width, height, layers, levels, metadata_size);
|
||||
}
|
||||
// DCC owns the clear when both planes are enabled; single-sample CMASK stays expanded.
|
||||
desc.info.metadata.kind = has_dcc ? ImageMetadataKind::Dcc : ImageMetadataKind::Cmask;
|
||||
desc.info.metadata.range = {has_dcc ? rt.dcc_addr.addr : rt.cmask.addr, metadata_size.size};
|
||||
desc.info.metadata.clear_word = rt.clear_word0.word0;
|
||||
desc.info.metadata.clear_register_valid = true;
|
||||
}
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
if (volume) {
|
||||
|
||||
@@ -219,17 +219,6 @@ static void RtCheck(const HW::RenderTarget& rt) {
|
||||
}
|
||||
}
|
||||
|
||||
if (rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("RenderTarget: temporary: ignoring PS5 color metadata fast_clear=%s dcc=%s "
|
||||
"cmask=0x%016" PRIx64 " dcc_addr=0x%016" PRIx64 "\n",
|
||||
rt.info.cmask_fast_clear_enable ? "true" : "false",
|
||||
rt.info.dcc_compression_enable ? "true" : "false", rt.cmask.addr,
|
||||
rt.dcc_addr.addr);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (rt.info.blend_bypass) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
|
||||
@@ -627,12 +627,13 @@ void Validate(const ImageInfo& info) {
|
||||
}
|
||||
break;
|
||||
case ImageMetadataKind::Dcc:
|
||||
case ImageMetadataKind::Cmask:
|
||||
if (info.metadata.range.address == 0 ||
|
||||
info.metadata.range.address >= TRACKER_ADDRESS_SIZE ||
|
||||
(info.metadata.range.size != 0 &&
|
||||
info.metadata.range.size > TRACKER_ADDRESS_SIZE - info.metadata.range.address) ||
|
||||
info.metadata.compression == VideoOutCompression::Unsupported) {
|
||||
EXIT("invalid DCC metadata\n");
|
||||
EXIT("invalid color metadata\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -690,6 +691,12 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
|
||||
create.extent.width, create.extent.height, create.extent.depth,
|
||||
static_cast<int>(create.format), create.arrayLayers, create.mipLevels);
|
||||
}
|
||||
SetVulkanObjectNameF(
|
||||
graphics.device, backing.image,
|
||||
"Kyty.Image[guest=0x{:016x} size=0x{:x} extent={}x{}x{} format={} mips={} layers={} samples={}]",
|
||||
info.data.address, info.data.size, info.extent.width, info.extent.height, info.extent.depth,
|
||||
static_cast<uint32_t>(info.pixel_format), info.resources.levels, info.resources.layers,
|
||||
info.samples);
|
||||
}
|
||||
|
||||
uint64_t Image::HashGuestEdges() const {
|
||||
|
||||
@@ -17,17 +17,17 @@ namespace Libs::Graphics {
|
||||
|
||||
enum class VideoOutCompression : uint8_t { Uncompressed, Dcc256_256_0, Dcc256_64_64, Unsupported };
|
||||
|
||||
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc };
|
||||
enum class ImageMetadataKind : uint8_t { None, Htile, Dcc, Cmask };
|
||||
|
||||
struct ImageMetadataInfo {
|
||||
GuestRange range;
|
||||
ImageMetadataKind kind = ImageMetadataKind::None;
|
||||
uint32_t control = 0;
|
||||
uint32_t dcc_clear_word = 0;
|
||||
VideoOutCompression compression = VideoOutCompression::Uncompressed;
|
||||
bool stencil_compressed = false;
|
||||
bool dcc_clear_register_valid = false;
|
||||
bool dcc_alpha_msb = true;
|
||||
ImageMetadataKind kind = ImageMetadataKind::None;
|
||||
uint32_t control = 0;
|
||||
uint32_t clear_word = 0;
|
||||
VideoOutCompression compression = VideoOutCompression::Uncompressed;
|
||||
bool stencil_compressed = false;
|
||||
bool clear_register_valid = false;
|
||||
bool dcc_alpha_msb = true;
|
||||
};
|
||||
|
||||
struct ImageSubresources {
|
||||
|
||||
@@ -391,6 +391,12 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
view_info.level_count, view_info.base_layer, view_info.layer_count,
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
|
||||
}
|
||||
SetVulkanObjectNameF(
|
||||
m_graphics.device, view,
|
||||
"Kyty.ImageView[guest=0x{:016x} format={} aspect=0x{:x} mip={}+{} layer={}+{}]",
|
||||
info.data.address, static_cast<uint32_t>(normalized.format),
|
||||
static_cast<vk::ImageAspectFlags::MaskType>(normalized.aspect), normalized.base_level,
|
||||
normalized.level_count, normalized.base_layer, normalized.layer_count);
|
||||
views.push_back({normalized, view});
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -335,8 +335,10 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
|
||||
resource.written && !resource.read && !resource.atomic;
|
||||
const auto numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
const bool raw_float_atomic = format == Prospero::BufferFormat::k32Float && uint_resource &&
|
||||
resource.atomic;
|
||||
const bool format_ok =
|
||||
raw_sint_storage ||
|
||||
raw_sint_storage || raw_float_atomic ||
|
||||
(numeric_class != Prospero::TextureNumericClass::Unsupported &&
|
||||
numeric_class != Prospero::TextureNumericClass::Sint &&
|
||||
uint_resource == (numeric_class == Prospero::TextureNumericClass::Uint) &&
|
||||
@@ -512,19 +514,50 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
return view;
|
||||
}
|
||||
|
||||
static bool TextureViewPreservesMipLayout(const TileSurfaceDescription& description,
|
||||
uint32_t view_levels) {
|
||||
static bool ResolveTextureMipView(const TileSurfaceDescription& description, bool metadata,
|
||||
uint32_t view_levels, uint32_t& levels, uint32_t& base_level) {
|
||||
TileSurfaceLayout physical {};
|
||||
TileSurfaceLayout view {};
|
||||
auto view_description = description;
|
||||
view_description.levels = view_levels;
|
||||
return TileGetTiledTextureLayout(description, physical) &&
|
||||
TileGetTiledTextureLayout(view_description, view) &&
|
||||
physical.first_tail_level == view.first_tail_level &&
|
||||
physical.block_slice_size == view.block_slice_size &&
|
||||
physical.total_size == view.total_size &&
|
||||
std::equal(std::begin(physical.mips), std::begin(physical.mips) + description.levels,
|
||||
std::begin(view.mips));
|
||||
view_description.levels = levels;
|
||||
if (!TileGetTiledTextureLayout(description, physical) ||
|
||||
!TileGetTiledTextureLayout(view_description, view)) {
|
||||
return false;
|
||||
}
|
||||
if (physical.first_tail_level == view.first_tail_level &&
|
||||
physical.block_slice_size == view.block_slice_size &&
|
||||
physical.total_size == view.total_size &&
|
||||
std::equal(std::begin(physical.mips), std::begin(physical.mips) + description.levels,
|
||||
std::begin(view.mips))) {
|
||||
return true;
|
||||
}
|
||||
if (metadata || ((description.layers > 1 || description.depth > 1) &&
|
||||
physical.block_slice_size != view.block_slice_size)) {
|
||||
return false;
|
||||
}
|
||||
// T# addresses the last mip. A view can select the same stored subresources
|
||||
// with different mip indices; inaccessible mips need no host representation.
|
||||
for (uint32_t base = 0; base + view_levels <= description.levels; ++base) {
|
||||
bool matches = true;
|
||||
for (uint32_t i = 0; i < view_levels; ++i) {
|
||||
const auto source = base_level + i;
|
||||
const auto target = base + i;
|
||||
if (physical.mips[target] != view.mips[source] ||
|
||||
(target >= physical.first_tail_level) != (source >= view.first_tail_level) ||
|
||||
std::max(description.width >> target, 1u) != std::max(description.width >> source, 1u) ||
|
||||
std::max(description.height >> target, 1u) != std::max(description.height >> source, 1u) ||
|
||||
std::max(description.depth >> target, 1u) != std::max(description.depth >> source, 1u)) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
levels = description.levels;
|
||||
base_level = base;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
@@ -558,7 +591,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const auto view_levels = multisampled || single_storage_mip
|
||||
? 1u
|
||||
: static_cast<uint32_t>(last_level - base_level) + 1u;
|
||||
const auto levels =
|
||||
auto levels =
|
||||
multisampled ? 1u : std::max(physical_levels, base_level + view_levels);
|
||||
const auto tile = descriptor.TileMode();
|
||||
const bool depth_tile = tile == Prospero::TileMode::kDepth;
|
||||
@@ -600,12 +633,13 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
type == Prospero::ImageType::kColor2DArray ||
|
||||
type == Prospero::ImageType::kColor2DMsaaArray;
|
||||
const auto image_layers = layered ? depth : 1u;
|
||||
auto view_base = static_cast<uint32_t>(base_level);
|
||||
if (levels > physical_levels) {
|
||||
const TileSurfaceDescription physical {
|
||||
format, tile, volume ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
|
||||
width, height, volume ? depth : 1u, physical_levels, image_layers};
|
||||
// Texture mip views take precedence over the resource count, but must keep its storage layout.
|
||||
if (!TextureViewPreservesMipLayout(physical, levels)) {
|
||||
if (!ResolveTextureMipView(physical, !resource.r128 && descriptor.MetaCompress(),
|
||||
view_levels, levels, view_base)) {
|
||||
EXIT("unsupported texture mip view changes physical layout: base=%u last=%u max=%u "
|
||||
"extent=%ux%ux%u tile=%u\n",
|
||||
base_level, last_level, max_mip, width, height, depth,
|
||||
@@ -640,7 +674,8 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
pixel_format = depth_format->depth_attachment_format;
|
||||
}
|
||||
}
|
||||
const auto storage_view_format = storage && format == Prospero::BufferFormat::k32SInt
|
||||
const auto storage_view_format = storage && (resource.atomic ||
|
||||
format == Prospero::BufferFormat::k32SInt)
|
||||
? vk::Format::eR32Uint
|
||||
: SrgbStorageViewFormat(pixel_format);
|
||||
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
|
||||
@@ -676,6 +711,7 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
}
|
||||
desc.view_info = TextureViewInfo(resource, descriptor, view_format, surface_format, storage,
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.view_info.base_level = view_base;
|
||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
@@ -795,9 +831,14 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
|
||||
prepared.buffer_sources.clear();
|
||||
prepared.buffer_sources.reserve(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[i]);
|
||||
const auto& layout = program.bindings;
|
||||
if (layout.memory_offset_count == 0) {
|
||||
return;
|
||||
}
|
||||
const auto& resources = layout.descriptors.front().resources;
|
||||
prepared.buffer_sources.reserve(resources.size());
|
||||
for (const auto resource: resources) {
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[resource]);
|
||||
const auto address = descriptor.Base48();
|
||||
const auto requested_size = descriptor.GetSize();
|
||||
if (address == 0 || requested_size == 0) {
|
||||
@@ -815,10 +856,10 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.memory_offset_count);
|
||||
|
||||
prepared.buffers.clear();
|
||||
prepared.buffers.reserve(program.info.buffers.size());
|
||||
prepared.buffers.reserve(layout.memory_offset_count);
|
||||
EXIT_IF(prepared.shader_data.size() != layout.ShaderDataDwords());
|
||||
std::fill(prepared.shader_data.begin() + layout.memory_offset_dword,
|
||||
prepared.shader_data.end(), 0);
|
||||
@@ -827,11 +868,12 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto shift = (index % 4u) * 8u;
|
||||
prepared.shader_data[dword] |= offset << shift;
|
||||
};
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
for (uint32_t i = 0; i < layout.memory_offset_count; i++) {
|
||||
const auto resource = layout.descriptors.front().resources[i];
|
||||
uint32_t buffer_offset = 0;
|
||||
prepared.buffers.push_back(NativeStorageBuffer(m_context, prepared.buffer_sources[i],
|
||||
program.info.buffers[i], program.stage, i,
|
||||
buffer_offset));
|
||||
program.info.buffers[resource],
|
||||
program.stage, resource, buffer_offset));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
@@ -1042,8 +1084,8 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
} else {
|
||||
switch (binding.kind) {
|
||||
case BindingKind::Buffers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& view = descriptors.buffers.at(resource);
|
||||
EXIT_IF(descriptors.buffers.size() != binding.resources.size());
|
||||
for (const auto& view: descriptors.buffers) {
|
||||
EXIT_IF(view.buffer == nullptr);
|
||||
m_descriptor_buffers.push_back(view);
|
||||
}
|
||||
|
||||
@@ -473,15 +473,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
}
|
||||
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
|
||||
auto& image = cache.GetImage(target.image_id);
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
|
||||
"Kyty.MRT{}.Image[guest=0x{:016x} size=0x{:x} format={}]",
|
||||
target.target_slot, image.info.data.address, image.info.data.size,
|
||||
static_cast<uint32_t>(image.info.pixel_format));
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, image_view,
|
||||
"Kyty.MRT{}.View[guest=0x{:016x} mip={} layer={}+{}]",
|
||||
target.target_slot, image.info.data.address,
|
||||
target.desc.view_info.base_level, target.desc.view_info.base_layer,
|
||||
target.desc.view_info.layer_count);
|
||||
EXIT_IF(image.backing.samples != target.desc.info.samples || image_view == nullptr);
|
||||
const auto& view = target.desc.view_info;
|
||||
const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
|
||||
@@ -522,14 +513,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
EXIT("failed to consume HTile clear state\n");
|
||||
}
|
||||
auto& image = cache.GetImage(depth.image_id);
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, image.backing.image,
|
||||
"Kyty.DepthTarget.Image[guest=0x{:016x} size=0x{:x} format={}]",
|
||||
image.info.data.address, image.info.data.size,
|
||||
static_cast<uint32_t>(image.info.pixel_format));
|
||||
SetVulkanObjectNameF(m_context.GetGraphics().device, image_view,
|
||||
"Kyty.DepthTarget.View[guest=0x{:016x} layer={}+{}]",
|
||||
image.info.data.address, depth.desc.view_info.base_layer,
|
||||
depth.desc.view_info.layer_count);
|
||||
EXIT_IF(image_view == nullptr || image.backing.samples != depth.desc.info.samples);
|
||||
const auto draw_writes = depth.AttachmentWriteAspects();
|
||||
vk::ImageAspectFlags sampled_aspects;
|
||||
|
||||
@@ -112,26 +112,6 @@ void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Write);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeClockCounter(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
|
||||
uint64_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), 0,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::Write);
|
||||
|
||||
LOGF_COLOR(Log::Color::BrightGreen,
|
||||
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- Clock: 0x%016" PRIx64 "\n",
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeClockCounterWithWriteBack(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), 0,
|
||||
EndOfPipeWriteSize::Qword, EndOfPipeWriteAction::WriteBack);
|
||||
|
||||
LOGF_COLOR(Log::Color::BrightGreen,
|
||||
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- Clock: 0x%016" PRIx64 "\n",
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value) {
|
||||
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
|
||||
@@ -192,7 +172,7 @@ uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int f
|
||||
"\n",
|
||||
result, handle, index, flip_mode, flip_arg);
|
||||
}
|
||||
video_out.WaitForSubmitSlot();
|
||||
video_out.WaitForSubmitSlot(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,6 @@ void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst
|
||||
uint64_t value);
|
||||
void WriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t dw_offset, uint32_t dw_num);
|
||||
void WriteAtEndOfPipeClockCounter(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
|
||||
uint64_t value);
|
||||
void WriteAtEndOfPipeClockCounterWithWriteBack(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint64_t* dst_gpu_addr, uint64_t value);
|
||||
void WriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t* dst_gpu_addr, uint32_t value);
|
||||
void WriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 0) out vec4 color;
|
||||
layout(binding = 0) uniform sampler2D source;
|
||||
layout(push_constant) uniform AlphaControl {
|
||||
uint premultiplied;
|
||||
};
|
||||
|
||||
void main() {
|
||||
color = texture(source, uv);
|
||||
if (premultiplied == 0u) {
|
||||
color.rgb *= color.a;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
@@ -15,6 +16,11 @@ struct WindowContext;
|
||||
class Presenter final {
|
||||
public:
|
||||
struct Frame;
|
||||
struct Layer {
|
||||
Frame* frame;
|
||||
int bus;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
|
||||
explicit Presenter(WindowContext& window);
|
||||
~Presenter();
|
||||
@@ -23,11 +29,13 @@ public:
|
||||
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info);
|
||||
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
||||
CommandBuffer* producer = nullptr);
|
||||
[[nodiscard]] Frame* PrepareLastFrame();
|
||||
[[nodiscard]] bool PresentLastFrame();
|
||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||
[[nodiscard]] bool NeedsSystemOverlayRefresh() const noexcept;
|
||||
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
||||
void Present(Frame& frame, bool reuse = false);
|
||||
void Present(Frame& frame);
|
||||
void Present(std::span<const Layer> layers);
|
||||
void ClearLayer(int bus);
|
||||
void Discard(Frame& frame);
|
||||
|
||||
private:
|
||||
|
||||
@@ -51,6 +51,8 @@ constexpr int VIDEO_OUT_BUS_TYPE_OVERLAY = 1;
|
||||
constexpr int VIDEO_OUT_BUS_TYPE_SUB = 2;
|
||||
constexpr int VIDEO_OUT_FLIP_MODE_VSYNC = 1;
|
||||
constexpr int VIDEO_OUT_FLIP_MODE_VSYNC_MULTI = 4;
|
||||
constexpr int VIDEO_OUT_FLIP_MODE_SLAVE = 8;
|
||||
constexpr int VIDEO_OUT_FLIP_MODE_MASTER = 9;
|
||||
constexpr int VIDEO_OUT_BUFFER_INDEX_BLACK = -2;
|
||||
constexpr int VIDEO_OUT_BUFFER_INDEX_BLANK = -1;
|
||||
constexpr int VIDEO_OUT_BUFFER_NUM_MAX = 16;
|
||||
@@ -63,6 +65,7 @@ constexpr uint64_t VIDEO_OUT_REFRESH_RATE_119_88HZ = 13;
|
||||
constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0;
|
||||
constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1;
|
||||
constexpr uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8;
|
||||
constexpr uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED = 32;
|
||||
|
||||
enum class VideoOutEventKind : uintptr_t {
|
||||
Flip = VIDEO_OUT_EVENT_FLIP,
|
||||
@@ -183,6 +186,9 @@ struct VideoOutConfig {
|
||||
uint64_t generation = 0;
|
||||
bool opened = false;
|
||||
bool closing = false;
|
||||
int bus = 0;
|
||||
VideoOutConfig* master = nullptr;
|
||||
uint32_t slave_count = 0;
|
||||
int flip_rate = 0;
|
||||
uint64_t output_mode = VIDEO_OUT_OUTPUT_MODE_DEFAULT;
|
||||
float gamma = 1.0f;
|
||||
@@ -206,7 +212,7 @@ public:
|
||||
void Cancel(VideoOutConfig& cfg);
|
||||
void Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer);
|
||||
void Complete(uint64_t request_id);
|
||||
void WaitForSubmitSlot();
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
@@ -216,7 +222,9 @@ private:
|
||||
|
||||
struct Request {
|
||||
uint64_t id;
|
||||
uint64_t group;
|
||||
VideoOutConfig* cfg;
|
||||
VideoOutConfig* master;
|
||||
uint64_t generation;
|
||||
int index;
|
||||
int64_t flip_arg;
|
||||
@@ -224,6 +232,7 @@ private:
|
||||
FlipRequestSource source;
|
||||
RequestState state;
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
@@ -258,6 +267,7 @@ public:
|
||||
VideoOutConfig* Get(int handle);
|
||||
VideoOutConfig* Get(int handle, uint64_t& generation);
|
||||
bool IsOpened(int handle);
|
||||
int SetFlipMaster(int slave_handle, int master_handle);
|
||||
|
||||
void Init(uint32_t width, uint32_t height);
|
||||
FlipQueue& GetFlipQueue() { return m_flip_queue; }
|
||||
@@ -498,7 +508,8 @@ static bool IsSpecialBufferIndex(int index) {
|
||||
}
|
||||
|
||||
static bool IsValidFlipMode(int mode) {
|
||||
return mode >= VIDEO_OUT_FLIP_MODE_VSYNC && mode <= VIDEO_OUT_FLIP_MODE_VSYNC_MULTI;
|
||||
return (mode >= VIDEO_OUT_FLIP_MODE_VSYNC && mode <= VIDEO_OUT_FLIP_MODE_VSYNC_MULTI) ||
|
||||
mode == VIDEO_OUT_FLIP_MODE_SLAVE || mode == VIDEO_OUT_FLIP_MODE_MASTER;
|
||||
}
|
||||
|
||||
static int ReserveFlipRequest(VideoOutDriver::Impl& driver, int handle, int index, int flip_mode,
|
||||
@@ -508,13 +519,17 @@ static int ReserveFlipRequest(VideoOutDriver::Impl& driver, int handle, int inde
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (!IsValidFlipMode(flip_mode)) {
|
||||
return VIDEO_OUT_ERROR_INVALID_VALUE;
|
||||
return VIDEO_OUT_ERROR_INVALID_FLIP_MODE;
|
||||
}
|
||||
if (!IsValidBufferIndex(index)) {
|
||||
return VIDEO_OUT_ERROR_INVALID_INDEX;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(video_out->mutex);
|
||||
if ((video_out->master != nullptr) != (flip_mode == VIDEO_OUT_FLIP_MODE_SLAVE) ||
|
||||
(video_out->slave_count != 0) != (flip_mode == VIDEO_OUT_FLIP_MODE_MASTER)) {
|
||||
return VIDEO_OUT_ERROR_INVALID_FLIP_MODE;
|
||||
}
|
||||
if (video_out->closing ||
|
||||
(!IsSpecialBufferIndex(index) && !video_out->buffers[index].Occupied())) {
|
||||
return VIDEO_OUT_ERROR_INVALID_INDEX;
|
||||
@@ -532,8 +547,8 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
|
||||
if (attribute.reserved0 != 0 || attribute.aspect_ratio != 0 || attribute.width == 0 ||
|
||||
attribute.height == 0 || attribute.width > 16384 || attribute.height > 16384 ||
|
||||
attribute.pitch_in_pixel != 0 ||
|
||||
(attribute.option != 0 &&
|
||||
attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY) ||
|
||||
(attribute.option & ~(VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY |
|
||||
VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED)) != 0 ||
|
||||
attribute.tiling_mode != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 ||
|
||||
attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0 || buffer.data_address == 0 ||
|
||||
compression == Graphics::VideoOutCompression::Unsupported) {
|
||||
@@ -616,6 +631,11 @@ VideoOutDriver::Impl::~Impl() {
|
||||
m_present_thread.request_stop();
|
||||
m_present_thread.join();
|
||||
}
|
||||
for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; handle++) {
|
||||
if (m_video_out_ctx[handle].master != nullptr) {
|
||||
(void)Close(handle);
|
||||
}
|
||||
}
|
||||
for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; handle++) {
|
||||
(void)Close(handle);
|
||||
}
|
||||
@@ -660,6 +680,7 @@ int VideoOutDriver::Impl::Open(int bus_type, int index) {
|
||||
|
||||
config.closing = false;
|
||||
config.opened = true;
|
||||
config.bus = bus_type;
|
||||
if (++config.generation == 0) {
|
||||
EXIT("video-out port generation wrapped\n");
|
||||
}
|
||||
@@ -686,11 +707,13 @@ bool VideoOutDriver::Impl::Close(int handle) {
|
||||
VideoOutEventQueues pre_vblank_events;
|
||||
VideoOutEventQueues vblank_events;
|
||||
VideoOutEventQueues output_mode_events;
|
||||
VideoOutConfig* master = nullptr;
|
||||
{
|
||||
Common::LockGuard config_lock(config.mutex);
|
||||
if (config.closing) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(config.slave_count != 0);
|
||||
config.opened = false;
|
||||
config.closing = true;
|
||||
if (++config.generation == 0) {
|
||||
@@ -703,7 +726,9 @@ bool VideoOutDriver::Impl::Close(int handle) {
|
||||
vblank_events = std::move(config.events->vblank);
|
||||
output_mode_events = std::move(config.events->output_mode);
|
||||
}
|
||||
config.flip_rate = 0;
|
||||
config.flip_rate = 0;
|
||||
master = config.master;
|
||||
config.master = nullptr;
|
||||
|
||||
for (const auto& buffer: config.buffers) {
|
||||
if (buffer.Occupied() &&
|
||||
@@ -720,8 +745,12 @@ bool VideoOutDriver::Impl::Close(int handle) {
|
||||
}
|
||||
config.vblank_cond.SignalAll();
|
||||
}
|
||||
|
||||
if (master != nullptr) {
|
||||
Common::LockGuard master_lock(master->mutex);
|
||||
master->slave_count--;
|
||||
}
|
||||
m_flip_queue.Cancel(config);
|
||||
m_presenter.ClearLayer(config.bus);
|
||||
DeleteVideoOutEvents(flip_events, VideoOutEventKind::Flip);
|
||||
DeleteVideoOutEvents(pre_vblank_events, VideoOutEventKind::PreVblankStart);
|
||||
DeleteVideoOutEvents(vblank_events, VideoOutEventKind::Vblank);
|
||||
@@ -759,6 +788,31 @@ bool VideoOutDriver::Impl::IsOpened(int handle) {
|
||||
return handle > 0 && handle < VIDEO_OUT_NUM_MAX && m_video_out_ctx[handle].opened;
|
||||
}
|
||||
|
||||
int VideoOutDriver::Impl::SetFlipMaster(int slave_handle, int master_handle) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (slave_handle <= 0 || slave_handle >= VIDEO_OUT_NUM_MAX ||
|
||||
master_handle <= 0 || master_handle >= VIDEO_OUT_NUM_MAX ||
|
||||
!m_video_out_ctx[slave_handle].opened || !m_video_out_ctx[master_handle].opened) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto& slave = m_video_out_ctx[slave_handle];
|
||||
auto& master = m_video_out_ctx[master_handle];
|
||||
if (&slave == &master) {
|
||||
return VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION;
|
||||
}
|
||||
Common::LockGuard first_lock(m_video_out_ctx[std::min(slave_handle, master_handle)].mutex);
|
||||
Common::LockGuard second_lock(m_video_out_ctx[std::max(slave_handle, master_handle)].mutex);
|
||||
if (slave.master != nullptr || slave.slave_count != 0 || master.master != nullptr) {
|
||||
return VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION;
|
||||
}
|
||||
if (slave.flip_status.flipPendingNum != 0 || master.flip_status.flipPendingNum != 0) {
|
||||
return VIDEO_OUT_ERROR_RESOURCE_BUSY;
|
||||
}
|
||||
slave.master = &master;
|
||||
master.slave_count++;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void VideoOutDriver::Impl::VblankBegin() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
@@ -821,9 +875,7 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
const auto period = std::max(frequency / refresh, uint64_t {1});
|
||||
|
||||
if (m_presenter.IsGuestPaused()) {
|
||||
if (auto* frame = m_presenter.PrepareLastFrame(); frame != nullptr) {
|
||||
m_presenter.Present(*frame, true);
|
||||
}
|
||||
(void)m_presenter.PresentLastFrame();
|
||||
const auto frame_end = Common::Timer::QueryPerformanceCounter();
|
||||
total_wait +=
|
||||
static_cast<int64_t>(period) - static_cast<int64_t>(frame_end - frame_begin);
|
||||
@@ -833,8 +885,7 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
VblankBegin();
|
||||
bool presented = m_flip_queue.Flip(0);
|
||||
if (!presented && m_presenter.NeedsSystemOverlayRefresh()) {
|
||||
if (auto* frame = m_presenter.PrepareLastFrame(); frame != nullptr) {
|
||||
m_presenter.Present(*frame, true);
|
||||
if (m_presenter.PresentLastFrame()) {
|
||||
presented = true;
|
||||
} else {
|
||||
uint32_t width = 0;
|
||||
@@ -877,25 +928,57 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
uint64_t& request_id) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (m_requests.size() + m_cpu_requests.size() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
// A newer slave request replaces the one not yet captured by a master flip.
|
||||
if (cfg.master != nullptr) {
|
||||
for (auto* queue: {&m_requests, &m_cpu_requests}) {
|
||||
for (auto it = queue->begin(); it != queue->end();) {
|
||||
if (it->cfg != &cfg || it->group != 0) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
cfg.flip_status.flipPendingNum--;
|
||||
cfg.flip_status.gcQueueNum -= it->source == FlipRequestSource::GpuEop;
|
||||
if (it->state == RequestState::Ready) {
|
||||
m_presenter.Discard(*it->frame);
|
||||
it = queue->erase(it);
|
||||
} else {
|
||||
auto cancelled = it++;
|
||||
m_cancelled_requests.splice(m_cancelled_requests.end(), *queue, cancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
auto& pending = source == FlipRequestSource::GpuEop ? m_requests : m_cpu_requests;
|
||||
|
||||
Request r {};
|
||||
r.id = m_next_request_id++;
|
||||
r.group = cfg.master == nullptr ? r.id : 0;
|
||||
r.cfg = &cfg;
|
||||
r.master = cfg.master;
|
||||
r.generation = cfg.generation;
|
||||
r.index = index;
|
||||
r.flip_arg = flip_arg;
|
||||
r.submit_ptc = LibKernel::KernelGetProcessTimeCounter();
|
||||
r.source = source;
|
||||
r.state = RequestState::Reserved;
|
||||
if (cfg.slave_count != 0) {
|
||||
for (auto* queue: {&m_requests, &m_cpu_requests}) {
|
||||
for (auto& slave: *queue) {
|
||||
if (slave.master == &cfg && slave.group == 0) {
|
||||
slave.group = r.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pending.push_back(r);
|
||||
request_id = r.id;
|
||||
|
||||
cfg.flip_status.flipPendingNum = static_cast<int>(m_requests.size() + m_cpu_requests.size());
|
||||
cfg.flip_status.flipPendingNum++;
|
||||
cfg.flip_status.submitProcessTimeCounter = r.submit_ptc;
|
||||
if (source == FlipRequestSource::GpuEop) {
|
||||
cfg.flip_status.gcQueueNum++;
|
||||
@@ -917,7 +1000,9 @@ FlipQueue::~FlipQueue() {
|
||||
void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
std::vector<Graphics::Presenter::Frame*> frames;
|
||||
m_mutex.Lock();
|
||||
while (m_processing && !m_requests.empty() && m_requests.front().cfg == &cfg) {
|
||||
while (m_processing && std::any_of(m_requests.begin(), m_requests.end(), [&cfg](const auto& r) {
|
||||
return r.cfg == &cfg && r.state == RequestState::Presenting;
|
||||
})) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
}
|
||||
for (auto* queue: {&m_requests, &m_cpu_requests}) {
|
||||
@@ -991,6 +1076,7 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
bool current = false;
|
||||
bool premultiplied_alpha = false;
|
||||
{
|
||||
Common::LockGuard lock(cfg->mutex);
|
||||
current = cfg->opened && !cfg->closing && cfg->generation == generation;
|
||||
@@ -1012,6 +1098,9 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
request_id, index, surface.group_index);
|
||||
}
|
||||
source_info = cfg->groups[surface.group_index].ImageInfo(surface);
|
||||
premultiplied_alpha =
|
||||
(cfg->groups[surface.group_index].attribute.option &
|
||||
VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1051,6 +1140,7 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
||||
EXIT("video-out request changed while recording, id=%" PRIu64 "\n", request_id);
|
||||
}
|
||||
prepared->frame = frame;
|
||||
prepared->premultiplied_alpha = premultiplied_alpha;
|
||||
}
|
||||
|
||||
void FlipQueue::Complete(uint64_t request_id) {
|
||||
@@ -1082,9 +1172,12 @@ void FlipQueue::Complete(uint64_t request_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::WaitForSubmitSlot() {
|
||||
void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_requests.size() + m_cpu_requests.size() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
auto belongs_to_port = [&cfg](const auto& request) { return request.cfg == &cfg; };
|
||||
while (std::count_if(m_requests.begin(), m_requests.end(), belongs_to_port) +
|
||||
std::count_if(m_cpu_requests.begin(), m_cpu_requests.end(), belongs_to_port) >=
|
||||
VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
if (m_requests.empty()) {
|
||||
EXIT("video-out queue is saturated by CPU flips queued behind the current EOP\n");
|
||||
}
|
||||
@@ -1111,75 +1204,90 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
m_mutex.Lock();
|
||||
if (m_requests.empty()) {
|
||||
m_submit_cond_var.WaitFor(&m_mutex, micros);
|
||||
|
||||
if (m_requests.empty()) {
|
||||
m_mutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_processing) {
|
||||
EXIT("video-out flip queue processing is already active\n");
|
||||
}
|
||||
if (m_requests.front().state != RequestState::Ready) {
|
||||
EXIT_IF(m_processing);
|
||||
const auto owner = std::find_if(m_requests.begin(), m_requests.end(),
|
||||
[](const auto& r) { return r.group == r.id; });
|
||||
if (owner == m_requests.end()) {
|
||||
m_mutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
m_processing = true;
|
||||
auto r = m_requests.front();
|
||||
m_mutex.Unlock();
|
||||
|
||||
r.cfg->mutex.Lock();
|
||||
if (!IsFlipDueLocked(*r.cfg, r.generation)) {
|
||||
r.cfg->mutex.Unlock();
|
||||
Common::LockGuard queue_lock(m_mutex);
|
||||
m_processing = false;
|
||||
m_done_cond_var.SignalAll();
|
||||
const uint64_t group = owner->id;
|
||||
if (std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(),
|
||||
[group](const auto& r) { return r.group == group; }) ||
|
||||
std::any_of(m_requests.begin(), m_requests.end(), [group](const auto& r) {
|
||||
return r.group == group && r.state != RequestState::Ready;
|
||||
})) {
|
||||
m_mutex.Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_mutex.Lock();
|
||||
if (m_requests.empty() || m_requests.front().id != r.id ||
|
||||
m_requests.front().state != RequestState::Ready || !m_processing) {
|
||||
EXIT("video-out request changed before presentation, id=%" PRIu64 "\n", r.id);
|
||||
std::array<Request, VideoOutDriver::Impl::VIDEO_OUT_BUSES.size()> requests {};
|
||||
size_t count = 0;
|
||||
for (auto& request: m_requests) {
|
||||
if (request.group == group) {
|
||||
EXIT_IF(count == requests.size());
|
||||
request.state = RequestState::Presenting;
|
||||
requests[count++] = request;
|
||||
}
|
||||
}
|
||||
m_requests.front().state = RequestState::Presenting;
|
||||
m_processing = true;
|
||||
m_mutex.Unlock();
|
||||
|
||||
m_presenter.Present(*r.frame);
|
||||
// Lock each port in bus order, then present and complete the whole group at one Vblank.
|
||||
std::sort(requests.begin(), requests.begin() + count,
|
||||
[](const auto& a, const auto& b) { return a.cfg->bus < b.cfg->bus; });
|
||||
bool due = true;
|
||||
std::array<Graphics::Presenter::Layer, VideoOutDriver::Impl::VIDEO_OUT_BUSES.size()> layers {};
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
auto& r = requests[i];
|
||||
r.cfg->mutex.Lock();
|
||||
due &= r.cfg->opened && !r.cfg->closing && r.cfg->generation == r.generation;
|
||||
if (r.id == group) {
|
||||
due &= IsFlipDueLocked(*r.cfg, r.generation);
|
||||
}
|
||||
layers[i] = {r.frame, r.cfg->bus, r.premultiplied_alpha};
|
||||
}
|
||||
if (due) {
|
||||
m_presenter.Present(std::span(layers.data(), count));
|
||||
}
|
||||
|
||||
m_mutex.Lock();
|
||||
if (m_requests.empty() || m_requests.front().id != r.id ||
|
||||
m_requests.front().state != RequestState::Presenting) {
|
||||
EXIT("video-out flip queue changed while processing its front request\n");
|
||||
for (auto it = m_requests.begin(); it != m_requests.end();) {
|
||||
if (it->group != group) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (!due) {
|
||||
it++->state = RequestState::Ready;
|
||||
continue;
|
||||
}
|
||||
auto& r = *it;
|
||||
r.cfg->flip_status.count++;
|
||||
r.cfg->flip_status.processTime = LibKernel::KernelGetProcessTime();
|
||||
r.cfg->flip_status.processTimeCounter = LibKernel::KernelGetProcessTimeCounter();
|
||||
r.cfg->flip_status.submitProcessTimeCounter = r.submit_ptc;
|
||||
r.cfg->flip_status.flipArg = r.flip_arg;
|
||||
r.cfg->flip_status.currentBuffer = r.index;
|
||||
r.cfg->flip_status.flipPendingNum--;
|
||||
r.cfg->flip_status.gcQueueNum -= r.source == FlipRequestSource::GpuEop;
|
||||
TriggerVideoOutEvents(*r.cfg, VideoOutEventKind::Flip, reinterpret_cast<void*>(r.flip_arg));
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_requests.pop_front();
|
||||
|
||||
r.cfg->flip_status.count++;
|
||||
r.cfg->flip_status.processTime = LibKernel::KernelGetProcessTime();
|
||||
r.cfg->flip_status.processTimeCounter = LibKernel::KernelGetProcessTimeCounter();
|
||||
r.cfg->flip_status.submitProcessTimeCounter = r.submit_ptc;
|
||||
r.cfg->flip_status.flipArg = r.flip_arg;
|
||||
r.cfg->flip_status.currentBuffer = r.index;
|
||||
r.cfg->flip_status.flipPendingNum = static_cast<int>(m_requests.size() + m_cpu_requests.size());
|
||||
if (r.source == FlipRequestSource::GpuEop && r.cfg->flip_status.gcQueueNum > 0) {
|
||||
r.cfg->flip_status.gcQueueNum--;
|
||||
}
|
||||
TriggerVideoOutEvents(*r.cfg, VideoOutEventKind::Flip, reinterpret_cast<void*>(r.flip_arg));
|
||||
|
||||
m_processing = false;
|
||||
m_done_cond_var.SignalAll();
|
||||
m_submit_slot_cond_var.Signal();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
r.cfg->mutex.Unlock();
|
||||
|
||||
Graphics::RenderDocOnGuestFlip(m_presenter.Renderer());
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent) {
|
||||
LOGF("Flip done: %d\n", r.index);
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
requests[i - 1].cfg->mutex.Unlock();
|
||||
}
|
||||
|
||||
return true;
|
||||
if (due) {
|
||||
Graphics::RenderDocOnGuestFlip(m_presenter.Renderer());
|
||||
if (Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent) {
|
||||
LOGF("Flip done: %d\n", requests[0].index);
|
||||
}
|
||||
}
|
||||
return due;
|
||||
}
|
||||
|
||||
void FlipQueue::GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out) {
|
||||
@@ -1188,6 +1296,18 @@ void FlipQueue::GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out) {
|
||||
out = cfg.flip_status;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutSetFlipMaster(int slave_handle, int master_handle) {
|
||||
PRINT_NAME();
|
||||
return DriverState().SetFlipMaster(slave_handle, master_handle);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI void VideoOutAddBufferAttributeOption(VideoOutBufferAttribute2* attribute,
|
||||
uint64_t option) {
|
||||
PRINT_NAME();
|
||||
EXIT_IF(attribute == nullptr);
|
||||
attribute->option |= option;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -1467,7 +1587,7 @@ KYTY_SYSV_ABI int VideoOutSubmitFlip(int handle, int index, int flip_mode, int64
|
||||
uint64_t request_id = 0;
|
||||
const int result = ReserveFlipRequest(DriverState(), handle, index, flip_mode, flip_arg,
|
||||
FlipRequestSource::Cpu, request_id);
|
||||
if (result == VIDEO_OUT_ERROR_INVALID_VALUE) {
|
||||
if (result == VIDEO_OUT_ERROR_INVALID_FLIP_MODE) {
|
||||
LOGF("\t unsupported flip_mode = %d\n", flip_mode);
|
||||
}
|
||||
if (result != OK) {
|
||||
@@ -1504,8 +1624,10 @@ void VideoOutDriver::CompleteFlip(uint64_t request_id) {
|
||||
m_impl->GetFlipQueue().Complete(request_id);
|
||||
}
|
||||
|
||||
void VideoOutDriver::WaitForSubmitSlot() {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot();
|
||||
void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
auto* cfg = m_impl->Get(handle);
|
||||
EXIT_IF(cfg == nullptr);
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
void PrepareFlip(uint64_t request_id, Graphics::CommandBuffer& buffer);
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot();
|
||||
void WaitForSubmitSlot(int handle);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
@@ -52,6 +52,9 @@ void VideoOutShutdown();
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void* param);
|
||||
KYTY_SYSV_ABI int VideoOutClose(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutSetFlipMaster(int slave_handle, int master_handle);
|
||||
KYTY_SYSV_ABI void VideoOutAddBufferAttributeOption(VideoOutBufferAttribute2* attribute,
|
||||
uint64_t option);
|
||||
KYTY_SYSV_ABI void VideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribute,
|
||||
uint64_t pixel_format, uint32_t tiling_mode,
|
||||
uint32_t width, uint32_t height, uint64_t option,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
|
||||
#include "gpu_blit_shaders/gpu_video_out_overlay_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -14,7 +16,7 @@
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -25,10 +27,10 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct Presenter::Frame {
|
||||
VulkanImage image;
|
||||
uint64_t present_tick = 0;
|
||||
bool busy = false;
|
||||
bool reusing_last = false;
|
||||
VulkanImage image;
|
||||
vk::ImageView view = nullptr;
|
||||
uint64_t present_tick = 0;
|
||||
bool busy = false;
|
||||
|
||||
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
|
||||
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
|
||||
@@ -43,6 +45,7 @@ public:
|
||||
~FramePool() {
|
||||
m_scheduler.Wait(m_scheduler.CurrentTick() - 1);
|
||||
for (auto& frame: m_frames) {
|
||||
m_window.graphic_ctx.device.destroyImageView(frame->view, nullptr);
|
||||
if (frame->image.image != nullptr) {
|
||||
m_window.graphic_ctx.DeleteImage(frame->image);
|
||||
}
|
||||
@@ -60,6 +63,7 @@ public:
|
||||
}
|
||||
m_format = format;
|
||||
m_frames.reserve(count);
|
||||
m_free.reserve(count);
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
auto frame = std::make_unique<Presenter::Frame>();
|
||||
m_free.push_back(frame.get());
|
||||
@@ -83,60 +87,45 @@ public:
|
||||
return m_format;
|
||||
}
|
||||
|
||||
Presenter::Frame* Acquire() {
|
||||
Presenter::Frame* Acquire(vk::Extent2D extent, vk::Format format) {
|
||||
m_mutex.Lock();
|
||||
if (m_frames.empty()) {
|
||||
EXIT("prepared-frame pool was used before swapchain initialization\n");
|
||||
}
|
||||
while (m_free.empty()) {
|
||||
m_available.Wait(&m_mutex);
|
||||
// A synchronized flip may need more frames than the swapchain has images.
|
||||
// Queue capacity bounds growth; waiting here can prevent its master from recording.
|
||||
if (m_free.empty()) {
|
||||
auto frame = std::make_unique<Presenter::Frame>();
|
||||
m_free.push_back(frame.get());
|
||||
m_frames.push_back(std::move(frame));
|
||||
}
|
||||
auto* frame = m_free.front();
|
||||
m_free.pop_front();
|
||||
const auto compatible = std::ranges::find_if(m_free, [&](const auto* frame) {
|
||||
return frame->image.extent.width == extent.width &&
|
||||
frame->image.extent.height == extent.height && frame->image.format == format;
|
||||
});
|
||||
auto* frame = compatible == m_free.end() ? m_free.back() : *compatible;
|
||||
if (compatible != m_free.end()) {
|
||||
*compatible = m_free.back();
|
||||
}
|
||||
m_free.pop_back();
|
||||
if (frame->busy) {
|
||||
EXIT("prepared-frame pool returned an invalid frame\n");
|
||||
}
|
||||
if (m_last_frame == frame) {
|
||||
m_last_frame = nullptr;
|
||||
}
|
||||
frame->busy = true;
|
||||
frame->reusing_last = false;
|
||||
frame->busy = true;
|
||||
m_mutex.Unlock();
|
||||
|
||||
WaitForFrame(*frame);
|
||||
m_scheduler.Wait(frame->present_tick);
|
||||
return frame;
|
||||
}
|
||||
|
||||
Presenter::Frame* AcquireLast() {
|
||||
m_mutex.Lock();
|
||||
auto* frame = m_last_frame;
|
||||
if (frame == nullptr) {
|
||||
m_mutex.Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
auto free = std::find(m_free.begin(), m_free.end(), frame);
|
||||
if (free == m_free.end() || frame->busy) {
|
||||
m_mutex.Unlock();
|
||||
EXIT("last submitted frame is not available for reuse\n");
|
||||
}
|
||||
m_free.erase(free);
|
||||
m_last_frame = nullptr;
|
||||
frame->busy = true;
|
||||
frame->reusing_last = true;
|
||||
m_mutex.Unlock();
|
||||
|
||||
WaitForFrame(*frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void ValidateForPresent(Presenter::Frame* frame, bool reuse) {
|
||||
void ValidateForPresent(Presenter::Frame* frame) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (frame == nullptr || !frame->busy || frame->reusing_last != reuse) {
|
||||
if (frame == nullptr || !frame->busy) {
|
||||
EXIT("prepared frame has invalid presentation ownership\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Release(Presenter::Frame* frame, bool make_last = false) {
|
||||
void Release(Presenter::Frame* frame) {
|
||||
if (frame == nullptr) {
|
||||
EXIT("cannot release a null prepared frame\n");
|
||||
}
|
||||
@@ -144,26 +133,17 @@ public:
|
||||
if (!frame->busy) {
|
||||
EXIT("prepared frame was released twice\n");
|
||||
}
|
||||
frame->busy = false;
|
||||
frame->reusing_last = false;
|
||||
if (make_last) {
|
||||
m_last_frame = frame;
|
||||
}
|
||||
frame->busy = false;
|
||||
m_free.push_back(frame);
|
||||
m_available.Signal();
|
||||
}
|
||||
|
||||
private:
|
||||
void WaitForFrame(Presenter::Frame& frame) { m_scheduler.Wait(frame.present_tick); }
|
||||
|
||||
WindowContext& m_window;
|
||||
CommandScheduler& m_scheduler;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_available;
|
||||
std::vector<std::unique_ptr<Presenter::Frame>> m_frames;
|
||||
std::deque<Presenter::Frame*> m_free;
|
||||
Presenter::Frame* m_last_frame = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
std::vector<Presenter::Frame*> m_free;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
};
|
||||
|
||||
void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format) {
|
||||
@@ -181,13 +161,16 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
const auto features = graphics.GetFormatProperties(format).optimalTilingFeatures;
|
||||
const auto required =
|
||||
vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferSrc | vk::FormatFeatureFlagBits::eTransferDst;
|
||||
vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eTransferSrc |
|
||||
vk::FormatFeatureFlagBits::eTransferDst;
|
||||
if ((features & required) != required) {
|
||||
EXIT("prepared presentation format lacks optimal blit support: format=%d features=0x%x\n",
|
||||
static_cast<int>(format), static_cast<vk::FormatFeatureFlags::MaskType>(features));
|
||||
}
|
||||
|
||||
if (dst.image != nullptr) {
|
||||
graphics.device.destroyImageView(view, nullptr);
|
||||
view = nullptr;
|
||||
graphics.DeleteImage(dst);
|
||||
}
|
||||
|
||||
@@ -200,7 +183,8 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
create.format = format;
|
||||
create.tiling = vk::ImageTiling::eOptimal;
|
||||
create.initialLayout = vk::ImageLayout::eUndefined;
|
||||
create.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
create.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
|
||||
vk::ImageUsageFlagBits::eSampled;
|
||||
create.sharingMode = vk::SharingMode::eExclusive;
|
||||
create.samples = vk::SampleCountFlagBits::e1;
|
||||
if (!graphics.CreateImage(create, dst)) {
|
||||
@@ -258,7 +242,6 @@ void Presenter::Frame::CopyFrom(CommandBuffer& command_buffer, Image& source) {
|
||||
EXIT_IF(copy.srcSubresource.layerCount != copy.dstSubresource.layerCount);
|
||||
command.copyImage(source.backing.image, vk::ImageLayout::eTransferSrcOptimal, image.image,
|
||||
vk::ImageLayout::eTransferDstOptimal, copy);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
|
||||
void Presenter::Frame::Clear(CommandBuffer& command_buffer, const vk::ClearColorValue& color) {
|
||||
@@ -267,7 +250,6 @@ void Presenter::Frame::Clear(CommandBuffer& command_buffer, const vk::ClearColor
|
||||
Transit(command, vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite);
|
||||
const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.clearColorImage(image.image, vk::ImageLayout::eTransferDstOptimal, &color, 1, &range);
|
||||
Transit(command, vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
|
||||
class Swapchain final {
|
||||
@@ -283,9 +265,9 @@ public:
|
||||
[[nodiscard]] bool NeedsResize() const;
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
[[nodiscard]] bool PrepareSystemOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
bool draw_system_overlay);
|
||||
uint64_t Submit(CommandScheduler& scheduler);
|
||||
void RecordPresentCommands(CommandBuffer& command, Presenter::Frame* source,
|
||||
const Presenter::Layer& overlay, bool draw_system_overlay);
|
||||
uint64_t Submit(CommandScheduler& scheduler);
|
||||
[[nodiscard]] Status Present();
|
||||
|
||||
[[nodiscard]] uint32_t ImageCount() const noexcept {
|
||||
@@ -295,20 +277,25 @@ public:
|
||||
|
||||
private:
|
||||
void Destroy();
|
||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
||||
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Extent2D m_extent {};
|
||||
WindowContext& m_window;
|
||||
vk::SwapchainKHR m_handle = nullptr;
|
||||
vk::Format m_format = vk::Format::eUndefined;
|
||||
vk::Extent2D m_extent {};
|
||||
// Drawable pixel size observed when this swapchain was created.
|
||||
vk::Extent2D m_window_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
std::vector<vk::Semaphore> m_render_complete;
|
||||
vk::Extent2D m_window_extent {};
|
||||
std::vector<vk::Image> m_images;
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
std::vector<vk::Semaphore> m_render_complete;
|
||||
std::unique_ptr<SystemOverlay> m_system_overlay;
|
||||
uint32_t m_image_index = static_cast<uint32_t>(-1);
|
||||
uint32_t m_frame_index = 0;
|
||||
vk::DescriptorSetLayout m_overlay_descriptors = nullptr;
|
||||
vk::PipelineLayout m_overlay_layout = nullptr;
|
||||
vk::Pipeline m_overlay_pipeline = nullptr;
|
||||
vk::Sampler m_overlay_sampler = nullptr;
|
||||
uint32_t m_image_index = static_cast<uint32_t>(-1);
|
||||
uint32_t m_frame_index = 0;
|
||||
};
|
||||
|
||||
struct Presenter::Impl {
|
||||
@@ -347,12 +334,15 @@ struct Presenter::Impl {
|
||||
cache.UpdateImage(image_id);
|
||||
return image;
|
||||
}
|
||||
void Present();
|
||||
|
||||
RenderContext& renderer;
|
||||
WindowContext& window;
|
||||
Swapchain swapchain;
|
||||
CommandScheduler present_scheduler;
|
||||
FramePool frames;
|
||||
Common::Mutex present_mutex;
|
||||
std::array<Layer, 2> layers {};
|
||||
std::atomic<uint64_t> presented_overlay_revision {0};
|
||||
};
|
||||
|
||||
@@ -370,7 +360,7 @@ void Swapchain::Create() {
|
||||
EXIT_IF(m_window_extent.width == 0);
|
||||
EXIT_IF(m_window_extent.height == 0);
|
||||
m_window.RefreshSurfaceCapabilities();
|
||||
const auto& surface = m_window.surface_capabilities;
|
||||
const auto& surface = m_window.surface_capabilities;
|
||||
EXIT_NOT_IMPLEMENTED(surface.formats.empty());
|
||||
|
||||
m_extent = surface.capabilities.currentExtent;
|
||||
@@ -442,7 +432,7 @@ void Swapchain::Create() {
|
||||
LOGF("warning: requested present mode is unavailable; falling back to Fifo\n");
|
||||
create_info.presentMode = vk::PresentModeKHR::eFifo;
|
||||
}
|
||||
create_info.clipped = VK_TRUE;
|
||||
create_info.clipped = VK_TRUE;
|
||||
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
|
||||
"vkCreateSwapchainKHR");
|
||||
EXIT_IF(m_handle == nullptr);
|
||||
@@ -483,8 +473,8 @@ void Swapchain::Create() {
|
||||
graphics.device.createSemaphore(&semaphore_info, nullptr, &m_render_complete[i]),
|
||||
"create swapchain render-complete semaphore");
|
||||
}
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
m_image_index = static_cast<uint32_t>(-1);
|
||||
m_frame_index = 0;
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain() {
|
||||
@@ -505,6 +495,14 @@ void Swapchain::Destroy() {
|
||||
if (m_system_overlay != nullptr) {
|
||||
m_system_overlay->ReleaseVulkan();
|
||||
}
|
||||
graphics.device.destroyPipeline(m_overlay_pipeline, nullptr);
|
||||
graphics.device.destroyPipelineLayout(m_overlay_layout, nullptr);
|
||||
graphics.device.destroyDescriptorSetLayout(m_overlay_descriptors, nullptr);
|
||||
graphics.device.destroySampler(m_overlay_sampler, nullptr);
|
||||
m_overlay_pipeline = nullptr;
|
||||
m_overlay_layout = nullptr;
|
||||
m_overlay_descriptors = nullptr;
|
||||
m_overlay_sampler = nullptr;
|
||||
|
||||
for (const auto semaphore: m_image_acquired) {
|
||||
if (semaphore != nullptr) {
|
||||
@@ -544,8 +542,8 @@ void Swapchain::Recreate(bool surface_lost) {
|
||||
// Surface recreation goes through SDL_Vulkan_CreateSurface, which touches the
|
||||
// window's view/layer and must run on the main thread on macOS.
|
||||
EXIT_IF(!SDL_RunOnMainThread(
|
||||
[](void* window) { static_cast<WindowContext*>(window)->RecreateSurface(); },
|
||||
&m_window, true));
|
||||
[](void* window) { static_cast<WindowContext*>(window)->RecreateSurface(); }, &m_window,
|
||||
true));
|
||||
#else
|
||||
m_window.RecreateSurface();
|
||||
#endif
|
||||
@@ -592,14 +590,147 @@ bool Swapchain::PrepareSystemOverlay() {
|
||||
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||
}
|
||||
|
||||
void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
bool draw_system_overlay) {
|
||||
if (source.state.layout != vk::ImageLayout::eTransferSrcOptimal) {
|
||||
EXIT("invalid prepared presentation image, vk_image=%p layout=%d\n",
|
||||
static_cast<void*>(source.image), static_cast<int>(source.state.layout));
|
||||
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer) {
|
||||
auto device = m_window.graphic_ctx.device;
|
||||
if (m_overlay_pipeline == nullptr) {
|
||||
vk::SamplerCreateInfo sampler {};
|
||||
sampler.magFilter = vk::Filter::eLinear;
|
||||
sampler.minFilter = vk::Filter::eLinear;
|
||||
sampler.addressModeU = vk::SamplerAddressMode::eClampToEdge;
|
||||
sampler.addressModeV = vk::SamplerAddressMode::eClampToEdge;
|
||||
sampler.addressModeW = vk::SamplerAddressMode::eClampToEdge;
|
||||
RequireVulkanSuccess(device.createSampler(&sampler, nullptr, &m_overlay_sampler),
|
||||
"create video-out overlay sampler");
|
||||
const vk::DescriptorSetLayoutBinding binding {0, vk::DescriptorType::eCombinedImageSampler,
|
||||
1, vk::ShaderStageFlagBits::eFragment};
|
||||
vk::DescriptorSetLayoutCreateInfo descriptors {};
|
||||
descriptors.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptors.bindingCount = 1;
|
||||
descriptors.pBindings = &binding;
|
||||
RequireVulkanSuccess(
|
||||
device.createDescriptorSetLayout(&descriptors, nullptr, &m_overlay_descriptors),
|
||||
"create video-out overlay descriptor layout");
|
||||
const vk::PushConstantRange alpha {vk::ShaderStageFlagBits::eFragment, 0, sizeof(uint32_t)};
|
||||
vk::PipelineLayoutCreateInfo layout {};
|
||||
layout.setLayoutCount = 1;
|
||||
layout.pSetLayouts = &m_overlay_descriptors;
|
||||
layout.pushConstantRangeCount = 1;
|
||||
layout.pPushConstantRanges = α
|
||||
RequireVulkanSuccess(device.createPipelineLayout(&layout, nullptr, &m_overlay_layout),
|
||||
"create video-out overlay pipeline layout");
|
||||
const auto vertex = CompileSPV(GPU_BLIT_FS_TRIANGLE_SPV, device);
|
||||
const auto fragment = CompileSPV(GPU_VIDEO_OUT_OVERLAY_SPV, device);
|
||||
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
|
||||
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
|
||||
stages[0].module = vertex;
|
||||
stages[0].pName = "main";
|
||||
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
|
||||
stages[1].module = fragment;
|
||||
stages[1].pName = "main";
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input {};
|
||||
vk::PipelineInputAssemblyStateCreateInfo assembly {};
|
||||
assembly.topology = vk::PrimitiveTopology::eTriangleList;
|
||||
vk::PipelineViewportStateCreateInfo viewport {};
|
||||
viewport.viewportCount = 1;
|
||||
viewport.scissorCount = 1;
|
||||
vk::PipelineRasterizationStateCreateInfo rasterization {};
|
||||
rasterization.lineWidth = 1.0f;
|
||||
vk::PipelineMultisampleStateCreateInfo multisample {};
|
||||
multisample.rasterizationSamples = vk::SampleCountFlagBits::e1;
|
||||
vk::PipelineColorBlendAttachmentState attachment {};
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = vk::BlendFactor::eOne;
|
||||
attachment.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
attachment.srcAlphaBlendFactor = vk::BlendFactor::eOne;
|
||||
attachment.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
attachment.colorWriteMask = vk::ColorComponentFlagBits::eR |
|
||||
vk::ColorComponentFlagBits::eG |
|
||||
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
||||
vk::PipelineColorBlendStateCreateInfo blend {};
|
||||
blend.attachmentCount = 1;
|
||||
blend.pAttachments = &attachment;
|
||||
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
||||
vk::PipelineDynamicStateCreateInfo dynamic {};
|
||||
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||
dynamic.pDynamicStates = dynamic_states.data();
|
||||
vk::PipelineRenderingCreateInfo rendering {};
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachmentFormats = &m_format;
|
||||
vk::GraphicsPipelineCreateInfo create {};
|
||||
create.pNext = &rendering;
|
||||
create.stageCount = static_cast<uint32_t>(stages.size());
|
||||
create.pStages = stages.data();
|
||||
create.pVertexInputState = &vertex_input;
|
||||
create.pInputAssemblyState = &assembly;
|
||||
create.pViewportState = &viewport;
|
||||
create.pRasterizationState = &rasterization;
|
||||
create.pMultisampleState = &multisample;
|
||||
create.pColorBlendState = &blend;
|
||||
create.pDynamicState = &dynamic;
|
||||
create.layout = m_overlay_layout;
|
||||
RequireVulkanSuccess(
|
||||
device.createGraphicsPipelines(nullptr, 1, &create, nullptr, &m_overlay_pipeline),
|
||||
"create video-out overlay pipeline");
|
||||
device.destroyShaderModule(fragment, nullptr);
|
||||
device.destroyShaderModule(vertex, nullptr);
|
||||
}
|
||||
auto& frame = *layer.frame;
|
||||
if (frame.view == nullptr) {
|
||||
vk::ImageViewCreateInfo view {};
|
||||
view.image = frame.image.image;
|
||||
view.viewType = vk::ImageViewType::e2D;
|
||||
view.format = frame.image.format;
|
||||
view.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
RequireVulkanSuccess(device.createImageView(&view, nullptr, &frame.view),
|
||||
"create video-out overlay image view");
|
||||
}
|
||||
const vk::DescriptorImageInfo image {m_overlay_sampler, frame.view,
|
||||
vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
vk::WriteDescriptorSet write {};
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
|
||||
write.pImageInfo = ℑ
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_overlay_layout, 0, 1, &write);
|
||||
const uint32_t premultiplied = layer.premultiplied_alpha;
|
||||
command.pushConstants(m_overlay_layout, vk::ShaderStageFlagBits::eFragment, 0,
|
||||
sizeof(premultiplied), &premultiplied);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eGraphics, m_overlay_pipeline);
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = m_image_views[m_image_index];
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eLoad;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = m_extent;
|
||||
rendering.layerCount = 1;
|
||||
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};
|
||||
command.setViewport(0, 1, &viewport);
|
||||
command.setScissor(0, 1, &scissor);
|
||||
command.draw(3, 1, 0, 0);
|
||||
command.endRendering();
|
||||
}
|
||||
|
||||
void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame* source,
|
||||
const Presenter::Layer& overlay, bool draw_system_overlay) {
|
||||
EXIT_IF(m_image_index >= m_images.size());
|
||||
auto vk_command = command.Handle();
|
||||
auto vk_command = command.Handle();
|
||||
const bool draw_overlay = overlay.frame != nullptr;
|
||||
const bool draw_attachment = draw_overlay || draw_system_overlay;
|
||||
if (source != nullptr) {
|
||||
source->Transit(vk_command, vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::AccessFlagBits2::eTransferRead);
|
||||
}
|
||||
if (draw_overlay) {
|
||||
overlay.frame->Transit(vk_command, vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::AccessFlagBits2::eShaderRead);
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier to_transfer {};
|
||||
to_transfer.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
@@ -619,49 +750,68 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
||||
nullptr, 0, nullptr, 1, &to_transfer);
|
||||
|
||||
vk::ImageBlit region {};
|
||||
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
region.srcSubresource.mipLevel = 0;
|
||||
region.srcSubresource.baseArrayLayer = 0;
|
||||
region.srcSubresource.layerCount = 1;
|
||||
region.srcOffsets[1].x = static_cast<int>(source.extent.width);
|
||||
region.srcOffsets[1].y = static_cast<int>(source.extent.height);
|
||||
region.srcOffsets[1].z = 1;
|
||||
region.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
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[1].z = 1;
|
||||
vk_command.blitImage(source.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1, ®ion,
|
||||
vk::Filter::eLinear);
|
||||
if (source != nullptr) {
|
||||
vk::ImageBlit region {};
|
||||
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
region.srcSubresource.mipLevel = 0;
|
||||
region.srcSubresource.baseArrayLayer = 0;
|
||||
region.srcSubresource.layerCount = 1;
|
||||
region.srcOffsets[1].x = static_cast<int>(source->image.extent.width);
|
||||
region.srcOffsets[1].y = static_cast<int>(source->image.extent.height);
|
||||
region.srcOffsets[1].z = 1;
|
||||
region.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
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[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 {};
|
||||
to_present.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
to_present.dstAccessMask = draw_system_overlay ? vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite
|
||||
: vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
to_present.newLayout = draw_system_overlay ? vk::ImageLayout::eColorAttachmentOptimal
|
||||
: vk::ImageLayout::ePresentSrcKHR;
|
||||
to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.image = m_images[m_image_index];
|
||||
to_present.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
to_present.dstAccessMask = draw_attachment ? vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite
|
||||
: vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
to_present.newLayout = draw_attachment ? vk::ImageLayout::eColorAttachmentOptimal
|
||||
: vk::ImageLayout::ePresentSrcKHR;
|
||||
to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.image = m_images[m_image_index];
|
||||
to_present.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
to_present.subresourceRange.baseMipLevel = 0;
|
||||
to_present.subresourceRange.levelCount = 1;
|
||||
to_present.subresourceRange.baseArrayLayer = 0;
|
||||
to_present.subresourceRange.layerCount = 1;
|
||||
vk_command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
draw_system_overlay ? vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
: vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present);
|
||||
if (draw_system_overlay) {
|
||||
m_system_overlay->Record(vk_command, m_image_views[m_image_index]);
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
draw_attachment ? vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
: vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
|
||||
&to_present);
|
||||
if (draw_attachment) {
|
||||
if (draw_overlay) {
|
||||
DrawOverlay(vk_command, overlay);
|
||||
if (draw_system_overlay) {
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0,
|
||||
nullptr, 1, &to_present);
|
||||
}
|
||||
}
|
||||
if (draw_system_overlay) {
|
||||
m_system_overlay->Record(vk_command, m_image_views[m_image_index]);
|
||||
}
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
@@ -721,19 +871,18 @@ Presenter::~Presenter() = default;
|
||||
Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
auto* frame = m_impl->frames.Acquire();
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
auto& image = m_impl->ResolveSurface(info);
|
||||
if (image.backing.format == vk::Format::eUndefined) {
|
||||
EXIT("unsupported presentation source, image=%p\n", static_cast<const void*>(&image));
|
||||
}
|
||||
|
||||
auto frame_format = info.pixel_format;
|
||||
switch (frame_format) {
|
||||
case vk::Format::eR8G8B8A8Srgb: frame_format = vk::Format::eR8G8B8A8Unorm; break;
|
||||
case vk::Format::eB8G8R8A8Srgb: frame_format = vk::Format::eB8G8R8A8Unorm; break;
|
||||
default: break;
|
||||
}
|
||||
auto* frame = m_impl->frames.Acquire({info.extent.width, info.extent.height}, frame_format);
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
auto& image = m_impl->ResolveSurface(info);
|
||||
if (image.backing.format == vk::Format::eUndefined) {
|
||||
EXIT("unsupported presentation source, image=%p\n", static_cast<const void*>(&image));
|
||||
}
|
||||
frame->Configure(m_impl->window.graphic_ctx,
|
||||
{image.backing.extent.width, image.backing.extent.height}, frame_format);
|
||||
frame->CopyFrom(buffer, image);
|
||||
@@ -744,7 +893,7 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
||||
CommandBuffer* producer) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto format = m_impl->frames.GetFormat();
|
||||
auto* frame = m_impl->frames.Acquire();
|
||||
auto* frame = m_impl->frames.Acquire({width, height}, format);
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
frame->Configure(m_impl->window.graphic_ctx, {width, height}, format);
|
||||
vk::ClearColorValue clear {};
|
||||
@@ -760,8 +909,13 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
||||
return *frame;
|
||||
}
|
||||
|
||||
Presenter::Frame* Presenter::PrepareLastFrame() {
|
||||
return m_impl->frames.AcquireLast();
|
||||
bool Presenter::PresentLastFrame() {
|
||||
Common::LockGuard lock(m_impl->present_mutex);
|
||||
if (m_impl->layers[0].frame == nullptr && m_impl->layers[1].frame == nullptr) {
|
||||
return false;
|
||||
}
|
||||
m_impl->Present();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Presenter::IsGuestPaused() const noexcept {
|
||||
@@ -778,44 +932,76 @@ RenderContext& Presenter::Renderer() const noexcept {
|
||||
return m_impl->renderer;
|
||||
}
|
||||
|
||||
void Presenter::Present(Frame& frame, bool reuse) {
|
||||
void Presenter::Present(Frame& frame) {
|
||||
const Layer layer {&frame, 0, false};
|
||||
Present(std::span(&layer, 1));
|
||||
}
|
||||
|
||||
void Presenter::Present(std::span<const Layer> layers) {
|
||||
Common::LockGuard lock(m_impl->present_mutex);
|
||||
for (const auto& layer: layers) {
|
||||
EXIT_IF(layer.bus < 0 || layer.bus >= static_cast<int>(m_impl->layers.size()));
|
||||
m_impl->frames.ValidateForPresent(layer.frame);
|
||||
auto& previous = m_impl->layers[layer.bus];
|
||||
if (previous.frame != nullptr) {
|
||||
m_impl->frames.Release(previous.frame);
|
||||
}
|
||||
previous = layer;
|
||||
}
|
||||
m_impl->Present();
|
||||
}
|
||||
|
||||
void Presenter::ClearLayer(int bus) {
|
||||
if (static_cast<size_t>(bus) >= m_impl->layers.size()) {
|
||||
return;
|
||||
}
|
||||
Common::LockGuard lock(m_impl->present_mutex);
|
||||
auto& layer = m_impl->layers[bus];
|
||||
if (layer.frame != nullptr) {
|
||||
m_impl->frames.Release(layer.frame);
|
||||
layer = {};
|
||||
}
|
||||
}
|
||||
|
||||
void Presenter::Impl::Present() {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
m_impl->frames.ValidateForPresent(&frame, reuse);
|
||||
|
||||
const auto overlay_visual = GetSystemOverlayVisualState();
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
// Some window systems keep presenting an old swapchain after a resize.
|
||||
if (swapchain.NeedsResize()) {
|
||||
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
RecoverSwapchain(Swapchain::Status::Recreate);
|
||||
}
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
auto status = swapchain.AcquireNextImage();
|
||||
if (status != Swapchain::Status::Success) {
|
||||
m_impl->RecoverSwapchain(status);
|
||||
RecoverSwapchain(status);
|
||||
continue;
|
||||
}
|
||||
{
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
auto& command = m_impl->present_scheduler.BeginCommand();
|
||||
Common::LockGuard render_lock(renderer.GetMutex());
|
||||
auto& command = present_scheduler.BeginCommand();
|
||||
const bool draw_system_overlay =
|
||||
overlay_visual.active && swapchain.PrepareSystemOverlay();
|
||||
swapchain.RecordPresentCommands(command, frame.image, draw_system_overlay);
|
||||
frame.present_tick = swapchain.Submit(m_impl->present_scheduler);
|
||||
swapchain.RecordPresentCommands(command, layers[0].frame, layers[1],
|
||||
draw_system_overlay);
|
||||
const auto tick = swapchain.Submit(present_scheduler);
|
||||
for (const auto& layer: layers) {
|
||||
if (layer.frame != nullptr) {
|
||||
layer.frame->present_tick = tick;
|
||||
}
|
||||
}
|
||||
}
|
||||
status = swapchain.Present();
|
||||
if (status != Swapchain::Status::Success) {
|
||||
m_impl->RecoverSwapchain(status);
|
||||
RecoverSwapchain(status);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_impl->presented_overlay_revision.store(overlay_visual.revision,
|
||||
std::memory_order_release);
|
||||
m_impl->window.UpdateTitle();
|
||||
m_impl->frames.Release(&frame, true);
|
||||
presented_overlay_revision.store(overlay_visual.revision, std::memory_order_release);
|
||||
window.UpdateTitle();
|
||||
return;
|
||||
}
|
||||
LOGF("Vulkan presentation retry exhausted; dropping frame\n");
|
||||
m_impl->frames.Release(&frame, reuse);
|
||||
}
|
||||
|
||||
void Presenter::Discard(Frame& frame) {
|
||||
|
||||
@@ -564,8 +564,10 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
vk::PhysicalDeviceVulkan11Properties properties11 {};
|
||||
properties11.pNext = &subgroup_size_control;
|
||||
|
||||
vk::PhysicalDeviceFloatControlsProperties float_controls {};
|
||||
float_controls.pNext = &properties11;
|
||||
vk::PhysicalDeviceProperties2 properties2 {};
|
||||
properties2.pNext = &properties11;
|
||||
properties2.pNext = &float_controls;
|
||||
|
||||
if (graphics.mesh_shader_enabled) {
|
||||
subgroup_size_control.pNext = &graphics.mesh_shader_properties;
|
||||
@@ -628,6 +630,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
device_features.shaderFloat64 =
|
||||
supported_features2.features.shaderFloat64 &&
|
||||
float_controls.shaderSignedZeroInfNanPreserveFloat64 &&
|
||||
float_controls.shaderRoundingModeRTEFloat32;
|
||||
// if (device_features.shaderFloat64 && !float_controls.shaderDenormPreserveFloat64) {
|
||||
// Log::WriteToConsoleAndLog(
|
||||
// "WARNING: Vulkan device does not guarantee FP64 denormal preservation; "
|
||||
// "continuing with native FP64 arithmetic. Very small values may be flushed to zero.\n");
|
||||
// }
|
||||
|
||||
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2 {};
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -34,9 +35,6 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
present[index] = true;
|
||||
expected[index] = std::move(resources);
|
||||
};
|
||||
if (!program.info.buffers.empty()) {
|
||||
Expect(Kind::Buffers, Dense(program.info.buffers.size()));
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = IR::DescriptorBindingForImage(program.info.images[i]);
|
||||
if (!kind.has_value()) {
|
||||
@@ -54,23 +52,9 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
if (!program.info.samplers.empty()) {
|
||||
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
|
||||
}
|
||||
bool uses_gds = false;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::SharedAccessOf(inst.GetOpcode()) == IR::SharedAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<IR::MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
Fail(program, "shared operation has invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
uses_gds |= kind == IR::ResourceKind::Gds;
|
||||
}
|
||||
}
|
||||
auto& buffers = expected[static_cast<size_t>(Kind::Buffers)];
|
||||
const bool uses_gds = IR::CollectMemoryResources(program, buffers);
|
||||
present[static_cast<size_t>(Kind::Buffers)] = !buffers.empty();
|
||||
if (uses_gds) {
|
||||
Expect(Kind::Gds);
|
||||
}
|
||||
@@ -109,7 +93,7 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.memory_offset_count != program.info.buffers.size() ||
|
||||
program.bindings.memory_offset_count != buffers.size() ||
|
||||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
@@ -198,6 +182,7 @@ Emitter::SpirvRequirements Emitter::AnalyzeProgramRequirements(const IR::Program
|
||||
SpirvRequirements requirements {};
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
requirements.float64 |= inst.GetType() == IR::Type::F64;
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::Atomic &&
|
||||
inst.GetType() == IR::Type::U64) {
|
||||
requirements.buffer_int64_atomics = true;
|
||||
|
||||
@@ -291,9 +291,20 @@ uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0) {
|
||||
return EmitF32ToU32(state, arg0, false);
|
||||
}
|
||||
|
||||
uint32_t EmitConvertF32S32(EmitterState& state, uint32_t arg0) {
|
||||
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
|
||||
return EmitNative<spv::OpConvertSToF, IR::Type::F32>(state, signed_value);
|
||||
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0) {
|
||||
const auto converted = Unary(state, spv::OpFConvert, TypeF32(state), arg0);
|
||||
const auto source = EmitNative<spv::OpCompositeExtract, IR::Type::U32>(
|
||||
state, Unary(state, spv::OpBitcast, TypeU64(state), arg0), 1u);
|
||||
const auto exponent = EmitAndConstant(state, source, 0x7ff00000u);
|
||||
const auto overflow =
|
||||
Binary(state, spv::OpLogicalAnd, TypeBool(state),
|
||||
EmitCompareU32Constant(state, spv::OpUGreaterThan, exponent, 0x47e00000u),
|
||||
EmitCompareU32Constant(state, spv::OpULessThan, exponent, 0x7ff00000u));
|
||||
const auto clamped = EmitOrU32(state, EmitAndConstant(state, source, 0x80000000u),
|
||||
ConstantU32(state, 0x7f7fffffu));
|
||||
return EmitFlushF32DenormToSignedZero(
|
||||
state, Select(state, TypeF32(state), overflow,
|
||||
Unary(state, spv::OpBitcast, TypeF32(state), clamped), converted));
|
||||
}
|
||||
|
||||
uint32_t EmitCompositeExtractU64(EmitterState& state, uint32_t arg0, IR::Value arg1) {
|
||||
@@ -453,6 +464,11 @@ uint32_t EmitFPRecip32(EmitterState& state, uint32_t arg0) {
|
||||
return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), source);
|
||||
}
|
||||
|
||||
uint32_t EmitFPRecip64(EmitterState& state, uint32_t arg0) {
|
||||
const auto one = state.builder.Constant(spv::OpConstant, TypeF64(state), 0u, 0x3ff00000u);
|
||||
return EmitNative<spv::OpFDiv, IR::Type::F64>(state, one, arg0);
|
||||
}
|
||||
|
||||
uint32_t EmitFPRecipIFlag32(EmitterState& state, uint32_t arg0) {
|
||||
// Integer-to-float inputs used by IFLAG cannot be denormal.
|
||||
return Binary(state, spv::OpFDiv, TypeF32(state), ConstantF32(state, 0x3f800000u), arg0);
|
||||
|
||||
@@ -877,7 +877,7 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return;
|
||||
}
|
||||
const auto atomic_opcode = ImageAtomicOpcode(op);
|
||||
if (atomic_opcode != spv::OpNop) {
|
||||
if (image_info.access == IR::ImageAccess::Atomic) {
|
||||
const auto dimension = image.dimension;
|
||||
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
@@ -887,6 +887,15 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension),
|
||||
ConstantU32(state, 0));
|
||||
if (op == IR::ValueOpcode::ImageAtomicFMin32 ||
|
||||
op == IR::ValueOpcode::ImageAtomicFMax32) {
|
||||
return AtomicUpdate(state, pointer, IR::ResourceKind::Image,
|
||||
[&](uint32_t old) {
|
||||
return EmitFloatAtomicReplacement(
|
||||
state, old, ctx.Arg(inst, 2),
|
||||
op == IR::ValueOpcode::ImageAtomicFMax32);
|
||||
});
|
||||
}
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction(atomic_opcode, TypeU32(state), old, pointer,
|
||||
ConstantU32(state, spv::ScopeDevice),
|
||||
|
||||
@@ -13,13 +13,22 @@ inline constexpr auto EmitConvertU32U16 = EmitBitCastU16F16;
|
||||
inline constexpr auto EmitConvertU32U8 = EmitBitCastU16F16;
|
||||
inline constexpr auto EmitBitCastU32F32 = EmitBitCastU16F16;
|
||||
EMIT_NATIVE(BitCastF32U32, OpBitcast, F32, uint32_t)
|
||||
EMIT_NATIVE(BitCastU64F64, OpBitcast, U64, uint32_t)
|
||||
EMIT_NATIVE(BitCastF64U64, OpBitcast, F64, uint32_t)
|
||||
uint32_t EmitConvertU16U32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertU8U32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertF16F32(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitConvertF32F16 = EmitF16BitsToF32;
|
||||
uint32_t EmitConvertS32F32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertU32F32(EmitterState& state, uint32_t arg0);
|
||||
uint32_t EmitConvertF32S32(EmitterState& state, uint32_t arg0);
|
||||
template <IR::Type type>
|
||||
uint32_t EmitConvertSigned32ToFloat(EmitterState& state, uint32_t arg0) {
|
||||
const auto signed_value = Unary(state, spv::OpBitcast, TypeI32(state), arg0);
|
||||
return EmitNative<spv::OpConvertSToF, type>(state, signed_value);
|
||||
}
|
||||
inline constexpr auto EmitConvertF32S32 = EmitConvertSigned32ToFloat<IR::Type::F32>;
|
||||
inline constexpr auto EmitConvertF64S32 = EmitConvertSigned32ToFloat<IR::Type::F64>;
|
||||
uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0);
|
||||
EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t)
|
||||
EMIT_NATIVE(CompositeConstructU64, OpCompositeConstruct, U64, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(CompositeConstructU32x2, OpCompositeConstruct, U32x2, uint32_t, uint32_t)
|
||||
@@ -120,6 +129,10 @@ inline constexpr auto EmitFPCmpClass32 = EmitClassMaskF32;
|
||||
EMIT_NATIVE(FPAdd32, OpFAdd, F32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPSub32, OpFSub, F32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPMul32, OpFMul, F32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(FPMul64, OpFMul, F64, uint32_t, uint32_t)
|
||||
inline constexpr auto EmitFPFma64 =
|
||||
EmitGlsl<GLSLstd450Fma, IR::Type::F64, uint32_t, uint32_t, uint32_t>;
|
||||
uint32_t EmitFPRecip64(EmitterState& state, uint32_t arg0);
|
||||
inline constexpr auto EmitFPFma32 =
|
||||
EmitGlsl<GLSLstd450Fma, IR::Type::F32, uint32_t, uint32_t, uint32_t>;
|
||||
uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1);
|
||||
@@ -271,6 +284,8 @@ inline constexpr auto EmitImageAtomicUMax32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicAnd32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicOr32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicXor32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicFMin32 = EmitImage;
|
||||
inline constexpr auto EmitImageAtomicFMax32 = EmitImage;
|
||||
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
inline constexpr auto EmitPhi = EmitUnreachable;
|
||||
inline constexpr auto EmitTessellationBase = EmitUnreachable;
|
||||
|
||||
@@ -68,6 +68,7 @@ struct SpirvRequirements {
|
||||
bool pixel_valid_mask = false;
|
||||
bool buffer_int64_atomics = false;
|
||||
bool coherent_buffers = false;
|
||||
bool float64 = false;
|
||||
};
|
||||
|
||||
SpirvRequirements AnalyzeProgramRequirements(const IR::Program& program);
|
||||
@@ -139,6 +140,7 @@ uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
uint32_t TypeF32(EmitterState& state);
|
||||
uint32_t TypeF64(EmitterState& state);
|
||||
uint32_t TypeU32Vector(EmitterState& state, uint32_t components);
|
||||
|
||||
uint32_t TypeU32Composite(EmitterState& state, uint32_t components);
|
||||
@@ -167,6 +169,10 @@ template <spv::Op opcode, IR::Type type, typename... Args>
|
||||
uint32_t EmitNative(EmitterState& state, Args... args) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(opcode, TypeId(state, type), result, args...);
|
||||
if constexpr (type == IR::Type::F64 &&
|
||||
(opcode == spv::OpFMul || opcode == spv::OpFDiv || opcode == spv::OpExtInst)) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -706,26 +706,66 @@ uint32_t FormattedOutOfBoundsValue(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
return ConstructU32Composite(ctx.state, components, values);
|
||||
}
|
||||
|
||||
void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const PreparedFormattedMemory& plan, uint32_t component,
|
||||
uint32_t data) {
|
||||
if (component >= plan.info.component_count) return;
|
||||
const auto bits = plan.info.component_bits[component];
|
||||
if (bits == 16u && (plan.info.type == Format::ComponentType::Snorm ||
|
||||
plan.info.type == Format::ComponentType::Float)) {
|
||||
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data) {
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.type == Format::ComponentType::Unorm) {
|
||||
const auto value = EmitBitCastF32U32(ctx.state, data);
|
||||
const auto clamped = EmitFPMin32(
|
||||
ctx.state, EmitFPMax32(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)),
|
||||
ConstantF32Value(ctx.state, 1.0f));
|
||||
const auto scaled = EmitFPMul32(
|
||||
ctx.state, clamped, ConstantF32Value(ctx.state, float((1u << bits) - 1u)));
|
||||
return Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
|
||||
EmitFPRoundEven32(ctx.state, scaled));
|
||||
}
|
||||
if (bits == 16u && (info.type == Format::ComponentType::Snorm ||
|
||||
info.type == Format::ComponentType::Float)) {
|
||||
const auto value = EmitBitCastF32U32(ctx.state, data);
|
||||
const auto pair = EmitCompositeConstructF32x2(ctx.state, value,
|
||||
ConstantF32Value(ctx.state, 0.0f));
|
||||
data = plan.info.type == Format::ComponentType::Float
|
||||
? EmitPackHalf2x16(ctx.state, pair)
|
||||
: EmitPackSnorm2x16(ctx.state, pair);
|
||||
}
|
||||
if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component],
|
||||
plan.indices[component], bits, data);
|
||||
} else {
|
||||
StoreWordInBounds(ctx, plan.resource, plan.indices[component], data);
|
||||
return info.type == Format::ComponentType::Float ? EmitPackHalf2x16(ctx.state, pair)
|
||||
: EmitPackSnorm2x16(ctx.state, pair);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
|
||||
const Format::BufferFormatInfo& info, uint32_t data,
|
||||
uint32_t components) {
|
||||
// RDNA2 formatted stores transfer the entire format, zero-filling missing VGPRs.
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, info.component_count,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
|
||||
auto packed = ConstantU32(ctx.state, 0);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
auto value = ConstantU32(ctx.state, 0);
|
||||
if (component < components) {
|
||||
value = data;
|
||||
if (components != 1u) {
|
||||
value = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state),
|
||||
value, data, component);
|
||||
}
|
||||
}
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.packed_bitfield) {
|
||||
packed = EmitBitFieldInsert(
|
||||
ctx.state, packed, value,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, bits));
|
||||
} else if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordInBounds(ctx, mem, resource, plan.addresses[component],
|
||||
plan.indices[component], bits, value);
|
||||
} else {
|
||||
StoreWordInBounds(ctx, resource, plan.indices[component], value);
|
||||
}
|
||||
}
|
||||
if (info.packed_bitfield) StoreWordInBounds(ctx, resource, plan.indices[0], packed);
|
||||
});
|
||||
}
|
||||
|
||||
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
@@ -737,11 +777,7 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource, data);
|
||||
return;
|
||||
}
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, 1u,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
|
||||
StoreFormattedInBounds(ctx, mem, plan, 0u, data);
|
||||
});
|
||||
StoreFormattedPrepared(ctx, inst, mem, resource, info, data, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -859,16 +895,7 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, components,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(state, plan.in_bounds, [&]() {
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data,
|
||||
composite, component);
|
||||
StoreFormattedInBounds(ctx, mem, plan, component, data);
|
||||
}
|
||||
});
|
||||
StoreFormattedPrepared(ctx, inst, mem, resource, info, composite, components);
|
||||
return;
|
||||
}
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
|
||||
@@ -47,6 +47,10 @@ uint32_t TypeF32(EmitterState& state) {
|
||||
return state.builder.Type(spv::OpTypeFloat, 32);
|
||||
}
|
||||
|
||||
uint32_t TypeF64(EmitterState& state) {
|
||||
return state.builder.Type(spv::OpTypeFloat, 64);
|
||||
}
|
||||
|
||||
uint32_t TypeU32Vector(EmitterState& state, uint32_t components) {
|
||||
return state.builder.Type(spv::OpTypeVector, TypeU32(state), components);
|
||||
}
|
||||
@@ -679,6 +683,19 @@ void DefineModule(EmitterState& state) {
|
||||
// contract prevents host compilers from treating synthesized IEEE values as finite.
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
|
||||
32u);
|
||||
if (state.requirements.float64) {
|
||||
EXIT_NOT_IMPLEMENTED(state.program.stage != ShaderType::Compute);
|
||||
EXIT_NOT_IMPLEMENTED(state.input_info.compute->float_mode != 0xc0);
|
||||
// MODE=0xc0 uses round-to-nearest-even and preserves FP64 input/output denormals.
|
||||
state.builder.RequireCapability(spv::CapabilityFloat64);
|
||||
state.builder.RequireCapability(spv::CapabilityRoundingModeRTE);
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve,
|
||||
64u);
|
||||
// FP64 denormal preservation is temporarily disabled.
|
||||
// state.builder.RequireCapability(spv::CapabilityDenormPreserve);
|
||||
// state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeDenormPreserve, 64u);
|
||||
state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeRoundingModeRTE, 32u);
|
||||
}
|
||||
if (const auto* cs = ShaderWorkgroupInput(state.program.stage, state.input_info)) {
|
||||
uint32_t local_x = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
|
||||
@@ -440,6 +440,7 @@ uint32_t TypeId(EmitterState& state, IR::Type type) {
|
||||
case IR::Type::U64: return TypeU64(state);
|
||||
case IR::Type::U32x2: return TypeU32Pair(state);
|
||||
case IR::Type::F32: return TypeF32(state);
|
||||
case IR::Type::F64: return TypeF64(state);
|
||||
case IR::Type::U32x3: return TypeU32Vector(state, 3);
|
||||
case IR::Type::U32x4: return TypeU32Vector(state, 4);
|
||||
case IR::Type::F32x2: return TypeF32Vector(state, 2);
|
||||
|
||||
@@ -1389,6 +1389,15 @@ bool SplitSharedMergeBlock(Graph& graph, uint32_t merge,
|
||||
if (construct_predecessors.empty() || (!force_split && !has_external_predecessor)) {
|
||||
return false;
|
||||
}
|
||||
if (construct_predecessors.size() == 1u) {
|
||||
const auto* predecessor = graph.FindBlock(construct_predecessors.front());
|
||||
if (predecessor != nullptr && predecessor->inst_begin == predecessor->inst_end &&
|
||||
predecessor->terminator.kind == TerminatorKind::Branch &&
|
||||
predecessor->terminator.true_block == merge &&
|
||||
predecessor->start_pc == merge_block->start_pc) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto synthetic_merge = AppendSyntheticBranchBlock(graph, merge);
|
||||
auto* synthetic_block = graph.FindBlock(synthetic_merge);
|
||||
if (synthetic_block != nullptr) {
|
||||
|
||||
@@ -198,6 +198,8 @@ constexpr Detail::OpcodeMap MIMG_ATOMIC_OPCODE_LIST[] = {
|
||||
{0x18u, Opcode::IMAGE_ATOMIC_AND},
|
||||
{0x19u, Opcode::IMAGE_ATOMIC_OR},
|
||||
{0x1au, Opcode::IMAGE_ATOMIC_XOR},
|
||||
{0x1eu, Opcode::IMAGE_ATOMIC_FMIN},
|
||||
{0x1fu, Opcode::IMAGE_ATOMIC_FMAX},
|
||||
};
|
||||
|
||||
constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCODE_LIST);
|
||||
|
||||
@@ -579,6 +579,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::IMAGE_ATOMIC_AND:
|
||||
case Opcode::IMAGE_ATOMIC_OR:
|
||||
case Opcode::IMAGE_ATOMIC_XOR:
|
||||
case Opcode::IMAGE_ATOMIC_FMIN:
|
||||
case Opcode::IMAGE_ATOMIC_FMAX:
|
||||
case Opcode::IMAGE_LOAD:
|
||||
case Opcode::IMAGE_LOAD_MIP:
|
||||
case Opcode::IMAGE_GET_RESINFO:
|
||||
|
||||
@@ -154,6 +154,8 @@ enum class Opcode {
|
||||
V_CUBEMA_F32,
|
||||
V_CNDMASK_B32,
|
||||
V_DOT2C_F32_F16,
|
||||
V_CVT_F64_I32,
|
||||
V_CVT_F32_F64,
|
||||
V_CVT_F32_I32,
|
||||
V_CVT_F32_U32,
|
||||
V_CVT_U32_F32,
|
||||
@@ -173,6 +175,7 @@ enum class Opcode {
|
||||
V_CVT_F32_UBYTE1,
|
||||
V_CVT_F32_UBYTE2,
|
||||
V_CVT_F32_UBYTE3,
|
||||
V_RCP_F64,
|
||||
V_RCP_F32,
|
||||
V_RCP_IFLAG_F32,
|
||||
V_FRACT_F32,
|
||||
@@ -234,6 +237,8 @@ enum class Opcode {
|
||||
V_MAD_I32_I24,
|
||||
V_MAD_U32_U24,
|
||||
V_MAD_U64_U32,
|
||||
V_FMA_F64,
|
||||
V_MUL_F64,
|
||||
V_FMA_F32,
|
||||
V_FMA_F16,
|
||||
V_PACK_B32_F16,
|
||||
@@ -354,6 +359,7 @@ enum class Opcode {
|
||||
V_CMPX_GT_F32,
|
||||
V_CMPX_LG_F32,
|
||||
V_CMPX_GE_F32,
|
||||
V_CMPX_O_F32,
|
||||
V_CMPX_NGE_F32,
|
||||
V_CMPX_NLG_F32,
|
||||
V_CMPX_NGT_F32,
|
||||
@@ -404,6 +410,7 @@ enum class Opcode {
|
||||
V_CMP_LE_U16,
|
||||
V_CMP_GT_U16,
|
||||
V_CMPX_LT_U16,
|
||||
V_CMPX_EQ_U16,
|
||||
V_CMPX_GT_U16,
|
||||
V_CMP_NE_U16,
|
||||
V_CMP_GE_U16,
|
||||
@@ -569,6 +576,8 @@ enum class Opcode {
|
||||
IMAGE_ATOMIC_AND,
|
||||
IMAGE_ATOMIC_OR,
|
||||
IMAGE_ATOMIC_XOR,
|
||||
IMAGE_ATOMIC_FMIN,
|
||||
IMAGE_ATOMIC_FMAX,
|
||||
IMAGE_SAMPLE,
|
||||
IMAGE_GATHER4_L,
|
||||
IMAGE_GATHER4_LZ,
|
||||
|
||||
@@ -87,6 +87,9 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = {
|
||||
{0x00u, Opcode::V_NOP},
|
||||
{0x01u, Opcode::V_MOV_B32},
|
||||
{0x02u, Opcode::V_READFIRSTLANE_B32},
|
||||
{0x04u, Opcode::V_CVT_F64_I32},
|
||||
{0x0fu, Opcode::V_CVT_F32_F64},
|
||||
{0x2fu, Opcode::V_RCP_F64},
|
||||
{0x05u, Opcode::V_CVT_F32_I32},
|
||||
{0x06u, Opcode::V_CVT_F32_U32},
|
||||
{0x07u, Opcode::V_CVT_U32_F32},
|
||||
@@ -146,6 +149,9 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = {
|
||||
{0x00u, Opcode::V_NOP},
|
||||
{0x01u, Opcode::V_MOV_B32},
|
||||
{0x02u, Opcode::V_READFIRSTLANE_B32},
|
||||
{0x04u, Opcode::V_CVT_F64_I32},
|
||||
{0x0fu, Opcode::V_CVT_F32_F64},
|
||||
{0x2fu, Opcode::V_RCP_F64},
|
||||
{0x05u, Opcode::V_CVT_F32_I32},
|
||||
{0x06u, Opcode::V_CVT_F32_U32},
|
||||
{0x07u, Opcode::V_CVT_U32_F32},
|
||||
@@ -214,6 +220,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x11u, Opcode::V_CMPX_LT_F32}, {0x12u, Opcode::V_CMPX_EQ_F32},
|
||||
{0x13u, Opcode::V_CMPX_LE_F32}, {0x14u, Opcode::V_CMPX_GT_F32},
|
||||
{0x15u, Opcode::V_CMPX_LG_F32}, {0x16u, Opcode::V_CMPX_GE_F32},
|
||||
{0x17u, Opcode::V_CMPX_O_F32},
|
||||
{0x19u, Opcode::V_CMPX_NGE_F32}, {0x1au, Opcode::V_CMPX_NLG_F32},
|
||||
{0x1bu, Opcode::V_CMPX_NGT_F32}, {0x1cu, Opcode::V_CMPX_NLE_F32},
|
||||
{0x1du, Opcode::V_CMPX_NEQ_F32}, {0x1eu, Opcode::V_CMPX_NLT_F32},
|
||||
@@ -231,7 +238,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16},
|
||||
{0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16},
|
||||
{0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16},
|
||||
{0xb9u, Opcode::V_CMPX_LT_U16, false},
|
||||
{0xb9u, Opcode::V_CMPX_LT_U16, false}, {0xbau, Opcode::V_CMPX_EQ_U16, false},
|
||||
{0xbcu, Opcode::V_CMPX_GT_U16}, {0xc0u, Opcode::V_CMP_F_U32},
|
||||
{0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32},
|
||||
{0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32},
|
||||
@@ -266,6 +273,8 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x146u, Opcode::V_CUBETC_F32},
|
||||
{0x147u, Opcode::V_CUBEMA_F32},
|
||||
{0x14bu, Opcode::V_FMA_F32},
|
||||
{0x14cu, Opcode::V_FMA_F64},
|
||||
{0x165u, Opcode::V_MUL_F64},
|
||||
{0x148u, Opcode::V_BFE_U32},
|
||||
{0x149u, Opcode::V_BFE_I32},
|
||||
{0x14au, Opcode::V_BFI_B32},
|
||||
@@ -451,6 +460,8 @@ bool IsVopcCompareExec(Opcode opcode);
|
||||
|
||||
bool IsVop1FloatSourceOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::V_CVT_F32_F64:
|
||||
case Opcode::V_RCP_F64:
|
||||
case Opcode::V_MOV_B32:
|
||||
case Opcode::V_CVT_F32_F16:
|
||||
case Opcode::V_CVT_U32_F32:
|
||||
@@ -521,9 +532,12 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
|
||||
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
// The source field extract and the destination insert are independent (Translate.cpp), so a
|
||||
// partial destination may combine with any source selector.
|
||||
{Opcode::V_NOT_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
|
||||
SdwaSelFull(), false},
|
||||
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
|
||||
{Opcode::V_FFBL_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_FFBH_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
|
||||
{Opcode::V_CVT_F32_F16, SdwaSelWords() | SdwaSelFull(), 0, 0, true},
|
||||
{Opcode::V_CVT_F16_F32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
|
||||
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), true},
|
||||
@@ -706,6 +720,11 @@ void ApplyDppModifier(Operand& operand, uint32_t modifier, uint32_t encoding) {
|
||||
|
||||
void DecodeVop1Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
uint32_t opcode, uint32_t vdst, Instruction& inst) {
|
||||
if (inst.opcode == Opcode::V_CVT_F64_I32 || inst.opcode == Opcode::V_CVT_F32_F64 ||
|
||||
inst.opcode == Opcode::V_RCP_F64) {
|
||||
SetUnsupported(inst, Family::VOP1, opcode, "FP64 instructions do not support DPP");
|
||||
return;
|
||||
}
|
||||
const auto modifier = code[word_index + 1u];
|
||||
const auto src0 = modifier & 0xffu;
|
||||
SetRawWords(inst, code, word_index, 2);
|
||||
@@ -755,6 +774,7 @@ bool IsVop2FloatOpcode(Opcode opcode) {
|
||||
|
||||
bool IsVop1FloatResultOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::V_CVT_F32_F64:
|
||||
case Opcode::V_CVT_F32_I32:
|
||||
case Opcode::V_CVT_F32_U32:
|
||||
case Opcode::V_CVT_F32_F16:
|
||||
@@ -820,6 +840,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GT_F32:
|
||||
case Opcode::V_CMPX_LG_F32:
|
||||
case Opcode::V_CMPX_GE_F32:
|
||||
case Opcode::V_CMPX_O_F32:
|
||||
case Opcode::V_CMPX_NGE_F32:
|
||||
case Opcode::V_CMPX_NLG_F32:
|
||||
case Opcode::V_CMPX_NGT_F32:
|
||||
@@ -1174,6 +1195,7 @@ void DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
|
||||
uint32_t NativeVop3SourceCount(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::V_MUL_F64:
|
||||
case Opcode::V_MUL_LO_U32:
|
||||
case Opcode::V_MUL_HI_U32:
|
||||
case Opcode::V_MUL_LO_I32:
|
||||
@@ -1324,6 +1346,8 @@ bool SupportsNativeVop3SourceModifiers(Opcode opcode) {
|
||||
case Opcode::V_MAX_F32:
|
||||
case Opcode::V_MAC_F32:
|
||||
case Opcode::V_MAD_F32:
|
||||
case Opcode::V_MUL_F64:
|
||||
case Opcode::V_FMA_F64:
|
||||
case Opcode::V_FMA_F32:
|
||||
case Opcode::V_PACK_B32_F16:
|
||||
case Opcode::V_CUBEID_F32:
|
||||
@@ -1356,6 +1380,7 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
|
||||
case Opcode::V_MAD_F32:
|
||||
case Opcode::V_FMA_F32:
|
||||
case Opcode::V_FMA_F16:
|
||||
case Opcode::V_CUBEID_F32:
|
||||
case Opcode::V_CVT_PKRTZ_F16_F32:
|
||||
case Opcode::V_LDEXP_F32:
|
||||
case Opcode::V_MIN3_F32:
|
||||
@@ -1432,6 +1457,7 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GT_F32:
|
||||
case Opcode::V_CMPX_LG_F32:
|
||||
case Opcode::V_CMPX_GE_F32:
|
||||
case Opcode::V_CMPX_O_F32:
|
||||
case Opcode::V_CMPX_NGE_F32:
|
||||
case Opcode::V_CMPX_NLG_F32:
|
||||
case Opcode::V_CMPX_NGT_F32:
|
||||
@@ -1454,6 +1480,7 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_NE_I64:
|
||||
case Opcode::V_CMPX_NE_U64:
|
||||
case Opcode::V_CMPX_LT_U16:
|
||||
case Opcode::V_CMPX_EQ_U16:
|
||||
case Opcode::V_CMPX_GT_U16:
|
||||
case Opcode::V_CMPX_LT_F16:
|
||||
case Opcode::V_CMPX_EQ_F16:
|
||||
|
||||
@@ -106,12 +106,12 @@ void Translator::EXP(const Decoder::Instruction& inst) {
|
||||
ir.Emit(IR::ValueOpcode::SetAttribute, {data, ir.GetExec()}, AddExportInfo(inst));
|
||||
}
|
||||
|
||||
bool Translator::EmitInterpolation(const Decoder::Instruction& inst) {
|
||||
void Translator::EmitInterpolation(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return true;
|
||||
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return true;
|
||||
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return true;
|
||||
default: return false;
|
||||
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return;
|
||||
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return;
|
||||
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return;
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpc
|
||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false, cmpx);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered) {
|
||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx) {
|
||||
const auto lhs = IR::F32(ReadOperand(inst.src0, IR::Type::F32));
|
||||
const auto rhs = IR::F32(ReadOperand(inst.src1, IR::Type::F32));
|
||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {lhs})),
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::FPIsNan32, {rhs})));
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, false);
|
||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx);
|
||||
}
|
||||
|
||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx) {
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
void Translator::FailMissingTranslation(const Decoder::Instruction& inst) {
|
||||
EXIT("opcode %s at pc 0x%08x has no IR translation",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc);
|
||||
}
|
||||
|
||||
void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
|
||||
current_opcode = inst.opcode;
|
||||
current_pc = inst.pc;
|
||||
@@ -16,35 +21,26 @@ void Translator::TranslateInstruction(const Decoder::Instruction& inst) {
|
||||
default: break;
|
||||
}
|
||||
|
||||
bool translated = false;
|
||||
switch (inst.family) {
|
||||
case Decoder::Family::SOP1:
|
||||
case Decoder::Family::SOP2:
|
||||
case Decoder::Family::SOPK:
|
||||
case Decoder::Family::SOPC:
|
||||
case Decoder::Family::SOPP: translated = EmitScalar(inst); break;
|
||||
case Decoder::Family::SOPP: return EmitScalar(inst);
|
||||
case Decoder::Family::VOP1:
|
||||
case Decoder::Family::VOP2:
|
||||
case Decoder::Family::VOP3:
|
||||
case Decoder::Family::VOP3P:
|
||||
case Decoder::Family::VOPC: translated = EmitVector(inst); break;
|
||||
case Decoder::Family::VOPC: return EmitVector(inst);
|
||||
case Decoder::Family::SMEM:
|
||||
case Decoder::Family::MUBUF:
|
||||
case Decoder::Family::MTBUF:
|
||||
case Decoder::Family::FLAT:
|
||||
case Decoder::Family::DS:
|
||||
case Decoder::Family::MIMG: translated = EmitMemory(inst); break;
|
||||
case Decoder::Family::VINTRP: translated = EmitInterpolation(inst); break;
|
||||
case Decoder::Family::EXP:
|
||||
EXP(inst);
|
||||
translated = true;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!translated) {
|
||||
EXIT("opcode %s at pc 0x%08x has no IR translation",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc);
|
||||
case Decoder::Family::MIMG: return EmitMemory(inst);
|
||||
case Decoder::Family::VINTRP: return EmitInterpolation(inst);
|
||||
case Decoder::Family::EXP: return EXP(inst);
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool accumulator, bool quiet_snan) {
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto lhs = ReadF16LaneAsF32(inst.src0, high, true);
|
||||
@@ -43,16 +43,15 @@ bool Translator::PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
result = PackU16Lanes(override_lane(false), override_lane(true));
|
||||
}
|
||||
WriteOperand(raw, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool invalid_negative) {
|
||||
const auto argument = ReadF16AsF32(inst.src0);
|
||||
auto result = IR::F32(ir.Emit(opcode, {argument}));
|
||||
if (!invalid_negative) {
|
||||
WriteF16(DestinationOperand(inst), result);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
const auto negative =
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::FPOrdLessThan32, {argument, IR::Value::F32(0.0f)}));
|
||||
@@ -60,10 +59,9 @@ bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
|
||||
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
|
||||
Write16Bits(DestinationOperand(inst), ir.Select(negative, invalid, bits));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto argument = ReadF16AsF32(inst.src0);
|
||||
const auto magnitude =
|
||||
ir.BitwiseAnd(ir.BitCastU32(argument), IR::U32(IR::Value(0x7fffffffu)));
|
||||
@@ -96,18 +94,16 @@ bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode o
|
||||
const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f)));
|
||||
const auto invalid = IR::U32(IR::Value(inst.dst.clamp ? 0u : 0xfe00u));
|
||||
Write16Bits(DestinationOperand(inst), ir.Select(infinite, invalid, bits));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool reverse) {
|
||||
const auto lhs = ReadF16AsF32(reverse ? inst.src1 : inst.src0);
|
||||
const auto rhs = ReadF16AsF32(reverse ? inst.src0 : inst.src1);
|
||||
WriteF16(DestinationOperand(inst), IR::F32(ir.Emit(opcode, {lhs, rhs})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool accumulator, bool mix) {
|
||||
std::array<IR::Value, 3> args;
|
||||
for (uint32_t index = 0; index < args.size(); index++) {
|
||||
@@ -115,16 +111,14 @@ bool Translator::Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcod
|
||||
args[index] = mix ? IR::Value(ReadMixF32(operand)) : IR::Value(ReadF16AsF32(operand));
|
||||
}
|
||||
WriteF16(DestinationOperand(inst), IR::F32(ir.Emit(opcode, {args[0], args[1], args[2]})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto type = IR::ArgTypeOf(opcode, 0);
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool reverse) {
|
||||
std::array<IR::Value, 2> args;
|
||||
for (uint32_t index = 0; index < args.size(); index++) {
|
||||
@@ -132,10 +126,9 @@ bool Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode o
|
||||
args[index] = ReadOperand(operand, IR::ArgTypeOf(opcode, index));
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {args[0], args[1]}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool accumulator, bool mix) {
|
||||
std::array<IR::Value, 3> args;
|
||||
for (uint32_t index = 0; index < args.size(); index++) {
|
||||
@@ -145,10 +138,9 @@ bool Translator::FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
: ReadOperand(operand, type);
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {args[0], args[1], args[2]}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_FREXP_MANT_F32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_FREXP_MANT_F32(const Decoder::Instruction& inst) {
|
||||
const auto bits = ReadU32(inst.src0);
|
||||
const auto exponent =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(23u), IR::Value(8u)}));
|
||||
@@ -166,10 +158,9 @@ bool Translator::V_FREXP_MANT_F32(const Decoder::Instruction& inst) {
|
||||
ir.Select(zero, bits, subnormal));
|
||||
const auto result = ir.Select(ir.IEqual(exponent, IR::U32(IR::Value(0xffu))), bits, finite);
|
||||
WriteOperand(DestinationOperand(inst), ir.BitCastF32(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_DOT2C_F32_F16(const Decoder::Instruction& inst) {
|
||||
void Translator::V_DOT2C_F32_F16(const Decoder::Instruction& inst) {
|
||||
auto a = inst.src0;
|
||||
a.op_sel = false;
|
||||
a.op_sel_hi = true;
|
||||
@@ -183,26 +174,25 @@ bool Translator::V_DOT2C_F32_F16(const Decoder::Instruction& inst) {
|
||||
const auto acc = ReadMixF32(inst.dst);
|
||||
const auto lo = IR::F32(ir.Emit(IR::ValueOpcode::FPFma32, {a_lo, b_lo, acc}));
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(IR::ValueOpcode::FPFma32, {a_hi, b_hi, lo}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_CUBEID_F32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_CUBEID_F32(const Decoder::Instruction& inst) {
|
||||
return FloatCube(inst, 0u);
|
||||
}
|
||||
|
||||
bool Translator::V_CUBESC_F32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_CUBESC_F32(const Decoder::Instruction& inst) {
|
||||
return FloatCube(inst, 1u);
|
||||
}
|
||||
|
||||
bool Translator::V_CUBETC_F32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_CUBETC_F32(const Decoder::Instruction& inst) {
|
||||
return FloatCube(inst, 2u);
|
||||
}
|
||||
|
||||
bool Translator::V_CUBEMA_F32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_CUBEMA_F32(const Decoder::Instruction& inst) {
|
||||
return FloatCube(inst, 3u);
|
||||
}
|
||||
|
||||
bool Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kind) {
|
||||
void Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kind) {
|
||||
const auto x = ReadMixF32(inst.src0);
|
||||
const auto y = ReadMixF32(inst.src1);
|
||||
const auto z = ReadMixF32(inst.src2);
|
||||
@@ -242,7 +232,6 @@ bool Translator::FloatCube(const Decoder::Instruction& inst, uint32_t result_kin
|
||||
default: EXIT("invalid cube result kind");
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -4,25 +4,23 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic) {
|
||||
const auto value = ReadU16AsU32(inst.src1, arithmetic);
|
||||
const auto count = ir.BitwiseAnd(ReadU16AsU32(inst.src0, false), IR::U32(IR::Value(15u)));
|
||||
const auto result = IR::U32(ir.Emit(opcode, {value, count}));
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool sign) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, sign);
|
||||
const auto rhs = ReadU16AsU32(inst.src1, sign);
|
||||
const auto result = IR::U32(ir.Emit(opcode, {lhs, rhs}));
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
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));
|
||||
@@ -31,18 +29,16 @@ bool Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)}));
|
||||
}
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MED3_I16(const Decoder::Instruction& inst) {
|
||||
void Translator::V_MED3_I16(const Decoder::Instruction& inst) {
|
||||
const auto result = IR::U32(ir.Emit(
|
||||
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
|
||||
ReadU16AsU32(inst.src2, true)}));
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic) {
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto count =
|
||||
@@ -52,10 +48,9 @@ bool Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::Valu
|
||||
};
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
PackU16Lanes(translate_lane(false), translate_lane(true)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto lhs = ReadU16LaneAsU32(inst.src0, high, false);
|
||||
const auto rhs = ReadU16LaneAsU32(inst.src1, high, false);
|
||||
@@ -63,10 +58,9 @@ bool Translator::PackedInteger16Binary(const Decoder::Instruction& inst, IR::Val
|
||||
};
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
PackU16Lanes(translate_lane(false), translate_lane(true)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::PackedInteger16Mad(const Decoder::Instruction& inst, bool sign) {
|
||||
void Translator::PackedInteger16Mad(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto lhs = ReadU16LaneAsU32(inst.src0, high, false);
|
||||
const auto rhs = ReadU16LaneAsU32(inst.src1, high, false);
|
||||
@@ -74,10 +68,9 @@ bool Translator::PackedInteger16Mad(const Decoder::Instruction& inst, bool sign)
|
||||
};
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
PackU16Lanes(translate_lane(false), translate_lane(true)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool sign) {
|
||||
const auto translate_lane = [&](bool high) {
|
||||
const auto lhs = ReadU16LaneAsU32(inst.src0, high, sign);
|
||||
@@ -86,7 +79,6 @@ bool Translator::PackedInteger16MinMax(const Decoder::Instruction& inst, IR::Val
|
||||
};
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
PackU16Lanes(translate_lane(false), translate_lane(true)));
|
||||
return true;
|
||||
}
|
||||
|
||||
IR::U1 Translator::U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
@@ -100,7 +92,7 @@ IR::U1 Translator::U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpco
|
||||
return negate_result ? ir.LogicalNot(result) : result;
|
||||
}
|
||||
|
||||
bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
|
||||
void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
|
||||
IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result,
|
||||
bool unary) {
|
||||
const auto invocation_result =
|
||||
@@ -119,7 +111,7 @@ bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
||||
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||
const auto mask = WriteMask(inst.dst, invocation_result, true);
|
||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
@@ -146,10 +138,9 @@ bool Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
||||
ir.SetScalarMaskTag(dst, mask_valid);
|
||||
}
|
||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
IR::Type type, bool reverse, bool mask_shift_count,
|
||||
bool update_scc) {
|
||||
std::array<IR::Value, 3> args;
|
||||
@@ -177,10 +168,9 @@ bool Translator::SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
ir.SetScc(ir.INotEqual(IR::U32(result), IR::U32(IR::Value(0u))));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
|
||||
// Signed 64-bit sources sign-extend literals; generic B64 operands zero-extend them.
|
||||
const auto source =
|
||||
inst.src0.kind == Decoder::OperandKind::LiteralConstant
|
||||
@@ -192,10 +182,9 @@ bool Translator::S_ASHR_I64(const Decoder::Instruction& inst) {
|
||||
ir.Emit(IR::ValueOpcode::ShiftRightArithmetic64, {source, ReadU32(inst.src1)});
|
||||
WriteOperand(inst.dst, result);
|
||||
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool negate_rhs, bool negate_result, bool update_scc) {
|
||||
const auto lhs = ReadU32(inst.src0);
|
||||
auto rhs = ReadU32(inst.src1);
|
||||
@@ -210,31 +199,27 @@ bool Translator::ComposedIntegerBinary(const Decoder::Instruction& inst, IR::Val
|
||||
if (update_scc) {
|
||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_AND_OR_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_AND_OR_B32(const Decoder::Instruction& inst) {
|
||||
const auto result =
|
||||
ir.BitwiseOr(ir.BitwiseAnd(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_OR3_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_OR3_B32(const Decoder::Instruction& inst) {
|
||||
const auto result =
|
||||
ir.BitwiseOr(ir.BitwiseOr(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_XOR3_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_XOR3_B32(const Decoder::Instruction& inst) {
|
||||
const auto result =
|
||||
ir.BitwiseXor(ir.BitwiseXor(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
|
||||
const auto source = ExtractU64(ReadU64(inst.src0));
|
||||
const auto low_lsb = IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {source[0]}));
|
||||
const auto high_lsb = IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {source[1]}));
|
||||
@@ -243,10 +228,9 @@ bool Translator::S_FF1_I32_B64(const Decoder::Instruction& inst) {
|
||||
ir.Select(ir.INotEqual(source[1], IR::U32(IR::Value(0u))),
|
||||
high_position, IR::U32(IR::Value(0xffffffffu))));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
|
||||
void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto source = ReadU32(inst.src0);
|
||||
const auto value = sign ? ir.BitwiseXor(
|
||||
source, ir.ShiftRightArithmetic(source, IR::U32(IR::Value(31u))))
|
||||
@@ -256,10 +240,9 @@ bool Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto result = ir.Select(ir.INotEqual(value, IR::U32(IR::Value(0u))), position,
|
||||
IR::U32(IR::Value(0xffffffffu)));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_FLBIT_I32_B64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_FLBIT_I32_B64(const Decoder::Instruction& inst) {
|
||||
const auto source = ReadU64(inst.src0);
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb64, {source}));
|
||||
const auto position = ir.ISub(IR::U32(IR::Value(63u)), msb);
|
||||
@@ -268,10 +251,9 @@ bool Translator::S_FLBIT_I32_B64(const Decoder::Instruction& inst) {
|
||||
{source, ir.ConstructU64(IR::U32(IR::Value(0u)), IR::U32(IR::Value(0u)))}));
|
||||
const auto result = ir.Select(nonzero, position, IR::U32(IR::Value(0xffffffffu)));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) {
|
||||
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) {
|
||||
const auto extract24 = [&](IR::U32 value) {
|
||||
return IR::U32(
|
||||
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
|
||||
@@ -284,10 +266,9 @@ bool Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
|
||||
result = ir.IAdd(result, ReadU32(inst.src2));
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadU32(inst.src0);
|
||||
const auto rhs = ReadU32(inst.src1);
|
||||
const auto add = ExtractU64(ReadU64(inst.src2));
|
||||
@@ -305,36 +286,32 @@ bool Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
||||
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
||||
WriteMask(inst.dst2, ir.LogicalOr(carry0, carry1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_SAD_U32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_SAD_U32(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadU32(inst.src0);
|
||||
const auto rhs = ReadU32(inst.src1);
|
||||
const auto lo = IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {lhs, rhs}));
|
||||
const auto hi = IR::U32(ir.Emit(IR::ValueOpcode::UMax32, {lhs, rhs}));
|
||||
const auto result = ir.IAdd(ir.ISub(hi, lo), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_ADD3_U32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_ADD3_U32(const Decoder::Instruction& inst) {
|
||||
const auto result =
|
||||
ir.IAdd(ir.IAdd(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BITSET_B32(const Decoder::Instruction& inst, bool set) {
|
||||
void Translator::S_BITSET_B32(const Decoder::Instruction& inst, bool set) {
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(31u)));
|
||||
const auto bit = ir.ShiftLeftLogical(IR::U32(IR::Value(1u)), offset);
|
||||
const auto old = ReadU32(inst.dst);
|
||||
const auto result = set ? ir.BitwiseOr(old, bit) : ir.BitwiseAnd(old, ir.BitwiseNot(bit));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BITSET_B64(const Decoder::Instruction& inst, bool set) {
|
||||
void Translator::S_BITSET_B64(const Decoder::Instruction& inst, bool set) {
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(63u)));
|
||||
const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u)));
|
||||
const auto bit = ir.ShiftLeftLogical(IR::U32(IR::Value(1u)), word_bit);
|
||||
@@ -347,16 +324,14 @@ bool Translator::S_BITSET_B64(const Decoder::Instruction& inst, bool set) {
|
||||
{offset, IR::Value(32u)}));
|
||||
WriteU32Pair(inst.dst,
|
||||
{ir.Select(high, old[0], low_value), ir.Select(high, high_value, old[1])});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_BCNT_U32_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_BCNT_U32_B32(const Decoder::Instruction& inst) {
|
||||
const auto count = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {ReadU32(inst.src0)}));
|
||||
WriteOperand(DestinationOperand(inst), ir.IAdd(count, ReadU32(inst.src1)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low) {
|
||||
void Translator::V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low) {
|
||||
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
|
||||
const auto local = ir.BitwiseAnd(lane, IR::U32(IR::Value(31u)));
|
||||
const auto below =
|
||||
@@ -368,10 +343,9 @@ bool Translator::V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low) {
|
||||
const auto active = ir.BitwiseAnd(ReadU32(inst.src0), thread_mask);
|
||||
const auto count = IR::U32(ir.Emit(IR::ValueOpcode::BitCount32, {active}));
|
||||
WriteOperand(DestinationOperand(inst), ir.IAdd(count, ReadU32(inst.src1)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
|
||||
const auto replicate = [&](IR::U32 value) {
|
||||
auto bits = ir.BitwiseOr(value, ir.ShiftLeftLogical(value, IR::U32(IR::Value(8u))));
|
||||
bits = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x00ff00ffu)));
|
||||
@@ -387,10 +361,9 @@ bool Translator::S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst) {
|
||||
const auto low = ir.BitwiseAnd(source, IR::U32(IR::Value(0xffffu)));
|
||||
const auto high = ir.ShiftRightLogical(source, IR::U32(IR::Value(16u)));
|
||||
WriteOperand(DestinationOperand(inst), ir.ConstructU64(replicate(low), replicate(high)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
|
||||
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))));
|
||||
@@ -409,16 +382,14 @@ bool Translator::S_QUADMASK_B64(const Decoder::Instruction& inst) {
|
||||
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})})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::BFM_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::BFM_B32(const Decoder::Instruction& inst) {
|
||||
const auto count = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(31u)));
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::BitFieldInsert,
|
||||
{IR::Value(0u), IR::Value(0xffffffffu), offset, count});
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
IR::U32 Translator::RightMask32(IR::U32 count) {
|
||||
@@ -435,15 +406,14 @@ IR::U64 Translator::RightMask64(IR::U32 count) {
|
||||
return ir.ConstructU64(RightMask32(low_count), RightMask32(high_count));
|
||||
}
|
||||
|
||||
bool Translator::S_BFM_B64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_BFM_B64(const Decoder::Instruction& inst) {
|
||||
const auto count = ir.BitwiseAnd(ReadU32(inst.src0), IR::U32(IR::Value(63u)));
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {RightMask64(count), offset});
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BFE_U32(const Decoder::Instruction& inst, bool sign) {
|
||||
void Translator::S_BFE_U32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto source = ReadU32(inst.src0);
|
||||
const auto field = ReadU32(inst.src1);
|
||||
const auto offset =
|
||||
@@ -457,10 +427,9 @@ bool Translator::S_BFE_U32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto result = IR::U32(ir.Emit(opcode, {source, offset, count}));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BFE_U64(const Decoder::Instruction& inst) {
|
||||
void Translator::S_BFE_U64(const Decoder::Instruction& inst) {
|
||||
const auto source = ReadU64(inst.src0);
|
||||
const auto field = ReadU32(inst.src1);
|
||||
const auto offset =
|
||||
@@ -473,10 +442,9 @@ bool Translator::S_BFE_U64(const Decoder::Instruction& inst) {
|
||||
const auto result = ir.Emit(IR::ValueOpcode::BitwiseAnd64, {shifted, RightMask64(count)});
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
ir.SetScc(IR::U1(ir.Emit(IR::ValueOpcode::INotEqual64, {result, IR::Value(uint64_t {0})})));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_BFE_U32(const Decoder::Instruction& inst, bool sign) {
|
||||
void Translator::V_BFE_U32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto source = ReadU32(inst.src0);
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||
const auto raw_count = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
|
||||
@@ -485,29 +453,26 @@ bool Translator::V_BFE_U32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto opcode =
|
||||
sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract;
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {source, offset, count}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_BFI_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_BFI_B32(const Decoder::Instruction& inst) {
|
||||
const auto bits = ReadU32(inst.src0);
|
||||
const auto insert = ReadU32(inst.src1);
|
||||
const auto base = ReadU32(inst.src2);
|
||||
const auto result =
|
||||
ir.BitwiseOr(ir.BitwiseAnd(bits, insert), ir.BitwiseAnd(ir.BitwiseNot(bits), base));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
|
||||
void Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
|
||||
const auto value = ReadU32(inst.src0);
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||
const auto bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {value, offset, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
const auto value = ReadU32Pair(inst.src0);
|
||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(63u)));
|
||||
const auto word_bit = ir.BitwiseAnd(offset, IR::U32(IR::Value(31u)));
|
||||
@@ -516,10 +481,9 @@ bool Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
const auto bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
|
||||
const auto hi = ReadU32(inst.src0);
|
||||
const auto lo = ReadU32(inst.src1);
|
||||
const auto shift = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
|
||||
@@ -530,10 +494,9 @@ bool Translator::V_ALIGNBIT_B32(const Decoder::Instruction& inst) {
|
||||
const auto hi_part =
|
||||
ir.Select(ir.INotEqual(shift, IR::U32(IR::Value(0u))), hi_part_raw, IR::U32(IR::Value(0u)));
|
||||
WriteOperand(DestinationOperand(inst), ir.BitwiseOr(lo_part, hi_part));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_ALIGNBYTE_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_ALIGNBYTE_B32(const Decoder::Instruction& inst) {
|
||||
const auto hi = ReadU32(inst.src0);
|
||||
const auto lo = ReadU32(inst.src1);
|
||||
const auto byte_offset = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
|
||||
@@ -546,39 +509,34 @@ bool Translator::V_ALIGNBYTE_B32(const Decoder::Instruction& inst) {
|
||||
IR::U1(ir.Emit(IR::ValueOpcode::ULessThan32, {byte_offset, IR::Value(8u)}));
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
ir.Select(in_range, ExtractU64(shifted)[0], IR::U32(IR::Value(0u))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_LSHL_ADD_U32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_LSHL_ADD_U32(const Decoder::Instruction& inst) {
|
||||
const auto shift = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||
const auto result = ir.IAdd(ir.ShiftLeftLogical(ReadU32(inst.src0), shift), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_ADD_LSHL_U32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_ADD_LSHL_U32(const Decoder::Instruction& inst) {
|
||||
const auto shift = ir.BitwiseAnd(ReadU32(inst.src2), IR::U32(IR::Value(31u)));
|
||||
const auto result = ir.ShiftLeftLogical(ir.IAdd(ReadU32(inst.src0), ReadU32(inst.src1)), shift);
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_XAD_U32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_XAD_U32(const Decoder::Instruction& inst) {
|
||||
const auto result =
|
||||
ir.IAdd(ir.BitwiseXor(ReadU32(inst.src0), ReadU32(inst.src1)), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_LSHL_OR_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_LSHL_OR_B32(const Decoder::Instruction& inst) {
|
||||
const auto shift = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||
const auto result =
|
||||
ir.BitwiseOr(ir.ShiftLeftLogical(ReadU32(inst.src0), shift), ReadU32(inst.src2));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_CNDMASK_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::V_CNDMASK_B32(const Decoder::Instruction& inst) {
|
||||
Decoder::Operand mask_operand;
|
||||
mask_operand.kind = Decoder::OperandKind::VccLo;
|
||||
if (inst.src_count >= 3u) {
|
||||
@@ -594,17 +552,15 @@ bool Translator::V_CNDMASK_B32(const Decoder::Instruction& inst) {
|
||||
result = ir.Select(condition, ReadU32(inst.src1), ReadU32(inst.src0));
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::PackB16(const Decoder::Instruction& inst, bool high0, bool high1) {
|
||||
void Translator::PackB16(const Decoder::Instruction& inst, bool high0, bool high1) {
|
||||
const auto lo = high0 ? ir.ShiftRightLogical(ReadU32(inst.src0), IR::U32(IR::Value(16u)))
|
||||
: ReadU32(inst.src0);
|
||||
const auto hi = high1 ? ir.ShiftRightLogical(ReadU32(inst.src1), IR::U32(IR::Value(16u)))
|
||||
: ReadU32(inst.src1);
|
||||
const auto result = PackU16Lanes(lo, hi);
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
@@ -222,7 +223,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
|
||||
const bool store_or_atomic = decoded.opcode == Decoder::Opcode::IMAGE_STORE ||
|
||||
decoded.opcode == Decoder::Opcode::IMAGE_STORE_MIP ||
|
||||
(decoded.opcode >= Decoder::Opcode::IMAGE_ATOMIC_SWAP &&
|
||||
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_XOR);
|
||||
decoded.opcode <= Decoder::Opcode::IMAGE_ATOMIC_FMAX);
|
||||
if (store_or_atomic) {
|
||||
return index == 0u ? decoded.dst : decoded.src0;
|
||||
}
|
||||
@@ -404,7 +405,7 @@ IR::Value Translator::NarrowSubdword(IR::U32 value, uint32_t bits) {
|
||||
: ir.Emit(IR::ValueOpcode::ConvertU16U32, {value});
|
||||
}
|
||||
|
||||
bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
void Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource =
|
||||
raw ? GetScalarAddressResource(RawScalarLoadBase(inst.src0)) : GetBufferResource(memory);
|
||||
@@ -427,10 +428,9 @@ bool Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
WriteOperand(ScalarDestinationOperand(inst.dst, component), loaded[component]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
IR::ValueOpcode opcode;
|
||||
const auto bits = memory.data_bits;
|
||||
@@ -444,10 +444,15 @@ bool Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
case 2u: opcode = IR::ValueOpcode::LoadBufferU32x2; break;
|
||||
case 3u: opcode = IR::ValueOpcode::LoadBufferU32x3; break;
|
||||
case 4u: opcode = IR::ValueOpcode::LoadBufferU32x4; break;
|
||||
default: return false;
|
||||
default:
|
||||
EXIT("opcode %s at pc 0x%08x has unsupported buffer load dword count %u",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc,
|
||||
memory.data_dwords);
|
||||
}
|
||||
break;
|
||||
default: return false;
|
||||
default:
|
||||
EXIT("opcode %s at pc 0x%08x has unsupported buffer load width %u",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc, bits);
|
||||
}
|
||||
const auto resource = GetBufferResource(memory);
|
||||
const auto address = ReadBufferAddress(inst, 0);
|
||||
@@ -464,10 +469,9 @@ bool Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
ir.CompositeExtract(loaded, component));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
|
||||
void Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetBufferResource(memory);
|
||||
const auto address = ReadBufferAddress(inst, 1);
|
||||
@@ -508,17 +512,21 @@ bool Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
|
||||
ReadU32(OffsetOperand(data_src, 2u)),
|
||||
ReadU32(OffsetOperand(data_src, 3u))});
|
||||
break;
|
||||
default: return false;
|
||||
default:
|
||||
EXIT("opcode %s at pc 0x%08x has unsupported buffer store dword count %u",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc,
|
||||
memory.data_dwords);
|
||||
}
|
||||
break;
|
||||
default: return false;
|
||||
default:
|
||||
EXIT("opcode %s at pc 0x%08x has unsupported buffer store width %u",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc, memory.data_bits);
|
||||
}
|
||||
ir.Emit(opcode, {resource, address.index, address.offset, address.soffset, value, ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetBufferResource(memory);
|
||||
const auto address = ReadBufferAddress(inst, 1);
|
||||
@@ -543,10 +551,9 @@ bool Translator::BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
if (inst.glc) {
|
||||
WriteOperand(inst.dst, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 1));
|
||||
@@ -556,10 +563,9 @@ bool Translator::IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode
|
||||
if (inst.glc) {
|
||||
WriteOperand(inst.dst, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool returns_value) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 1));
|
||||
@@ -568,10 +574,9 @@ bool Translator::DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opc
|
||||
if (returns_value) {
|
||||
WriteOperand(inst.dst, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
|
||||
void Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
IR::ValueOpcode opcode;
|
||||
const auto bits = memory.data_bits;
|
||||
@@ -580,7 +585,9 @@ bool Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
|
||||
case 8u: opcode = IR::ValueOpcode::LoadAddressU8; break;
|
||||
case 16u: opcode = IR::ValueOpcode::LoadAddressU16; break;
|
||||
case 32u: opcode = IR::ValueOpcode::LoadAddressU32; break;
|
||||
default: return false;
|
||||
default:
|
||||
EXIT("opcode %s at pc 0x%08x has unsupported flat load width %u",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc, bits);
|
||||
}
|
||||
const auto address = ReadAddressOperands(inst, 0);
|
||||
const auto active = ir.GetExec();
|
||||
@@ -595,10 +602,9 @@ bool Translator::FLAT_LOAD(const Decoder::Instruction& inst) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
bits == 32u ? loaded : WidenSubdword(loaded, bits, sign));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::FLAT_STORE(const Decoder::Instruction& inst) {
|
||||
void Translator::FLAT_STORE(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto data_op = MemorySourceAt(inst, 0);
|
||||
const auto address = ReadAddressOperands(inst, 1);
|
||||
@@ -607,7 +613,9 @@ bool Translator::FLAT_STORE(const Decoder::Instruction& inst) {
|
||||
case 8u: opcode = IR::ValueOpcode::StoreAddressU8; break;
|
||||
case 16u: opcode = IR::ValueOpcode::StoreAddressU16; break;
|
||||
case 32u: opcode = IR::ValueOpcode::StoreAddressU32; break;
|
||||
default: return false;
|
||||
default:
|
||||
EXIT("opcode %s at pc 0x%08x has unsupported flat store width %u",
|
||||
Decoder::InstructionToString(inst).c_str(), inst.pc, memory.data_bits);
|
||||
}
|
||||
const auto count = memory.data_bits == 32u ? memory.data_dwords : 1u;
|
||||
for (uint32_t index = 0; index < count; index++) {
|
||||
@@ -622,20 +630,18 @@ bool Translator::FLAT_STORE(const Decoder::Instruction& inst) {
|
||||
ir.Emit(opcode, {address.resource, address.low, address.high, value, ir.GetExec()},
|
||||
AddMemoryInfo(component, inst.pc));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_GET_RESINFO(const Decoder::Instruction& inst) {
|
||||
void Translator::IMAGE_GET_RESINFO(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageQueryDimensions, {resource, address},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
WriteImageComponents(inst.dst, result, memory, 4u);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_GET_LOD(const Decoder::Instruction& inst) {
|
||||
void Translator::IMAGE_GET_LOD(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto sampler = GetSamplerResource(memory);
|
||||
@@ -643,30 +649,27 @@ bool Translator::IMAGE_GET_LOD(const Decoder::Instruction& inst) {
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageQueryLod, {resource, sampler, address},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
WriteImageComponents(inst.dst, result, memory, 2u);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_LOAD(const Decoder::Instruction& inst) {
|
||||
void Translator::IMAGE_LOAD(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 0));
|
||||
const auto result = ir.Emit(IR::ValueOpcode::ImageRead, {resource, address, ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
WriteImageComponents(inst.dst, result, memory, 4u);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_STORE(const Decoder::Instruction& inst) {
|
||||
void Translator::IMAGE_STORE(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto address = MakeImageAddress(inst, MemorySourceAt(inst, 1));
|
||||
const auto data = ConstructU32x4(MemorySourceAt(inst, 0), memory.data_dwords);
|
||||
ir.Emit(IR::ValueOpcode::ImageWrite, {resource, address, data, ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
|
||||
void Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto sampler = GetSamplerResource(memory);
|
||||
@@ -683,10 +686,9 @@ bool Translator::IMAGE_SAMPLE(const Decoder::Instruction& inst) {
|
||||
} else {
|
||||
WriteImageComponents(inst.dst, result, memory, 4u);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
|
||||
void Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto resource = GetImageResource(memory);
|
||||
const auto sampler = GetSamplerResource(memory);
|
||||
@@ -697,7 +699,6 @@ bool Translator::IMAGE_GATHER(const Decoder::Instruction& inst) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index),
|
||||
ir.Emit(IR::ValueOpcode::CompositeExtractU32x4, {result, IR::Value(index)}));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IR::Value Translator::LoadSharedU32(uint32_t width, IR::U32 address, const IR::MemoryInfo& memory,
|
||||
@@ -753,7 +754,7 @@ void Translator::WriteSharedU32(uint32_t width, IR::U32 address,
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::DS_READ(const Decoder::Instruction& inst) {
|
||||
void Translator::DS_READ(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 0));
|
||||
if (memory.data_bits == 32u) {
|
||||
@@ -762,16 +763,15 @@ bool Translator::DS_READ(const Decoder::Instruction& inst) {
|
||||
for (uint32_t index = 0; index < width; index++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, index), ExtractSharedU32(loaded, width, index));
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
const auto opcode =
|
||||
memory.data_bits == 8u ? IR::ValueOpcode::LoadSharedU8 : IR::ValueOpcode::LoadSharedU16;
|
||||
const auto loaded = ir.Emit(opcode, {address, ir.GetExec()}, AddMemoryInfo(memory, inst.pc));
|
||||
WriteOperand(inst.dst, WidenSubdword(loaded, memory.data_bits, memory.data_signed));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_READ2(const Decoder::Instruction& inst) {
|
||||
void Translator::DS_READ2(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto width = memory.data_dwords / 2u;
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 0));
|
||||
@@ -790,10 +790,9 @@ bool Translator::DS_READ2(const Decoder::Instruction& inst) {
|
||||
WriteOperand(OffsetOperand(inst.dst, width + index),
|
||||
ExtractSharedU32(second_value, width, index));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_WRITE(const Decoder::Instruction& inst) {
|
||||
void Translator::DS_WRITE(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto width = memory.data_dwords;
|
||||
const auto data_operand = MemorySourceAt(inst, 0);
|
||||
@@ -804,16 +803,15 @@ bool Translator::DS_WRITE(const Decoder::Instruction& inst) {
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 1));
|
||||
if (memory.data_bits == 32u) {
|
||||
WriteSharedU32(width, address, values, memory, inst.pc);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
const auto opcode =
|
||||
memory.data_bits == 8u ? IR::ValueOpcode::WriteSharedU8 : IR::ValueOpcode::WriteSharedU16;
|
||||
ir.Emit(opcode, {address, NarrowSubdword(IR::U32(values[0]), memory.data_bits), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_WRITE2(const Decoder::Instruction& inst) {
|
||||
void Translator::DS_WRITE2(const Decoder::Instruction& inst) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto width = memory.data_dwords / 2u;
|
||||
const auto address = ReadU32(MemorySourceAt(inst, 1));
|
||||
@@ -834,28 +832,25 @@ bool Translator::DS_WRITE2(const Decoder::Instruction& inst) {
|
||||
second.offset = memory.secondary_offset;
|
||||
WriteSharedU32(width, address, second_values, second, inst.pc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
ir.Emit(opcode,
|
||||
{ReadU32(MemorySourceAt(inst, 1)), ReadU32(MemorySourceAt(inst, 0)),
|
||||
ReadU32(MemorySourceAt(inst, 2)), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
void Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec(), ir.GetExecLo(),
|
||||
ir.GetExecHi()},
|
||||
AddMemoryInfo(memory, inst.pc)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
|
||||
void Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
const auto base =
|
||||
ir.BitwiseAnd(ReadU32(MemorySourceAt(inst, write ? 1u : 0u)), IR::U32(IR::Value(0xffffu)));
|
||||
@@ -869,24 +864,21 @@ bool Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::LoadSharedU32, {address, ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_SWIZZLE_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::DS_SWIZZLE_B32(const Decoder::Instruction& inst) {
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::SwizzleU32,
|
||||
{ReadU32(MemorySourceAt(inst, 0)),
|
||||
ReadU32(MemorySourceAt(inst, 1)), ir.GetExec()}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) {
|
||||
void Translator::DS_BPERMUTE_B32(const Decoder::Instruction& inst) {
|
||||
const auto address = ir.IAdd(ReadU32(inst.src0), IR::U32(IR::Value(inst.offset)));
|
||||
WriteOperand(inst.dst, ir.Emit(IR::ValueOpcode::BpermuteU32,
|
||||
{ReadU32(inst.src1), address, ir.GetExec()}));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::S_LOAD_DWORD:
|
||||
case Decoder::Opcode::S_LOAD_DWORDX2:
|
||||
@@ -907,7 +899,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
for (uint32_t component = 0; component < 2; component++) {
|
||||
WriteOperand(ScalarDestinationOperand(inst.dst, component), IR::Value(UINT32_MAX));
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
|
||||
@@ -1034,6 +1026,10 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicOr32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_XOR:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicXor32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_FMIN:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMin32);
|
||||
case Decoder::Opcode::IMAGE_ATOMIC_FMAX:
|
||||
return IMAGE_ATOMIC(inst, IR::ValueOpcode::ImageAtomicFMax32);
|
||||
|
||||
case Decoder::Opcode::FLAT_LOAD_UBYTE:
|
||||
case Decoder::Opcode::FLAT_LOAD_SBYTE:
|
||||
@@ -1105,7 +1101,7 @@ bool Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::DS_WRITE_B64:
|
||||
case Decoder::Opcode::DS_WRITE_B96:
|
||||
case Decoder::Opcode::DS_WRITE_B128: return DS_WRITE(inst);
|
||||
default: return false;
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,106 +2,106 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
using O = Decoder::Opcode;
|
||||
switch (inst.opcode) {
|
||||
case O::S_MOV_B32:
|
||||
case O::S_MOVK_I32: MOV_B32(inst, false); return true;
|
||||
case O::S_MOV_B64: S_MOV_B64(inst); return true;
|
||||
case O::S_WQM_B32: S_WQM(inst, false); return true;
|
||||
case O::S_WQM_B64: S_WQM(inst, true); return true;
|
||||
case O::S_GETPC_B64: S_GETPC_B64(inst); return true;
|
||||
case O::S_SETPC_B64: return true;
|
||||
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return true;
|
||||
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return true;
|
||||
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return true;
|
||||
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return true;
|
||||
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return true;
|
||||
case O::S_SETREG_B32: EmitControlNop(); return true;
|
||||
case O::S_WAITCNT: EmitWaitcnt(); return true;
|
||||
case O::S_MOVK_I32: MOV_B32(inst, false); return;
|
||||
case O::S_MOV_B64: S_MOV_B64(inst); return;
|
||||
case O::S_WQM_B32: S_WQM(inst, false); return;
|
||||
case O::S_WQM_B64: S_WQM(inst, true); return;
|
||||
case O::S_GETPC_B64: S_GETPC_B64(inst); return;
|
||||
case O::S_SETPC_B64: return;
|
||||
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return;
|
||||
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return;
|
||||
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return;
|
||||
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return;
|
||||
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return;
|
||||
case O::S_SETREG_B32: EmitControlNop(); return;
|
||||
case O::S_WAITCNT: EmitWaitcnt(); return;
|
||||
|
||||
case O::S_AND_SAVEEXEC_B32:
|
||||
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_ANDN1_SAVEEXEC_B32:
|
||||
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_ORN2_SAVEEXEC_B32:
|
||||
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_AND_SAVEEXEC_B64:
|
||||
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::S_ANDN1_SAVEEXEC_B64:
|
||||
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::S_ORN2_SAVEEXEC_B64:
|
||||
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, true);
|
||||
return true;
|
||||
case O::S_ADD_U32: ADD_U32(inst, false, false); return true;
|
||||
case O::S_ADDC_U32: ADD_U32(inst, false, true); return true;
|
||||
case O::S_SUB_U32: SUB_U32(inst, false, false); return true;
|
||||
case O::S_SUBB_U32: SUBB_U32(inst, false, false); return true;
|
||||
case O::S_ABSDIFF_I32: S_ABSDIFF_I32(inst); return true;
|
||||
case O::S_ADD_I32: S_ADD_SUB_I32(inst, false); return true;
|
||||
case O::S_SUB_I32: S_ADD_SUB_I32(inst, true); return true;
|
||||
case O::S_LSHL1_ADD_U32: S_LSHL_ADD_U32(inst, 1u); return true;
|
||||
case O::S_LSHL2_ADD_U32: S_LSHL_ADD_U32(inst, 2u); return true;
|
||||
case O::S_LSHL3_ADD_U32: S_LSHL_ADD_U32(inst, 3u); return true;
|
||||
case O::S_LSHL4_ADD_U32: S_LSHL_ADD_U32(inst, 4u); return true;
|
||||
return;
|
||||
case O::S_ADD_U32: ADD_U32(inst, false, false); return;
|
||||
case O::S_ADDC_U32: ADD_U32(inst, false, true); return;
|
||||
case O::S_SUB_U32: SUB_U32(inst, false, false); return;
|
||||
case O::S_SUBB_U32: SUBB_U32(inst, false, false); return;
|
||||
case O::S_ABSDIFF_I32: S_ABSDIFF_I32(inst); return;
|
||||
case O::S_ADD_I32: S_ADD_SUB_I32(inst, false); return;
|
||||
case O::S_SUB_I32: S_ADD_SUB_I32(inst, true); return;
|
||||
case O::S_LSHL1_ADD_U32: S_LSHL_ADD_U32(inst, 1u); return;
|
||||
case O::S_LSHL2_ADD_U32: S_LSHL_ADD_U32(inst, 2u); return;
|
||||
case O::S_LSHL3_ADD_U32: S_LSHL_ADD_U32(inst, 3u); return;
|
||||
case O::S_LSHL4_ADD_U32: S_LSHL_ADD_U32(inst, 4u); return;
|
||||
case O::S_MIN_I32:
|
||||
ScalarMinMax32(inst, IR::ValueOpcode::SMin32, IR::ValueOpcode::SLessThan32);
|
||||
return true;
|
||||
return;
|
||||
case O::S_MAX_I32:
|
||||
ScalarMinMax32(inst, IR::ValueOpcode::SMax32, IR::ValueOpcode::SGreaterThan32);
|
||||
return true;
|
||||
return;
|
||||
case O::S_MIN_U32:
|
||||
ScalarMinMax32(inst, IR::ValueOpcode::UMin32, IR::ValueOpcode::ULessThan32);
|
||||
return true;
|
||||
return;
|
||||
case O::S_MAX_U32:
|
||||
ScalarMinMax32(inst, IR::ValueOpcode::UMax32, IR::ValueOpcode::UGreaterThan32);
|
||||
return true;
|
||||
return;
|
||||
|
||||
case O::S_CMP_EQ_U32:
|
||||
case O::S_CMP_EQ_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_LG_U32:
|
||||
case O::S_CMP_LG_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true,
|
||||
false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true,
|
||||
false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::S_CMP_LG_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, true, false);
|
||||
return true;
|
||||
return;
|
||||
|
||||
case O::S_AND_B64:
|
||||
return S_U64_MASK(inst, IR::ValueOpcode::LogicalAnd, IR::ValueOpcode::BitwiseAnd32,
|
||||
@@ -217,12 +217,12 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_NOP:
|
||||
case O::S_SLEEP:
|
||||
case O::S_SETPRIO:
|
||||
case O::S_TRAP: EmitControlNop(); return true;
|
||||
case O::S_WAITCNT_DEPCTR: EmitWaitcnt(); return true;
|
||||
case O::S_BARRIER: S_BARRIER(); return true;
|
||||
case O::S_SENDMSG: S_SENDMSG(inst); return true;
|
||||
case O::S_TTRACEDATA: S_TTRACEDATA(); return true;
|
||||
case O::S_INST_PREFETCH: S_INST_PREFETCH(); return true;
|
||||
case O::S_TRAP: EmitControlNop(); return;
|
||||
case O::S_WAITCNT_DEPCTR: EmitWaitcnt(); return;
|
||||
case O::S_BARRIER: S_BARRIER(); return;
|
||||
case O::S_SENDMSG: S_SENDMSG(inst); return;
|
||||
case O::S_TTRACEDATA: S_TTRACEDATA(); return;
|
||||
case O::S_INST_PREFETCH: S_INST_PREFETCH(); return;
|
||||
case O::S_BRANCH:
|
||||
case O::S_CBRANCH_SCC0:
|
||||
case O::S_CBRANCH_SCC1:
|
||||
@@ -231,8 +231,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
case O::S_CBRANCH_EXECZ:
|
||||
case O::S_CBRANCH_EXECNZ:
|
||||
case O::S_CBRANCH_CDBGSYS:
|
||||
case O::S_ENDPGM: return true;
|
||||
default: return false;
|
||||
case O::S_ENDPGM: return;
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
@@ -225,9 +226,28 @@ IR::Value Translator::ReadOperand(const Decoder::Operand& operand, IR::Type type
|
||||
}
|
||||
return ir.INotEqual(ReadRawU32(operand), IR::U32(IR::Value(0u)));
|
||||
}
|
||||
if (type == IR::Type::U64) {
|
||||
const auto pair = ReadU32Pair(operand);
|
||||
return ir.ConstructU64(pair[0], pair[1]);
|
||||
if (type == IR::Type::U64 || type == IR::Type::F64) {
|
||||
auto pair = ReadU32Pair(operand);
|
||||
if (type == IR::Type::F64) {
|
||||
if (operand.kind == Decoder::OperandKind::LiteralConstant) {
|
||||
pair = {IR::U32(IR::Value(0u)), IR::U32(IR::Value(operand.value))};
|
||||
} else if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
const auto bits = operand.value == 0x3e22f983u
|
||||
? 0x3fc45f306dc9c882ull
|
||||
: std::bit_cast<uint64_t>(static_cast<double>(
|
||||
std::bit_cast<float>(operand.value)));
|
||||
pair = {IR::U32(IR::Value(static_cast<uint32_t>(bits))),
|
||||
IR::U32(IR::Value(static_cast<uint32_t>(bits >> 32u)))};
|
||||
}
|
||||
if (operand.absolute) {
|
||||
pair[1] = ir.BitwiseAnd(pair[1], IR::U32(IR::Value(0x7fffffffu)));
|
||||
}
|
||||
if (operand.negate) {
|
||||
pair[1] = ir.BitwiseXor(pair[1], IR::U32(IR::Value(0x80000000u)));
|
||||
}
|
||||
}
|
||||
const auto bits = ir.ConstructU64(pair[0], pair[1]);
|
||||
return type == IR::Type::F64 ? ir.Emit(IR::ValueOpcode::BitCastF64U64, {bits}) : IR::Value(bits);
|
||||
}
|
||||
auto bits = ApplyBitSourceModifiers(operand, ReadRawU32(operand));
|
||||
if (TypesOverlap(type, IR::Type::F32) && !TypesOverlap(type, IR::Type::U32)) {
|
||||
@@ -403,6 +423,10 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
||||
Write16Bits(operand, IR::U32(ir.Emit(IR::ValueOpcode::ConvertU32U16, {bits})));
|
||||
return;
|
||||
}
|
||||
if (type == IR::Type::F64) {
|
||||
value = ir.Emit(IR::ValueOpcode::BitCastU64F64, {value});
|
||||
type = IR::Type::U64;
|
||||
}
|
||||
if (type == IR::Type::U64) {
|
||||
WriteU32Pair(operand, {ir.CompositeExtract(value, 0), ir.CompositeExtract(value, 1)});
|
||||
return;
|
||||
@@ -842,6 +866,14 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
|
||||
include_vector(inst.src1);
|
||||
include_vector(inst.src2);
|
||||
include_vector(inst.src3);
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::V_CVT_F64_I32: include_vector(inst.dst, 2u); break;
|
||||
case Decoder::Opcode::V_FMA_F64: include_vector(inst.src2, 2u); [[fallthrough]];
|
||||
case Decoder::Opcode::V_MUL_F64: include_vector(inst.src1, 2u); [[fallthrough]];
|
||||
case Decoder::Opcode::V_RCP_F64: include_vector(inst.dst, 2u); [[fallthrough]];
|
||||
case Decoder::Opcode::V_CVT_F32_F64: include_vector(inst.src0, 2u); break;
|
||||
default: break;
|
||||
}
|
||||
if (inst.family == Decoder::Family::DS) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::DS_WRITE_B64:
|
||||
|
||||
@@ -80,34 +80,34 @@ private:
|
||||
BufferAddress ReadBufferAddress(const Decoder::Instruction& inst, uint32_t source_offset);
|
||||
IR::U32 WidenSubdword(IR::Value value, uint32_t bits, bool sign);
|
||||
IR::Value NarrowSubdword(IR::U32 value, uint32_t bits);
|
||||
bool S_LOAD(const Decoder::Instruction& inst, bool raw);
|
||||
bool BUFFER_LOAD(const Decoder::Instruction& inst);
|
||||
bool BUFFER_STORE(const Decoder::Instruction& inst);
|
||||
bool BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool returns_value);
|
||||
bool FLAT_LOAD(const Decoder::Instruction& inst);
|
||||
bool FLAT_STORE(const Decoder::Instruction& inst);
|
||||
bool IMAGE_GET_RESINFO(const Decoder::Instruction& inst);
|
||||
bool IMAGE_GET_LOD(const Decoder::Instruction& inst);
|
||||
bool IMAGE_LOAD(const Decoder::Instruction& inst);
|
||||
bool IMAGE_STORE(const Decoder::Instruction& inst);
|
||||
bool IMAGE_SAMPLE(const Decoder::Instruction& inst);
|
||||
bool IMAGE_GATHER(const Decoder::Instruction& inst);
|
||||
void S_LOAD(const Decoder::Instruction& inst, bool raw);
|
||||
void BUFFER_LOAD(const Decoder::Instruction& inst);
|
||||
void BUFFER_STORE(const Decoder::Instruction& inst);
|
||||
void BUFFER_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void IMAGE_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void DS_ATOMIC(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool returns_value);
|
||||
void FLAT_LOAD(const Decoder::Instruction& inst);
|
||||
void FLAT_STORE(const Decoder::Instruction& inst);
|
||||
void IMAGE_GET_RESINFO(const Decoder::Instruction& inst);
|
||||
void IMAGE_GET_LOD(const Decoder::Instruction& inst);
|
||||
void IMAGE_LOAD(const Decoder::Instruction& inst);
|
||||
void IMAGE_STORE(const Decoder::Instruction& inst);
|
||||
void IMAGE_SAMPLE(const Decoder::Instruction& inst);
|
||||
void IMAGE_GATHER(const Decoder::Instruction& inst);
|
||||
IR::Value LoadSharedU32(uint32_t width, IR::U32 address, const IR::MemoryInfo& memory,
|
||||
uint32_t pc);
|
||||
IR::Value ExtractSharedU32(IR::Value value, uint32_t width, uint32_t index);
|
||||
void WriteSharedU32(uint32_t width, IR::U32 address, const std::array<IR::Value, 4>& values,
|
||||
const IR::MemoryInfo& memory, uint32_t pc);
|
||||
bool DS_READ(const Decoder::Instruction& inst);
|
||||
bool DS_READ2(const Decoder::Instruction& inst);
|
||||
bool DS_WRITE(const Decoder::Instruction& inst);
|
||||
bool DS_WRITE2(const Decoder::Instruction& inst);
|
||||
bool DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool DS_ADDTID(const Decoder::Instruction& inst, bool write);
|
||||
bool DS_SWIZZLE_B32(const Decoder::Instruction& inst);
|
||||
bool DS_BPERMUTE_B32(const Decoder::Instruction& inst);
|
||||
void DS_READ(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);
|
||||
void DS_MINMAX_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void DS_ADDTID(const Decoder::Instruction& inst, bool write);
|
||||
void DS_SWIZZLE_B32(const Decoder::Instruction& inst);
|
||||
void DS_BPERMUTE_B32(const Decoder::Instruction& inst);
|
||||
|
||||
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||
IR::U32 ConvertF32ToU32Saturated(IR::F32 value, float upper_bound, float safe_upper,
|
||||
@@ -124,7 +124,7 @@ private:
|
||||
bool signed_value, bool cmpx);
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
||||
bool cmpx);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx);
|
||||
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
||||
void V_CVT_F32_U32(const Decoder::Instruction& inst);
|
||||
@@ -143,77 +143,77 @@ private:
|
||||
void V_CVT_PKNORM_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void V_CVT_PK_U8_F32(const Decoder::Instruction& inst);
|
||||
void V_PACK_B32_F16(const Decoder::Instruction& inst);
|
||||
bool PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
void PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool quiet_snan);
|
||||
bool Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool invalid_negative);
|
||||
bool Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||
bool Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
void Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||
void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool mix);
|
||||
bool FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||
bool FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||
void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool mix);
|
||||
bool V_FREXP_MANT_F32(const Decoder::Instruction& inst);
|
||||
bool V_DOT2C_F32_F16(const Decoder::Instruction& inst);
|
||||
bool V_CUBEID_F32(const Decoder::Instruction& inst);
|
||||
bool V_CUBESC_F32(const Decoder::Instruction& inst);
|
||||
bool V_CUBETC_F32(const Decoder::Instruction& inst);
|
||||
bool V_CUBEMA_F32(const Decoder::Instruction& inst);
|
||||
bool FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
|
||||
bool Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
bool Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
bool V_MAD_I16(const Decoder::Instruction& inst);
|
||||
bool V_MED3_I16(const Decoder::Instruction& inst);
|
||||
bool PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void V_FREXP_MANT_F32(const Decoder::Instruction& inst);
|
||||
void V_DOT2C_F32_F16(const Decoder::Instruction& inst);
|
||||
void V_CUBEID_F32(const Decoder::Instruction& inst);
|
||||
void V_CUBESC_F32(const Decoder::Instruction& inst);
|
||||
void V_CUBETC_F32(const Decoder::Instruction& inst);
|
||||
void V_CUBEMA_F32(const Decoder::Instruction& inst);
|
||||
void FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
|
||||
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void V_MAD_I16(const Decoder::Instruction& inst);
|
||||
void V_MED3_I16(const Decoder::Instruction& inst);
|
||||
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic);
|
||||
bool PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
bool PackedInteger16Mad(const Decoder::Instruction& inst, bool sign);
|
||||
bool PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
bool S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
|
||||
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void PackedInteger16Mad(const Decoder::Instruction& inst, bool sign);
|
||||
void PackedInteger16MinMax(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
void S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode logical_opcode,
|
||||
IR::ValueOpcode bit_opcode, bool negate_rhs, bool negate_result, bool unary);
|
||||
IR::U1 U64MaskBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool negate_rhs,
|
||||
bool negate_result);
|
||||
bool SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
||||
void SimpleInteger(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
||||
bool reverse, bool mask_shift_count, bool update_scc);
|
||||
bool S_ASHR_I64(const Decoder::Instruction& inst);
|
||||
bool ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
void S_ASHR_I64(const Decoder::Instruction& inst);
|
||||
void ComposedIntegerBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool negate_rhs, bool negate_result, bool update_scc);
|
||||
bool V_AND_OR_B32(const Decoder::Instruction& inst);
|
||||
bool V_OR3_B32(const Decoder::Instruction& inst);
|
||||
bool V_XOR3_B32(const Decoder::Instruction& inst);
|
||||
bool S_FF1_I32_B64(const Decoder::Instruction& inst);
|
||||
bool V_FFBH_32(const Decoder::Instruction& inst, bool sign);
|
||||
bool S_FLBIT_I32_B64(const Decoder::Instruction& inst);
|
||||
bool Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
|
||||
bool V_MAD_U64_U32(const Decoder::Instruction& inst);
|
||||
bool V_SAD_U32(const Decoder::Instruction& inst);
|
||||
bool V_ADD3_U32(const Decoder::Instruction& inst);
|
||||
bool S_BITSET_B32(const Decoder::Instruction& inst, bool set);
|
||||
bool S_BITSET_B64(const Decoder::Instruction& inst, bool set);
|
||||
bool V_BCNT_U32_B32(const Decoder::Instruction& inst);
|
||||
bool V_MBCNT_U32_B32(const Decoder::Instruction& inst, bool low);
|
||||
bool S_BITREPLICATE_B64_B32(const Decoder::Instruction& inst);
|
||||
bool S_QUADMASK_B64(const Decoder::Instruction& inst);
|
||||
bool BFM_B32(const Decoder::Instruction& inst);
|
||||
void V_AND_OR_B32(const Decoder::Instruction& inst);
|
||||
void V_OR3_B32(const Decoder::Instruction& inst);
|
||||
void V_XOR3_B32(const Decoder::Instruction& inst);
|
||||
void S_FF1_I32_B64(const Decoder::Instruction& inst);
|
||||
void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
|
||||
void S_FLBIT_I32_B64(const Decoder::Instruction& inst);
|
||||
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
|
||||
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);
|
||||
void S_BITSET_B32(const Decoder::Instruction& inst, bool set);
|
||||
void S_BITSET_B64(const Decoder::Instruction& inst, bool set);
|
||||
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 BFM_B32(const Decoder::Instruction& inst);
|
||||
IR::U32 RightMask32(IR::U32 count);
|
||||
IR::U64 RightMask64(IR::U32 count);
|
||||
bool S_BFM_B64(const Decoder::Instruction& inst);
|
||||
bool S_BFE_U32(const Decoder::Instruction& inst, bool sign);
|
||||
bool S_BFE_U64(const Decoder::Instruction& inst);
|
||||
bool V_BFE_U32(const Decoder::Instruction& inst, bool sign);
|
||||
bool V_BFI_B32(const Decoder::Instruction& inst);
|
||||
bool S_BITCMP_B32(const Decoder::Instruction& inst, bool expected);
|
||||
bool S_BITCMP_B64(const Decoder::Instruction& inst, bool expected);
|
||||
bool V_ALIGNBIT_B32(const Decoder::Instruction& inst);
|
||||
bool V_ALIGNBYTE_B32(const Decoder::Instruction& inst);
|
||||
bool V_LSHL_ADD_U32(const Decoder::Instruction& inst);
|
||||
bool V_ADD_LSHL_U32(const Decoder::Instruction& inst);
|
||||
bool V_XAD_U32(const Decoder::Instruction& inst);
|
||||
bool V_LSHL_OR_B32(const Decoder::Instruction& inst);
|
||||
bool V_CNDMASK_B32(const Decoder::Instruction& inst);
|
||||
bool PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
|
||||
void S_BFM_B64(const Decoder::Instruction& inst);
|
||||
void S_BFE_U32(const Decoder::Instruction& inst, bool sign);
|
||||
void S_BFE_U64(const Decoder::Instruction& inst);
|
||||
void V_BFE_U32(const Decoder::Instruction& inst, bool sign);
|
||||
void V_BFI_B32(const Decoder::Instruction& inst);
|
||||
void S_BITCMP_B32(const Decoder::Instruction& inst, bool expected);
|
||||
void S_BITCMP_B64(const Decoder::Instruction& inst, bool expected);
|
||||
void V_ALIGNBIT_B32(const Decoder::Instruction& inst);
|
||||
void V_ALIGNBYTE_B32(const Decoder::Instruction& inst);
|
||||
void V_LSHL_ADD_U32(const Decoder::Instruction& inst);
|
||||
void V_ADD_LSHL_U32(const Decoder::Instruction& inst);
|
||||
void V_XAD_U32(const Decoder::Instruction& inst);
|
||||
void V_LSHL_OR_B32(const Decoder::Instruction& inst);
|
||||
void V_CNDMASK_B32(const Decoder::Instruction& inst);
|
||||
void PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
|
||||
|
||||
void S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin);
|
||||
void S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation, bool negate_exec,
|
||||
@@ -249,10 +249,11 @@ private:
|
||||
void V_INTERP_MOV_F32(const Decoder::Instruction& inst);
|
||||
void EXP(const Decoder::Instruction& inst);
|
||||
|
||||
bool EmitScalar(const Decoder::Instruction& inst);
|
||||
bool EmitVector(const Decoder::Instruction& inst);
|
||||
bool EmitInterpolation(const Decoder::Instruction& inst);
|
||||
bool EmitMemory(const Decoder::Instruction& inst);
|
||||
void EmitScalar(const Decoder::Instruction& inst);
|
||||
void EmitVector(const Decoder::Instruction& inst);
|
||||
void EmitInterpolation(const Decoder::Instruction& inst);
|
||||
void EmitMemory(const Decoder::Instruction& inst);
|
||||
static void FailMissingTranslation(const Decoder::Instruction& inst);
|
||||
|
||||
IR::Program& program;
|
||||
IR::IREmitter ir;
|
||||
|
||||
@@ -2,318 +2,324 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
using O = Decoder::Opcode;
|
||||
switch (inst.opcode) {
|
||||
case O::V_NOP: return true;
|
||||
case O::V_ADD_I32: ADD_U32(inst, true, false); return true;
|
||||
case O::V_ADDC_U32: ADD_U32(inst, true, true); return true;
|
||||
case O::V_SUB_I32: SUB_U32(inst, true, false); return true;
|
||||
case O::V_SUBREV_I32: SUB_U32(inst, true, true); return true;
|
||||
case O::V_SUB_CO_CI_U32: SUBB_U32(inst, true, false); return true;
|
||||
case O::V_SUBREV_CO_CI_U32: SUBB_U32(inst, true, true); return true;
|
||||
case O::V_NOP: return;
|
||||
case O::V_ADD_I32: ADD_U32(inst, true, false); return;
|
||||
case O::V_ADDC_U32: ADD_U32(inst, true, true); return;
|
||||
case O::V_SUB_I32: SUB_U32(inst, true, false); return;
|
||||
case O::V_SUBREV_I32: SUB_U32(inst, true, true); return;
|
||||
case O::V_SUB_CO_CI_U32: SUBB_U32(inst, true, false); return;
|
||||
case O::V_SUBREV_CO_CI_U32: SUBB_U32(inst, true, true); return;
|
||||
|
||||
case O::V_MOV_B32: MOV_B32(inst, true); return true;
|
||||
case O::V_MOVRELS_B32: V_MOVRELS_B32(inst); return true;
|
||||
case O::V_MOVRELD_B32: V_MOVRELD_B32(inst); return true;
|
||||
case O::V_READFIRSTLANE_B32: V_READFIRSTLANE_B32(inst); return true;
|
||||
case O::V_READLANE_B32: V_READLANE_B32(inst); return true;
|
||||
case O::V_WRITELANE_B32: V_WRITELANE_B32(inst); return true;
|
||||
case O::V_PERMLANE16_B32: V_PERMLANE16_B32(inst, false); return true;
|
||||
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return true;
|
||||
case O::V_MOV_B32: MOV_B32(inst, true); return;
|
||||
case O::V_MOVRELS_B32: V_MOVRELS_B32(inst); return;
|
||||
case O::V_MOVRELD_B32: V_MOVRELD_B32(inst); return;
|
||||
case O::V_READFIRSTLANE_B32: V_READFIRSTLANE_B32(inst); return;
|
||||
case O::V_READLANE_B32: V_READLANE_B32(inst); return;
|
||||
case O::V_WRITELANE_B32: V_WRITELANE_B32(inst); return;
|
||||
case O::V_PERMLANE16_B32: V_PERMLANE16_B32(inst, false); return;
|
||||
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
||||
|
||||
case O::V_CMP_F_I32:
|
||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return true;
|
||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_T_I32:
|
||||
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false, false); return true;
|
||||
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMP_EQ_U32:
|
||||
case O::V_CMP_EQ_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_EQ_U32:
|
||||
case O::V_CMPX_EQ_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NE_U32:
|
||||
case O::V_CMP_NE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NE_U32:
|
||||
case O::V_CMPX_NE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
||||
true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
||||
true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_EQ_I64:
|
||||
case O::V_CMP_EQ_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NE_I64:
|
||||
case O::V_CMPX_NE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, true);
|
||||
return true;
|
||||
return;
|
||||
|
||||
case O::V_CMP_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_EQ_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true);
|
||||
return;
|
||||
case O::V_CMP_LE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
|
||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return true;
|
||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return true;
|
||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMP_EQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_EQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NLG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NLG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NEQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NLE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NLE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NLT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NLT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NGE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NGE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NGT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NGT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_EQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_EQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LG_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_GE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_GE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMP_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CMPX_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
||||
return true;
|
||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true); return true;
|
||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false); return true;
|
||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return true;
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return true;
|
||||
return;
|
||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
|
||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
|
||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
|
||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
|
||||
|
||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return true;
|
||||
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return true;
|
||||
case O::V_CVT_F32_UBYTE2: V_CVT_F32_UBYTE(inst, 2); return true;
|
||||
case O::V_CVT_F32_UBYTE3: V_CVT_F32_UBYTE(inst, 3); return true;
|
||||
case O::V_CVT_F32_U32: V_CVT_F32_U32(inst); return true;
|
||||
case O::V_CVT_F32_I32: V_CVT_F32_I32(inst); return true;
|
||||
case O::V_CVT_U32_F32: V_CVT_U32_F32(inst); return true;
|
||||
case O::V_CVT_I32_F32: V_CVT_I32_F32(inst); return true;
|
||||
case O::V_CVT_F16_F32: V_CVT_F16_F32(inst); return true;
|
||||
case O::V_CVT_F32_F16: V_CVT_F32_F16(inst); return true;
|
||||
case O::V_CVT_F16_U16: V_CVT_F16_16(inst, false); return true;
|
||||
case O::V_CVT_F16_I16: V_CVT_F16_16(inst, true); return true;
|
||||
case O::V_CVT_U16_F16: V_CVT_16_F16(inst, false); return true;
|
||||
case O::V_CVT_I16_F16: V_CVT_16_F16(inst, true); return true;
|
||||
case O::V_CVT_RPI_I32_F32: V_CVT_RPI_I32_F32(inst); return true;
|
||||
case O::V_CVT_FLR_I32_F32: V_CVT_FLR_I32_F32(inst); return true;
|
||||
case O::V_FREXP_EXP_I32_F32: V_FREXP_EXP_I32_F32(inst); return true;
|
||||
case O::V_CVT_OFF_F32_I4: V_CVT_OFF_F32_I4(inst); return true;
|
||||
case O::V_CVT_PKRTZ_F16_F32: V_CVT_PKRTZ_F16_F32(inst); return true;
|
||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
|
||||
case O::V_CVT_F32_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return;
|
||||
case O::V_CVT_F32_UBYTE2: V_CVT_F32_UBYTE(inst, 2); return;
|
||||
case O::V_CVT_F32_UBYTE3: V_CVT_F32_UBYTE(inst, 3); return;
|
||||
case O::V_CVT_F32_U32: V_CVT_F32_U32(inst); return;
|
||||
case O::V_CVT_F32_I32: V_CVT_F32_I32(inst); return;
|
||||
case O::V_CVT_F64_I32: return FloatUnary(inst, IR::ValueOpcode::ConvertF64S32);
|
||||
case O::V_CVT_F32_F64: return FloatUnary(inst, IR::ValueOpcode::ConvertF32F64);
|
||||
case O::V_CVT_U32_F32: V_CVT_U32_F32(inst); return;
|
||||
case O::V_CVT_I32_F32: V_CVT_I32_F32(inst); return;
|
||||
case O::V_CVT_F16_F32: V_CVT_F16_F32(inst); return;
|
||||
case O::V_CVT_F32_F16: V_CVT_F32_F16(inst); return;
|
||||
case O::V_CVT_F16_U16: V_CVT_F16_16(inst, false); return;
|
||||
case O::V_CVT_F16_I16: V_CVT_F16_16(inst, true); return;
|
||||
case O::V_CVT_U16_F16: V_CVT_16_F16(inst, false); return;
|
||||
case O::V_CVT_I16_F16: V_CVT_16_F16(inst, true); return;
|
||||
case O::V_CVT_RPI_I32_F32: V_CVT_RPI_I32_F32(inst); return;
|
||||
case O::V_CVT_FLR_I32_F32: V_CVT_FLR_I32_F32(inst); return;
|
||||
case O::V_FREXP_EXP_I32_F32: V_FREXP_EXP_I32_F32(inst); return;
|
||||
case O::V_CVT_OFF_F32_I4: V_CVT_OFF_F32_I4(inst); return;
|
||||
case O::V_CVT_PKRTZ_F16_F32: V_CVT_PKRTZ_F16_F32(inst); return;
|
||||
case O::V_CVT_PKNORM_I16_F32:
|
||||
V_CVT_PKNORM_F32(inst, IR::ValueOpcode::PackSnorm2x16);
|
||||
return true;
|
||||
return;
|
||||
case O::V_CVT_PKNORM_U16_F32:
|
||||
V_CVT_PKNORM_F32(inst, IR::ValueOpcode::PackUnorm2x16);
|
||||
return true;
|
||||
case O::V_CVT_PK_U8_F32: V_CVT_PK_U8_F32(inst); return true;
|
||||
case O::V_PACK_B32_F16: V_PACK_B32_F16(inst); return true;
|
||||
return;
|
||||
case O::V_CVT_PK_U8_F32: V_CVT_PK_U8_F32(inst); return;
|
||||
case O::V_PACK_B32_F16: V_PACK_B32_F16(inst); return;
|
||||
case O::V_CVT_PK_U16_U32:
|
||||
case O::V_CVT_PK_I16_I32: return PackB16(inst, false, false);
|
||||
|
||||
@@ -390,6 +396,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
|
||||
case O::V_FREXP_MANT_F32: return V_FREXP_MANT_F32(inst);
|
||||
case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32);
|
||||
case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64);
|
||||
case O::V_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32);
|
||||
case O::V_FRACT_F32: return FloatUnary(inst, IR::ValueOpcode::FPFract32);
|
||||
case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32);
|
||||
@@ -406,6 +413,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_SUB_F32: return FloatBinary(inst, IR::ValueOpcode::FPSub32, false);
|
||||
case O::V_SUBREV_F32: return FloatBinary(inst, IR::ValueOpcode::FPSub32, true);
|
||||
case O::V_MUL_F32: return FloatBinary(inst, IR::ValueOpcode::FPMul32, false);
|
||||
case O::V_MUL_F64: return FloatBinary(inst, IR::ValueOpcode::FPMul64, false);
|
||||
case O::V_MIN_F32: return FloatBinary(inst, IR::ValueOpcode::FPMin32, false);
|
||||
case O::V_MAX_F32: return FloatBinary(inst, IR::ValueOpcode::FPMax32, false);
|
||||
case O::V_LDEXP_F32: return FloatBinary(inst, IR::ValueOpcode::FPLdexp, false);
|
||||
@@ -414,6 +422,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_MADAK_F32:
|
||||
case O::V_MAD_F32:
|
||||
case O::V_FMA_F32: return FloatTernary(inst, IR::ValueOpcode::FPFma32, false, true);
|
||||
case O::V_FMA_F64: return FloatTernary(inst, IR::ValueOpcode::FPFma64, false, false);
|
||||
case O::V_MIN3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMinTri32, false, false);
|
||||
case O::V_MAX3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMaxTri32, false, false);
|
||||
case O::V_MED3_F32: return FloatTernary(inst, IR::ValueOpcode::FPMedTri32, false, false);
|
||||
@@ -534,7 +543,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_XAD_U32: return V_XAD_U32(inst);
|
||||
case O::V_LSHL_OR_B32: return V_LSHL_OR_B32(inst);
|
||||
case O::V_CNDMASK_B32: return V_CNDMASK_B32(inst);
|
||||
default: return false;
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -435,7 +435,7 @@ struct BindingLayout {
|
||||
};
|
||||
|
||||
struct ShaderInfo {
|
||||
static constexpr uint32_t MaxBuffers = 32;
|
||||
static constexpr uint32_t MaxBuffers = 64;
|
||||
static constexpr uint32_t MaxImages = 64;
|
||||
static constexpr uint32_t MaxSamplers = 32;
|
||||
static constexpr uint32_t MaxSampledPairs = 64;
|
||||
|
||||
@@ -30,6 +30,7 @@ std::string TypeName(Type type) {
|
||||
"ImageResource",
|
||||
"SamplerResource",
|
||||
"ImageAddress",
|
||||
"F64",
|
||||
};
|
||||
const auto bits = static_cast<uint32_t>(type);
|
||||
if (bits == 0) {
|
||||
|
||||
@@ -27,6 +27,7 @@ enum class Type : uint32_t {
|
||||
ImageResource = 1u << 17u,
|
||||
SamplerResource = 1u << 18u,
|
||||
ImageAddress = 1u << 19u,
|
||||
F64 = 1u << 20u,
|
||||
};
|
||||
|
||||
constexpr Type operator|(Type lhs, Type rhs) {
|
||||
|
||||
@@ -33,6 +33,7 @@ constexpr Type U32 = Type::U32;
|
||||
constexpr Type U64 = Type::U64;
|
||||
constexpr Type F16 = Type::F16;
|
||||
constexpr Type F32 = Type::F32;
|
||||
constexpr Type F64 = Type::F64;
|
||||
constexpr Type U32x2 = Type::U32x2;
|
||||
constexpr Type U32x3 = Type::U32x3;
|
||||
constexpr Type U32x4 = Type::U32x4;
|
||||
@@ -220,6 +221,8 @@ ImageOpcodeInfo ImageOpcodeInfoOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::ImageAtomicAnd32:
|
||||
case ValueOpcode::ImageAtomicOr32:
|
||||
case ValueOpcode::ImageAtomicXor32:
|
||||
case ValueOpcode::ImageAtomicFMin32:
|
||||
case ValueOpcode::ImageAtomicFMax32:
|
||||
return {ImageAccess::Atomic, ImageResourceClass::Storage, false};
|
||||
default: return {};
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ VALUE_OPCODE(BitCastU16F16, U16, F16)
|
||||
VALUE_OPCODE(BitCastF16U16, F16, U16)
|
||||
VALUE_OPCODE(BitCastU32F32, U32, F32)
|
||||
VALUE_OPCODE(BitCastF32U32, F32, U32)
|
||||
VALUE_OPCODE(BitCastU64F64, U64, F64)
|
||||
VALUE_OPCODE(BitCastF64U64, F64, U64)
|
||||
VALUE_OPCODE(ConvertU16U32, U16, U32)
|
||||
VALUE_OPCODE(ConvertU32U16, U32, U16)
|
||||
VALUE_OPCODE(ConvertU8U32, U8, U32)
|
||||
@@ -61,6 +63,8 @@ VALUE_OPCODE(ConvertF16F32, F16, F32)
|
||||
VALUE_OPCODE(ConvertS32F32, U32, F32)
|
||||
VALUE_OPCODE(ConvertU32F32, U32, F32)
|
||||
VALUE_OPCODE(ConvertF32S32, F32, U32)
|
||||
VALUE_OPCODE(ConvertF64S32, F64, U32)
|
||||
VALUE_OPCODE(ConvertF32F64, F32, F64)
|
||||
VALUE_OPCODE(ConvertF32U32, F32, U32)
|
||||
VALUE_OPCODE(CompositeConstructU64, U64, U32, U32)
|
||||
VALUE_OPCODE(CompositeConstructU32x2, U32x2, U32, U32)
|
||||
@@ -166,6 +170,9 @@ VALUE_OPCODE(FPAdd32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPSub32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPFma32, F32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPMul32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPMul64, F64, F64, F64)
|
||||
VALUE_OPCODE(FPFma64, F64, F64, F64, F64)
|
||||
VALUE_OPCODE(FPRecip64, F64, F64)
|
||||
VALUE_OPCODE(FPMin32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPMax32, F32, F32, F32)
|
||||
VALUE_OPCODE(FPMinTri32, F32, F32, F32, F32)
|
||||
@@ -284,6 +291,8 @@ VALUE_OPCODE(ImageAtomicUMax32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicAnd32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicOr32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicXor32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicFMin32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(ImageAtomicFMax32, U32, ImageResource, ImageAddress, U32, U1)
|
||||
VALUE_OPCODE(GetAttribute, U32, U32, U32)
|
||||
VALUE_OPCODE(GetInterpolationParameter, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(SetAttribute, Void, U32x4, U1)
|
||||
|
||||
@@ -46,29 +46,45 @@ void AddBinding(BindingLayout& layout, DescriptorBindingKind kind,
|
||||
layout.descriptors.push_back({kind, std::move(resources)});
|
||||
}
|
||||
|
||||
bool UsesGds(const Program& program) {
|
||||
} // namespace
|
||||
|
||||
bool CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers) {
|
||||
std::array<bool, ShaderInfo::MaxBuffers> live_buffers {};
|
||||
bool uses_gds = false;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (SharedAccessOf(inst.GetOpcode()) == SharedAccess::None) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (BufferAccessOf(op) == BufferAccess::None &&
|
||||
SharedAccessOf(op) == SharedAccess::None) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<MemoryFlags>().index;
|
||||
if (index >= program.memory_info.size()) {
|
||||
BindingFail("typed shader contains invalid shared-memory metadata");
|
||||
BindingFail("typed shader contains invalid memory metadata");
|
||||
}
|
||||
const auto kind = program.memory_info[index].kind;
|
||||
if (kind != ResourceKind::Lds && kind != ResourceKind::Gds) {
|
||||
BindingFail("typed shader contains invalid shared-memory metadata");
|
||||
const auto& memory = program.memory_info[index];
|
||||
if (memory.planning_only) {
|
||||
continue;
|
||||
}
|
||||
uses_gds |= kind == ResourceKind::Gds;
|
||||
if (SharedAccessOf(op) != SharedAccess::None) {
|
||||
if (memory.kind != ResourceKind::Lds && memory.kind != ResourceKind::Gds) {
|
||||
BindingFail("typed shader contains invalid shared-memory metadata");
|
||||
}
|
||||
uses_gds |= memory.kind == ResourceKind::Gds;
|
||||
} else if (memory.kind == ResourceKind::Buffer || memory.kind == ResourceKind::ScalarBuffer) {
|
||||
EXIT_IF(memory.resource >= program.info.buffers.size());
|
||||
live_buffers.at(memory.resource) = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
if (live_buffers.at(i)) {
|
||||
buffers.push_back(i);
|
||||
}
|
||||
}
|
||||
return uses_gds;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
if (!program.shader_info_complete || program.binding_layout_complete) {
|
||||
EXIT("shader binding layout failed: %s", !program.shader_info_complete
|
||||
@@ -76,18 +92,17 @@ void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
: "binding layout already allocated");
|
||||
}
|
||||
BindingLayout next;
|
||||
std::vector<uint32_t> buffers;
|
||||
const bool uses_gds = CollectMemoryResources(program, buffers);
|
||||
next.user_data_registers = CollectUserData(program);
|
||||
next.memory_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
|
||||
next.memory_offset_count = static_cast<uint32_t>(program.info.buffers.size());
|
||||
next.memory_offset_count = static_cast<uint32_t>(buffers.size());
|
||||
next.push_data_start_dword =
|
||||
PushData::StartFor(push_data_start_dword, next.ShaderDataDwords());
|
||||
|
||||
if (!program.info.buffers.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < resources.size(); i++) {
|
||||
resources[i] = i;
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::Buffers, std::move(resources));
|
||||
if (!buffers.empty()) {
|
||||
// Draw binding accesses this first group directly when memory_offset_count is nonzero.
|
||||
AddBinding(next, DescriptorBindingKind::Buffers, std::move(buffers));
|
||||
}
|
||||
|
||||
std::array<std::vector<uint32_t>, ImageBindingCount> image_groups;
|
||||
@@ -123,7 +138,7 @@ void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::Samplers, std::move(resources));
|
||||
}
|
||||
if (UsesGds(program)) {
|
||||
if (uses_gds) {
|
||||
AddBinding(next, DescriptorBindingKind::Gds);
|
||||
}
|
||||
if (program.info.uses_dma) {
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword = 0);
|
||||
|
||||
// Collect live direct buffer indices and report whether the shader accesses GDS.
|
||||
bool CollectMemoryResources(const Program& program, std::vector<uint32_t>& buffers);
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
@@ -400,6 +401,9 @@ struct ImageRemap {
|
||||
template <typename T>
|
||||
void Apply(std::vector<T>& images) const {
|
||||
EXIT_IF(images.size() != source_count);
|
||||
if (count == source_count) {
|
||||
return;
|
||||
}
|
||||
for (uint32_t index = 0; index < source_count; index++) {
|
||||
if (indices[index] != UINT32_MAX && indices[index] != index) {
|
||||
images[indices[index]] = std::move(images[index]);
|
||||
@@ -446,6 +450,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
: Prospero::BufferFormat::kInvalid,
|
||||
.descriptor_swizzle =
|
||||
program.info.buffers[i].formatted ? descriptor.DstSelXYZW() : DstSel(4, 5, 6, 7),
|
||||
.zero_stride_oob = descriptor.OutOfBounds() == 0u && stride == 0u,
|
||||
});
|
||||
}
|
||||
for (uint32_t i = 0; i < specialization.images.size(); i++) {
|
||||
@@ -485,7 +490,8 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
image.cube = DescriptorIsCube(descriptor);
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (base.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
if (base.atomic && format != Prospero::BufferFormat::k32UInt &&
|
||||
format != Prospero::BufferFormat::k32Float) {
|
||||
return SpecializationFail(
|
||||
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
@@ -514,7 +520,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
fmt::format("storage image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
}
|
||||
if (raw_sint_storage) {
|
||||
if (raw_sint_storage || base.atomic) {
|
||||
image.numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
}
|
||||
} else if (image.numeric_class == Prospero::TextureNumericClass::Unsupported ||
|
||||
@@ -1001,10 +1007,10 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
}
|
||||
}
|
||||
snapshot.images.resize(program.info.images.size());
|
||||
specialization.images.clear();
|
||||
specialization.images.reserve(program.info.images.size());
|
||||
for (const auto& image: program.info.images) {
|
||||
specialization.images.push_back({
|
||||
specialization.images.resize(program.info.images.size());
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& image = program.info.images[i];
|
||||
specialization.images[i] = {
|
||||
.numeric_class = image.numeric_class,
|
||||
.dimension = image.dimension,
|
||||
.mip_count = image.mip_count,
|
||||
@@ -1014,10 +1020,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
.indirect_mapping_offset = image.indirect_mapping_offset,
|
||||
.indirect_search_iterations = image.indirect_search_iterations,
|
||||
.cube = image.cube,
|
||||
});
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& image = program.info.images[i];
|
||||
};
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source == nullptr) {
|
||||
return false;
|
||||
@@ -1129,6 +1132,49 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto it = block->begin(); it != block->end(); ++it) {
|
||||
auto& inst = *it;
|
||||
if (BufferAccessOf(inst.GetOpcode()) == BufferAccess::Read) {
|
||||
const auto& memory = memory_info[inst.Flags<MemoryFlags>().index];
|
||||
if (memory.kind == ResourceKind::Buffer &&
|
||||
specialization.buffers[memory.resource].zero_stride_oob) {
|
||||
// Bounds mode 0 checks offset >= stride, so zero stride
|
||||
// makes every vector read out of bounds regardless of its address.
|
||||
const auto count = BufferComponentCount(inst.GetOpcode());
|
||||
std::array<Value, 4> values {Value(0u), Value(0u), Value(0u), Value(0u)};
|
||||
if (memory.formatted && !memory.typed) {
|
||||
const auto& buffer = buffers[memory.resource];
|
||||
const auto format = Format::GetFormatInfo(buffer.descriptor_format);
|
||||
for (uint32_t component = 0; component < count; component++) {
|
||||
if (format.type == Format::ComponentType::Unknown ||
|
||||
GetDstSel(buffer.descriptor_swizzle, component) != 1u) continue;
|
||||
const auto one = Format::FormattedConstantBits(
|
||||
format, Format::FormattedSourceKind::One);
|
||||
values[component] = Value(&*block->PrependNewInst(
|
||||
it, ValueOpcode::SelectU32,
|
||||
{inst.Arg(inst.NumArgs() - 1), Value(one), Value(0u)}));
|
||||
}
|
||||
}
|
||||
Value result = values[0];
|
||||
switch (inst.GetType()) {
|
||||
case Type::U8: result = Value(uint8_t {0}); break;
|
||||
case Type::U16: result = Value(uint16_t {0}); break;
|
||||
case Type::U32x2:
|
||||
result = Value(&*block->PrependNewInst(it, ValueOpcode::CompositeConstructU32x2,
|
||||
{values[0], values[1]}));
|
||||
break;
|
||||
case Type::U32x3:
|
||||
result = Value(&*block->PrependNewInst(it, ValueOpcode::CompositeConstructU32x3,
|
||||
{values[0], values[1], values[2]}));
|
||||
break;
|
||||
case Type::U32x4:
|
||||
result = Value(&*block->PrependNewInst(it, ValueOpcode::CompositeConstructU32x4,
|
||||
{values[0], values[1], values[2], values[3]}));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
inst.ReplaceUsesWith(result);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto image_opcode = ImageOpcodeInfoOf(inst.GetOpcode());
|
||||
if (image_opcode.access == ImageAccess::None) {
|
||||
continue;
|
||||
|
||||
@@ -12,6 +12,7 @@ struct ResourceSpecialization {
|
||||
uint32_t packed_stride = 0;
|
||||
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
|
||||
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
|
||||
bool zero_stride_oob = false;
|
||||
bool operator==(const Buffer&) const = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -409,16 +409,6 @@ static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
|
||||
uint32_t offset = (attrib[in.semantic] >> 14u) & 0xfffu;
|
||||
uint32_t fetch_index = (attrib[in.semantic] >> 26u) & 0x1u;
|
||||
|
||||
if (fetch_index != 0) {
|
||||
static std::atomic<uint64_t> log_count = 0;
|
||||
auto log_id = log_count.fetch_add(1);
|
||||
if (log_id < 64) {
|
||||
LOGF("\t temporary: PS5 vertex attrib semantic %u uses fetch index %u, buffer "
|
||||
"index %zu\n",
|
||||
static_cast<uint32_t>(in.semantic), fetch_index, index);
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(index >= ShaderVertexInputInfo::RES_MAX);
|
||||
|
||||
const auto* sharp = &buffer[index * 4];
|
||||
@@ -439,14 +429,6 @@ static void ShaderApplyAttribSemantics(ShaderVertexInputInfo& info,
|
||||
const auto format_raw = static_cast<uint32_t>(format);
|
||||
const auto buffer_format = format_raw >> 2u;
|
||||
const auto channels = (format_raw & 3u) + 1u;
|
||||
static std::atomic<uint64_t> log_count = 0;
|
||||
auto log_id = log_count.fetch_add(1);
|
||||
if (log_id < 64) {
|
||||
LOGF("\t PS5 vertex attrib semantic %u uses attrib format %u -> buffer "
|
||||
"format %u, offset %u, buffer index %zu\n",
|
||||
static_cast<uint32_t>(in.semantic), static_cast<uint32_t>(format),
|
||||
static_cast<uint32_t>(buffer_format), offset, index);
|
||||
}
|
||||
// AGC vertex formats encode the buffer format above the two channel-count bits.
|
||||
// The fetch prolog selects X001, XY01, XYZ1, or XYZW from that count.
|
||||
r.fields[3] = (r.fields[3] & ~((0x7fu << 12u) | 0xfffu)) |
|
||||
@@ -640,6 +622,7 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
|
||||
info.threads_num[2] = regs.cs_regs.num_thread_z;
|
||||
info.lds_size_dwords = static_cast<uint32_t>(regs.cs_regs.lds_size) * 128u;
|
||||
info.scratch_size_dwords = data.scratch_size_dwords;
|
||||
info.float_mode = regs.cs_regs.float_mode;
|
||||
info.group_id[0] = regs.cs_regs.tgid_x_en != 0;
|
||||
info.group_id[1] = regs.cs_regs.tgid_y_en != 0;
|
||||
info.group_id[2] = regs.cs_regs.tgid_z_en != 0;
|
||||
@@ -745,7 +728,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
void BuildStageStaticKey(const ShaderComputeInputInfo& info, std::vector<uint32_t>& key) {
|
||||
key.clear();
|
||||
key.push_back(info.workgroup_register);
|
||||
key.push_back(info.wave_size);
|
||||
key.push_back(info.wave_size | (static_cast<uint32_t>(info.float_mode) << 8u));
|
||||
key.push_back(info.host_subgroup_size);
|
||||
key.push_back(info.thread_ids_num);
|
||||
key.push_back(info.lds_size_dwords);
|
||||
|
||||
@@ -145,6 +145,7 @@ struct ShaderVertexInputInfo {
|
||||
};
|
||||
|
||||
struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
|
||||
uint8_t float_mode = 0xc0;
|
||||
uint32_t dispatch_threads_num[3] = {0, 0, 0};
|
||||
bool group_id[3] = {false, false, false};
|
||||
bool dispatch_thread_dimensions = false;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
class QByteArray;
|
||||
class QWidget;
|
||||
@@ -29,6 +31,11 @@ private:
|
||||
QWidget* m_parent = nullptr;
|
||||
QNetworkAccessManager m_network;
|
||||
bool m_checking_updates = false;
|
||||
|
||||
// Answer from the primary update feed, kept while the GitHub fallback feed is
|
||||
// being consulted, so it can still be shown if the fallback request fails.
|
||||
QString m_primary_tag;
|
||||
QUrl m_primary_page_url;
|
||||
};
|
||||
|
||||
#endif // UPDATE_CHECKER_H
|
||||
@@ -440,6 +440,14 @@ void ConfigurationEditDialog::add_game_directory() {
|
||||
start_dir = m_game_dirs_list->item(m_game_dirs_list->count() - 1)->text();
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// Use the native macOS picker to browse mounted volumes.
|
||||
const auto dir = QFileDialog::getExistingDirectory(this, tr("Select game folder"), start_dir);
|
||||
if (dir.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AddGameDirectoryItem(dir);
|
||||
#else
|
||||
QFileDialog dialog(this, tr("Select game folders"), start_dir);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly, true);
|
||||
@@ -462,6 +470,7 @@ void ConfigurationEditDialog::add_game_directory() {
|
||||
for (const auto& dir: dialog.selectedFiles()) {
|
||||
AddGameDirectoryItem(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
update_game_directory_buttons();
|
||||
}
|
||||
|
||||
@@ -84,12 +84,30 @@ void UpdateChecker::FetchUpdateInfo(const char* url, bool fallback, bool manual)
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
if (!fallback &&
|
||||
(!info.error.isEmpty() || info.tag != QString::fromLatin1(KYTY_RELEASE_TAG))) {
|
||||
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
|
||||
return;
|
||||
if (!fallback) {
|
||||
const bool up_to_date =
|
||||
info.error.isEmpty() && info.tag == QString::fromLatin1(KYTY_RELEASE_TAG);
|
||||
if (!up_to_date) {
|
||||
if (info.error.isEmpty()) {
|
||||
// The primary feed is reachable but disagrees with the build tag
|
||||
// (e.g. a stale mirror); remember its answer in case the fallback
|
||||
// request also fails.
|
||||
m_primary_tag = info.tag;
|
||||
m_primary_page_url = info.page_url;
|
||||
}
|
||||
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fallback && !info.error.isEmpty() && !m_primary_tag.isEmpty()) {
|
||||
// The fallback feed failed, but the primary feed already returned a
|
||||
// usable answer; prefer it over reporting a network error.
|
||||
info = {m_primary_tag, m_primary_page_url, {}};
|
||||
}
|
||||
m_primary_tag.clear();
|
||||
m_primary_page_url.clear();
|
||||
|
||||
m_checking_updates = false;
|
||||
emit CheckingChanged(false);
|
||||
ShowUpdateResult(info, manual);
|
||||
|
||||
+39
-5
@@ -7,6 +7,8 @@
|
||||
#include "common/threads.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/audio_internal.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/dualSenseHaptics.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
|
||||
@@ -101,10 +103,12 @@ private:
|
||||
uint32_t freq = 0;
|
||||
Format format = Format::Unknown;
|
||||
uint64_t last_output_time = 0;
|
||||
bool queue_primed = false;
|
||||
int channels_num = 0;
|
||||
int volume[12] = {};
|
||||
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
Controller::DualSenseHaptics::Stream* haptics = nullptr;
|
||||
};
|
||||
|
||||
struct PortIn {
|
||||
@@ -267,6 +271,8 @@ bool Audio::OpenSdlDevice(PortOut* port) {
|
||||
|
||||
void Audio::CloseSdlDevice(PortOut* port) {
|
||||
EXIT_IF(port == nullptr);
|
||||
Controller::DualSenseHaptics::Close(port->haptics);
|
||||
port->haptics = nullptr;
|
||||
|
||||
if (port->stream != nullptr) {
|
||||
SDL_DestroyAudioStream(port->stream);
|
||||
@@ -360,20 +366,34 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
|
||||
const auto prepared_size =
|
||||
BytesPerSample(port->format) * output_channels * port->samples_num;
|
||||
|
||||
uint32_t min_queued_size = 0;
|
||||
if (blocking) {
|
||||
constexpr uint64_t target_latency_us = 40000;
|
||||
const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0;
|
||||
const auto buffers =
|
||||
buffer_us != 0 ? static_cast<uint32_t>((target_latency_us + buffer_us - 1) / buffer_us)
|
||||
: 2u;
|
||||
const auto min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u);
|
||||
min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u);
|
||||
const auto wait_start = LibKernel::KernelGetProcessTime();
|
||||
while (SDL_GetAudioStreamQueued(port->stream) > static_cast<int>(min_queued_size)) {
|
||||
auto queued = SDL_GetAudioStreamQueued(port->stream);
|
||||
if (queued < static_cast<int>(prepared_size)) {
|
||||
port->queue_primed = false;
|
||||
}
|
||||
while (queued > static_cast<int>(min_queued_size)) {
|
||||
if (LibKernel::KernelGetProcessTime() - wait_start > 200000) {
|
||||
SDL_ClearAudioStream(port->stream);
|
||||
port->queue_primed = false;
|
||||
break;
|
||||
}
|
||||
Common::Thread::SleepMicro(1000);
|
||||
queued = SDL_GetAudioStreamQueued(port->stream);
|
||||
}
|
||||
if (port->queue_primed) {
|
||||
const auto next_time = port->last_output_time + buffer_us;
|
||||
const auto now = LibKernel::KernelGetProcessTime();
|
||||
if (next_time > now) {
|
||||
Common::Thread::SleepMicro(next_time - now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +401,10 @@ bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) {
|
||||
LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
if (blocking && !port->queue_primed &&
|
||||
SDL_GetAudioStreamQueued(port->stream) >= static_cast<int>(min_queued_size)) {
|
||||
port->queue_primed = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -416,7 +440,9 @@ Audio::Id Audio::AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, For
|
||||
port.volume[i] = 32768;
|
||||
}
|
||||
|
||||
if (type != AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
if (type == AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
port.haptics = Controller::DualSenseHaptics::Open(freq);
|
||||
} else {
|
||||
OpenSdlDevice(&port);
|
||||
}
|
||||
|
||||
@@ -528,7 +554,15 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
auto& port = m_out_ports[params[i].handle.GetId()];
|
||||
|
||||
QueueSdlAudio(&port, params[i].data, blocking);
|
||||
if (port.type == AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
// Haptics never pace output; keep the stream alive against close and volume changes.
|
||||
Common::LockGuard lock(m_mutex);
|
||||
Controller::DualSenseHaptics::Queue(
|
||||
port.haptics, Controller::GetActiveControllerId(), params[i].data, port.samples_num,
|
||||
static_cast<uint32_t>(port.channels_num), FormatIsFloat(port.format), port.volume);
|
||||
} else {
|
||||
QueueSdlAudio(&port, params[i].data, blocking);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
|
||||
+10
-11
@@ -950,11 +950,8 @@ public:
|
||||
if (deliver_seek_frame || sync_mode != 0) {
|
||||
return true;
|
||||
}
|
||||
if (audio_id) {
|
||||
return candidate.info.time_stamp <= last_audio_ts;
|
||||
}
|
||||
auto now = CurrentTimeNoLock();
|
||||
return now == 0 || candidate.info.time_stamp <= now;
|
||||
return now == 0 || candidate.info.time_stamp + candidate.timestamp_offset <= now;
|
||||
});
|
||||
if (!frame) {
|
||||
return false;
|
||||
@@ -999,7 +996,9 @@ public:
|
||||
out->details.audio.size = current_audio->info.details.audio.size;
|
||||
std::memcpy(out->details.audio.language_code,
|
||||
current_audio->info.details.audio.language_code, 4);
|
||||
last_audio_ts = out->time_stamp;
|
||||
start_time_ms = current_audio->info.time_stamp + current_audio->timestamp_offset;
|
||||
clock_start = std::chrono::steady_clock::now();
|
||||
paused_extra = {};
|
||||
RecordLoopBoundary(*current_audio);
|
||||
return true;
|
||||
}
|
||||
@@ -1064,7 +1063,6 @@ private:
|
||||
video_done = true;
|
||||
audio_done = true;
|
||||
seek_video_frame_pending = false;
|
||||
last_audio_ts = 0;
|
||||
last_output_loop_offset = 0;
|
||||
pending_loop_warnings = 0;
|
||||
}
|
||||
@@ -1397,7 +1395,6 @@ private:
|
||||
av_frame_free(&frame);
|
||||
return false;
|
||||
}
|
||||
ready.info.time_stamp += timestamp_offset;
|
||||
ready.timestamp_offset = timestamp_offset;
|
||||
frames.Push(std::move(ready));
|
||||
av_frame_free(&frame);
|
||||
@@ -1541,11 +1538,14 @@ private:
|
||||
return false;
|
||||
}
|
||||
auto* dst = buffer->Get();
|
||||
std::memset(dst, 0, static_cast<size_t>(size));
|
||||
auto* c = dst + pitch * h;
|
||||
// PPSA02433 samples the padded columns; zero chroma would turn them green.
|
||||
std::memset(dst, s->codecpar->color_range == AVCOL_RANGE_JPEG ? 0 : 16,
|
||||
static_cast<size_t>(pitch) * h);
|
||||
std::memset(c, 128, static_cast<size_t>(pitch) * h / 2);
|
||||
for (int y = 0; y < src->height; y++) {
|
||||
std::memcpy(dst + y * pitch, nv12->data[0] + y * nv12->linesize[0], src->width);
|
||||
}
|
||||
auto* c = dst + pitch * h;
|
||||
for (int y = 0; y < src->height / 2; y++) {
|
||||
std::memcpy(c + y * pitch, nv12->data[1] + y * nv12->linesize[1], src->width);
|
||||
}
|
||||
@@ -1674,7 +1674,6 @@ private:
|
||||
int32_t trick_speed = AVPLAYER_TRICK_SPEED_NORMAL;
|
||||
uint32_t sync_mode = 0;
|
||||
uint64_t start_time_ms = 0;
|
||||
uint64_t last_audio_ts = 0;
|
||||
uint64_t last_output_loop_offset = 0;
|
||||
uint32_t pending_loop_warnings = 0;
|
||||
std::chrono::steady_clock::time_point clock_start {};
|
||||
@@ -1719,7 +1718,7 @@ static AvPlayerInternal* create_player(const AvPlayerMemAllocator& mem,
|
||||
h->mem = mem;
|
||||
h->file = file;
|
||||
h->event = event;
|
||||
h->auto_start = auto_start;
|
||||
h->auto_start = auto_start || event.event_callback == nullptr;
|
||||
h->video_buffers = std::clamp(video_buffers <= 0 ? 2 : video_buffers, 2, 16);
|
||||
h->post_init.demux_video_buffer_size = 4 * 1024 * 1024;
|
||||
return h;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/dualSenseHaptics.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "libs/padData.h"
|
||||
@@ -113,6 +114,7 @@ public:
|
||||
void ReleaseHostPads();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
void SetVibration(uint8_t large_motor, uint8_t small_motor);
|
||||
int GetActiveControllerId();
|
||||
void SetLightBar(uint8_t r, uint8_t g, uint8_t b);
|
||||
bool SetTriggerEffect(const PadTriggerEffectParam& param);
|
||||
void ReadState(ControllerState* state, bool* flag, int* count);
|
||||
@@ -518,6 +520,7 @@ void GameController::ResetInputState() {
|
||||
|
||||
void GameController::ReleaseHostPads() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
DualSenseHaptics::Shutdown();
|
||||
|
||||
std::vector<SDL_Gamepad*> pads;
|
||||
for (const auto id: m_connected_ids) {
|
||||
@@ -551,6 +554,9 @@ void GameController::ReleaseHostPads() {
|
||||
|
||||
void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (DualSenseHaptics::SetVibration(m_active_id, large_motor, small_motor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_active_id == HOST_INPUT_CONTROLLER_ID) {
|
||||
return;
|
||||
@@ -568,6 +574,11 @@ void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
}
|
||||
}
|
||||
|
||||
int GameController::GetActiveControllerId() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_active_id;
|
||||
}
|
||||
|
||||
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
@@ -692,6 +703,10 @@ void ResetInputState() {
|
||||
g_controller->ResetInputState();
|
||||
}
|
||||
|
||||
int GetActiveControllerId() {
|
||||
return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadInit() {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ void SetRightStick(int id, int x, int y);
|
||||
void SetTouchPad(int id, int finger, bool down, float x, float y);
|
||||
void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us);
|
||||
void ResetInputState();
|
||||
int GetActiveControllerId();
|
||||
|
||||
int KYTY_SYSV_ABI PadInit();
|
||||
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "libs/dualSenseHaptics.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Controller::DualSenseHaptics {
|
||||
|
||||
struct Stream {
|
||||
uint32_t freq = 0;
|
||||
SDL_AudioStream* sdl = nullptr;
|
||||
SDL_AudioDeviceID device = 0;
|
||||
uint64_t next_check = 0;
|
||||
std::vector<float> buffer;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int FRAME_BYTES = 4 * sizeof(float);
|
||||
Common::Mutex g_mutex;
|
||||
int g_controller = -1;
|
||||
int g_playback_streams = 0;
|
||||
uint8_t g_large_motor = 0, g_small_motor = 0;
|
||||
uint64_t g_rumble_until = 0, g_haptics_until = 0;
|
||||
|
||||
void ApplyRumble() { // Caller holds g_mutex; no calls into Audio or Controller.
|
||||
SDL_LockJoysticks();
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(g_controller));
|
||||
pad != nullptr) {
|
||||
const auto now = SDL_GetTicks();
|
||||
const bool muted = g_haptics_until > now || g_rumble_until <= now;
|
||||
(void)SDL_RumbleGamepad(
|
||||
pad, muted ? 0 : g_large_motor * 0x101U, muted ? 0 : g_small_motor * 0x101U,
|
||||
g_rumble_until > now ? static_cast<uint32_t>(g_rumble_until - now) : 0);
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
}
|
||||
|
||||
void SelectController(int controller) {
|
||||
if (g_controller != controller) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
g_controller = controller;
|
||||
g_large_motor = g_small_motor = 0;
|
||||
g_rumble_until = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLCALL UpdateRumble(void*, SDL_AudioStream*, int, int) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (g_haptics_until != 0 && SDL_GetTicks() >= g_haptics_until) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
|
||||
void CloseDevice(Stream* stream) {
|
||||
if (stream->sdl != nullptr) {
|
||||
// Destroy outside g_mutex: SDL waits for any running get callback.
|
||||
SDL_DestroyAudioStream(stream->sdl);
|
||||
stream->sdl = nullptr;
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (--g_playback_streams == 0 && g_haptics_until != 0) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_AudioDeviceID FindDevice() {
|
||||
int count = 0;
|
||||
auto* devices = SDL_GetAudioPlaybackDevices(&count);
|
||||
SDL_AudioDeviceID device = 0;
|
||||
for (int i = 0; i < count && device == 0; i++) {
|
||||
const char* name = SDL_GetAudioDeviceName(devices[i]);
|
||||
SDL_AudioSpec spec {};
|
||||
if (name != nullptr && SDL_strcasestr(name, "DualSense") != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(devices[i], &spec, nullptr) && spec.channels == 4) {
|
||||
device = devices[i];
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
return device;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Stream* Open(uint32_t freq) {
|
||||
if (freq == 0 || !SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new Stream {.freq = freq};
|
||||
}
|
||||
|
||||
void Close(Stream* stream) {
|
||||
if (stream != nullptr) {
|
||||
CloseDevice(stream);
|
||||
delete stream;
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
void Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume) {
|
||||
if (stream == nullptr || data == nullptr || frames == 0 || channels == 0 || volume == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) != SDL_GAMEPAD_TYPE_PS5) {
|
||||
CloseDevice(stream);
|
||||
return;
|
||||
}
|
||||
const auto now = SDL_GetTicks();
|
||||
if (now >= stream->next_check) {
|
||||
stream->next_check = now + 2000;
|
||||
const auto device = FindDevice();
|
||||
if (stream->device != device || stream->sdl == nullptr) {
|
||||
CloseDevice(stream);
|
||||
stream->device = device;
|
||||
if (device != 0) {
|
||||
const SDL_AudioSpec desired {SDL_AUDIO_F32, 4, static_cast<int>(stream->freq)};
|
||||
auto* sdl = SDL_OpenAudioDeviceStream(device, &desired, UpdateRumble, nullptr);
|
||||
SDL_AudioSpec actual {};
|
||||
if (sdl != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(sdl), &actual, nullptr) &&
|
||||
actual.channels == 4 && SDL_ResumeAudioStreamDevice(sdl)) {
|
||||
stream->sdl = sdl;
|
||||
{
|
||||
Common::LockGuard lock(g_mutex);
|
||||
g_playback_streams++;
|
||||
}
|
||||
LOGF("DualSenseHaptics: playing on '%s'\n", SDL_GetAudioDeviceName(device));
|
||||
} else {
|
||||
SDL_DestroyAudioStream(sdl);
|
||||
LOGF("DualSenseHaptics: cannot open quad output: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stream->sdl == nullptr) {
|
||||
return;
|
||||
}
|
||||
stream->buffer.assign(static_cast<size_t>(frames) * 4, 0.0f);
|
||||
bool audible = false;
|
||||
for (uint32_t frame = 0; frame < frames; frame++) {
|
||||
for (uint32_t ch = 0; ch < 2; ch++) {
|
||||
const auto src_ch = channels == 1 ? 0 : ch;
|
||||
const auto index = static_cast<size_t>(frame) * channels + src_ch;
|
||||
float value = is_float ? static_cast<const float*>(data)[index]
|
||||
: static_cast<const int16_t*>(data)[index] / 32768.0f;
|
||||
value *= volume[src_ch] / 32768.0f;
|
||||
stream->buffer[static_cast<size_t>(frame) * 4 + ch + 2] = value;
|
||||
audible |= std::fabs(value) > 1.0f / 1024;
|
||||
}
|
||||
}
|
||||
auto queued = std::max(0, SDL_GetAudioStreamQueued(stream->sdl));
|
||||
if (queued > static_cast<int>(stream->freq * FRAME_BYTES * 80 / 1000)) {
|
||||
SDL_ClearAudioStream(stream->sdl);
|
||||
queued = 0;
|
||||
}
|
||||
const int bytes = static_cast<int>(frames * FRAME_BYTES);
|
||||
if (audible) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) !=
|
||||
SDL_GAMEPAD_TYPE_PS5) {
|
||||
return;
|
||||
}
|
||||
SelectController(controller);
|
||||
const auto playback_now = SDL_GetTicks();
|
||||
const bool starting = g_haptics_until <= playback_now;
|
||||
const auto duration = (queued + bytes) * 1000ULL / (stream->freq * FRAME_BYTES);
|
||||
g_haptics_until =
|
||||
std::max(g_haptics_until, playback_now + std::max<uint64_t>(duration, 250));
|
||||
if (starting) {
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
if (!SDL_PutAudioStreamData(stream->sdl, stream->buffer.data(), bytes)) {
|
||||
CloseDevice(stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor) {
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) != SDL_GAMEPAD_TYPE_PS5) {
|
||||
return false;
|
||||
}
|
||||
Common::LockGuard lock(g_mutex);
|
||||
SelectController(controller);
|
||||
g_large_motor = large_motor;
|
||||
g_small_motor = small_motor;
|
||||
g_rumble_until = SDL_GetTicks() + 0xffff;
|
||||
ApplyRumble();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
g_rumble_until = 0;
|
||||
SelectController(-1);
|
||||
}
|
||||
|
||||
} // namespace Libs::Controller::DualSenseHaptics
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_DUALSENSE_HAPTICS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_DUALSENSE_HAPTICS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Controller::DualSenseHaptics {
|
||||
|
||||
struct Stream;
|
||||
|
||||
Stream* Open(uint32_t freq);
|
||||
void Close(Stream* stream);
|
||||
void Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume);
|
||||
// Returns false for other gamepad types, which retain the normal rumble path.
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor);
|
||||
void Shutdown();
|
||||
|
||||
} // namespace Libs::Controller::DualSenseHaptics
|
||||
|
||||
#endif // EMULATOR_INCLUDE_EMULATOR_DUALSENSE_HAPTICS_H_
|
||||
+22
-12
@@ -188,7 +188,7 @@ struct AudioOut2PortStateEntry {
|
||||
uint32_t samples_num = 512;
|
||||
AudioInternal::Format audio_format = AudioInternal::Format::Unknown;
|
||||
int audio_handle = 0;
|
||||
const void* pcm_data = nullptr;
|
||||
std::vector<uint8_t> pcm_data;
|
||||
};
|
||||
|
||||
struct AudioOut2SpeakerArrayState {
|
||||
@@ -341,11 +341,17 @@ static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle p
|
||||
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);
|
||||
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(AudioOut2ContextHandle ctx) {
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
||||
state.pcm_data != nullptr && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
|
||||
!state.pcm_data.empty() && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -353,24 +359,23 @@ static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
|
||||
}
|
||||
|
||||
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
|
||||
// The backend consumes the buffers synchronously, but can block while pacing SDL's queue.
|
||||
// Keep both PCM storage and port handles alive until it returns.
|
||||
std::vector<AudioInternal::OutputParam> params;
|
||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||
|
||||
g_audioout2_port_mutex.Lock();
|
||||
Common::LockGuard lock(g_audioout2_port_mutex);
|
||||
for (const auto& state: g_audioout2_ports) {
|
||||
if (state.used && state.context == ctx && state.audio_handle > 0 &&
|
||||
state.pcm_data != nullptr && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data});
|
||||
!state.pcm_data.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data.data()});
|
||||
}
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
|
||||
if (params.empty()) {
|
||||
return;
|
||||
if (!params.empty()) {
|
||||
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
|
||||
blocking);
|
||||
}
|
||||
|
||||
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
|
||||
blocking);
|
||||
}
|
||||
|
||||
static void audioout2_close_audio_handle(int audio_handle) {
|
||||
@@ -679,7 +684,12 @@ int KYTY_SYSV_ABI AudioOut2PortSetAttributes(AudioOut2PortHandle port,
|
||||
if (has_pcm) {
|
||||
g_audioout2_port_mutex.Lock();
|
||||
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
|
||||
state->pcm_data = pcm_data;
|
||||
if (pcm_data != nullptr && state->audio_format != AudioInternal::Format::Unknown) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(pcm_data);
|
||||
state->pcm_data.assign(bytes, bytes + audioout2_pcm_size(*state));
|
||||
} else {
|
||||
state->pcm_data.clear();
|
||||
}
|
||||
}
|
||||
g_audioout2_port_mutex.Unlock();
|
||||
}
|
||||
|
||||
@@ -1226,6 +1226,7 @@ static KYTY_SYSV_ABI KernelModule KernelLoadStartModule(const char* module_file_
|
||||
|
||||
auto* program = rt->FindProgramByFileName(module_path);
|
||||
if (program != nullptr) {
|
||||
++program->load_count;
|
||||
if (res != nullptr) {
|
||||
*res = OK;
|
||||
}
|
||||
@@ -1240,6 +1241,7 @@ static KYTY_SYSV_ABI KernelModule KernelLoadStartModule(const char* module_file_
|
||||
|
||||
rt->RelocateProgram(program);
|
||||
|
||||
program->load_count = 1;
|
||||
int result = rt->StartModule(program, args, argp, nullptr);
|
||||
|
||||
LOGF("\tmodule_start() result = %d\n", result);
|
||||
@@ -1269,6 +1271,11 @@ static int KYTY_SYSV_ABI KernelStopUnloadModule(KernelModule handle, size_t args
|
||||
|
||||
if (program == nullptr) {
|
||||
LOGF("\tinvalid module handle = %" PRId32 "\n", handle);
|
||||
return KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
|
||||
if (program->load_count > 1) {
|
||||
--program->load_count;
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
#include "libs/network.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -163,6 +165,16 @@ int KYTY_SYSV_ABI NetSetsockopt(int s, int level, int optname, const void* optva
|
||||
return FinishSocketCall(Net::Setsockopt(s, level, optname, optval, optlen));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSend(int s, const void* buf, size_t len, int flags) {
|
||||
const auto size = std::min<size_t>(len, std::numeric_limits<int>::max());
|
||||
return FinishSocketCall(static_cast<int>(Net::Send(s, buf, size, flags | 0x20000)));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetRecv(int s, void* buf, size_t len, int flags) {
|
||||
const auto size = std::min<size_t>(len, std::numeric_limits<int>::max());
|
||||
return FinishSocketCall(static_cast<int>(Net::Recv(s, buf, size, flags)));
|
||||
}
|
||||
|
||||
uint32_t KYTY_SYSV_ABI NetHtonl(uint32_t host32) {
|
||||
return ((host32 & 0x000000ffu) << 24u) | ((host32 & 0x0000ff00u) << 8u) |
|
||||
((host32 & 0x00ff0000u) >> 8u) | ((host32 & 0xff000000u) >> 24u);
|
||||
@@ -207,6 +219,8 @@ LIB_DEFINE(InitNet_1_Net) {
|
||||
LIB_FUNC("Q4qBuN-c0ZM", LibNet::NetSocket);
|
||||
LIB_FUNC("45ggEzakPJQ", LibNet::NetSocketClose);
|
||||
LIB_FUNC("2mKX2Spso7I", LibNet::NetSetsockopt);
|
||||
LIB_FUNC("beRjXBn-z+o", LibNet::NetSend);
|
||||
LIB_FUNC("9wO9XrMsNhc", LibNet::NetRecv);
|
||||
LIB_FUNC("9T2pDF2Ryqg", LibNet::NetHtonl);
|
||||
LIB_FUNC("iWQWrwiSt8A", LibNet::NetHtons);
|
||||
LIB_FUNC("pQGpHYopAIY", LibNet::NetNtohl);
|
||||
|
||||
@@ -15,6 +15,8 @@ LIB_DEFINE(InitVideoOut_1) {
|
||||
|
||||
LIB_FUNC("Up36PTk687E", VideoOut::VideoOutOpen);
|
||||
LIB_FUNC("uquVH4-Du78", VideoOut::VideoOutClose);
|
||||
LIB_FUNC("WkYtyOg30do", VideoOut::VideoOutSetFlipMaster);
|
||||
LIB_FUNC("MBlv8YWoyOk", VideoOut::VideoOutAddBufferAttributeOption);
|
||||
LIB_FUNC("PjS5uASwcV8", VideoOut::VideoOutSetBufferAttribute2);
|
||||
LIB_FUNC("rKBUtgRrtbk", VideoOut::VideoOutRegisterBuffers2);
|
||||
LIB_FUNC("HuViW4HnrOw", VideoOut::VideoOutSubmitChangeBufferAttribute2);
|
||||
|
||||
+14
-9
@@ -51,15 +51,7 @@
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define PRINT_NAME() \
|
||||
if (PRINT_NAME_ENABLED) { \
|
||||
if (Log::GetDirection() != Log::Direction::Silent) { \
|
||||
const auto elapsed_ms = static_cast<uint64_t>(Loader::Timer::GetTimeMs()); \
|
||||
const auto print_name_time = fmt::format( \
|
||||
"{:02}:{:02}:{:02}.{:03}", elapsed_ms / 3600000, (elapsed_ms / 60000) % 60, \
|
||||
(elapsed_ms / 1000) % 60, elapsed_ms % 1000); \
|
||||
LOGF_COLOR(Log::Color::Cyan, "[%d][%s] %s::%s::%s()\n", \
|
||||
Common::Thread::GetThreadIdUnique(), print_name_time.c_str(), g_library, \
|
||||
g_module, __func__); \
|
||||
} \
|
||||
Libs::PrintName(g_library, g_module, __func__); \
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
@@ -68,6 +60,19 @@ class SymbolDatabase;
|
||||
|
||||
namespace Libs {
|
||||
|
||||
// Keep the formatting path from inflating fiber functions' stack frames under LTO.
|
||||
[[gnu::noinline]] inline void PrintName(const char* library, const char* module, const char* function) {
|
||||
if (Log::GetDirection() != Log::Direction::Silent) {
|
||||
const auto elapsed_ms = static_cast<uint64_t>(Loader::Timer::GetTimeMs());
|
||||
const auto print_name_time = fmt::format(
|
||||
"{:02}:{:02}:{:02}.{:03}", elapsed_ms / 3600000, (elapsed_ms / 60000) % 60,
|
||||
(elapsed_ms / 1000) % 60, elapsed_ms % 1000);
|
||||
LOGF_COLOR(Log::Color::Cyan, "[%d][%s] %s::%s::%s()\n",
|
||||
Common::Thread::GetThreadIdUnique(), print_name_time.c_str(), library, module,
|
||||
function);
|
||||
}
|
||||
}
|
||||
|
||||
void InitAll(Loader::SymbolDatabase* s);
|
||||
|
||||
} // namespace Libs
|
||||
|
||||
@@ -1003,6 +1003,7 @@ static int ConvertHostSocketError(int error) {
|
||||
case ENOBUFS: posix_error = Posix::POSIX_ENOBUFS; break;
|
||||
case ENOMEM: posix_error = Posix::POSIX_ENOMEM; break;
|
||||
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
|
||||
case EPIPE: posix_error = Posix::POSIX_EPIPE; break;
|
||||
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
+597
-168
File diff suppressed because it is too large.
Load diff
@@ -7,53 +7,6 @@ namespace Loader::Jit {
|
||||
|
||||
#pragma pack(1)
|
||||
|
||||
struct JmpWithIndex {
|
||||
void SetIndex(uint32_t index) { *reinterpret_cast<uint32_t*>(&code[1]) = index; }
|
||||
|
||||
void SetFunc(void* handler) {
|
||||
auto func_addr = reinterpret_cast<int64_t>(handler);
|
||||
auto rip_addr = reinterpret_cast<int64_t>(&code[10]);
|
||||
auto offset64 = func_addr - rip_addr;
|
||||
auto offset32 = static_cast<uint32_t>(static_cast<uint64_t>(offset64) & 0xffffffffu);
|
||||
|
||||
*reinterpret_cast<uint32_t*>(&code[6]) = offset32;
|
||||
}
|
||||
|
||||
static uint64_t GetSize() { return 16; }
|
||||
|
||||
// 68 00 00 00 00 push <index>
|
||||
// E9 E0 FF FF FF jmp <handler>
|
||||
uint8_t code[16] = {0x68, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00,
|
||||
0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
|
||||
};
|
||||
|
||||
struct CallPlt {
|
||||
explicit CallPlt(uint32_t table_size) {
|
||||
for (uint32_t index = 0; index < table_size; index++) {
|
||||
auto* c = new (&code[32] + JmpWithIndex::GetSize() * index) JmpWithIndex;
|
||||
c->SetIndex(index);
|
||||
c->SetFunc(this);
|
||||
}
|
||||
}
|
||||
|
||||
void SetPltGot(uint64_t vaddr) { *reinterpret_cast<uint64_t*>(&code[2]) = vaddr; }
|
||||
|
||||
uint64_t GetAddr(uint32_t index) {
|
||||
return reinterpret_cast<uint64_t>(&code[32] + JmpWithIndex::GetSize() * index);
|
||||
}
|
||||
|
||||
static uint64_t GetSize(uint32_t table_size) {
|
||||
return 32 + JmpWithIndex::GetSize() * table_size;
|
||||
}
|
||||
|
||||
// 0: 49 bb 88 77 66 55 44 movabs r11,0x1122334455667788
|
||||
// 7: 33 22 11
|
||||
// a: 41 ff 73 08 push QWORD PTR [r11+0x8]
|
||||
// e: 41 ff 63 10 jmp QWORD PTR [r11+0x10]
|
||||
uint8_t code[32] = {0x49, 0xBB, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x41, 0xFF,
|
||||
0x73, 0x08, 0x41, 0xFF, 0x63, 0x10, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
|
||||
};
|
||||
|
||||
struct JmpRax {
|
||||
template <class Handler>
|
||||
void SetFunc(Handler func) {
|
||||
|
||||
+122
-375
@@ -126,19 +126,13 @@ struct StubbedImportRecord {
|
||||
std::string program;
|
||||
};
|
||||
|
||||
// The structure will be passed via the stack
|
||||
// since the size of an object is larger than 16 bytes
|
||||
struct RelocateHandlerStack {
|
||||
uint64_t stack[3];
|
||||
};
|
||||
|
||||
static std::vector<StubbedImportRecord> g_stubbed_imports;
|
||||
static std::atomic_uint32_t g_unresolved_stub_call_log_count {0};
|
||||
static std::vector<uint64_t> g_unresolved_stub_thunk_pages;
|
||||
static uint64_t g_unresolved_stub_thunk_offset = 0;
|
||||
static constexpr uint64_t UNRESOLVED_STUB_PAGE_SIZE = 4096;
|
||||
|
||||
static KYTY_SYSV_ABI uint64_t ResolveImportStubWithId(uint64_t record_id);
|
||||
static KYTY_SYSV_ABI uint64_t UnresolvedImportStub(uint64_t record_id);
|
||||
|
||||
static bool PatchGuestMemory64(uint64_t vaddr, uint64_t value) {
|
||||
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
|
||||
@@ -148,7 +142,7 @@ static bool PatchGuestMemory64(uint64_t vaddr, uint64_t value) {
|
||||
}
|
||||
|
||||
static uint64_t AllocateUnresolvedImportThunk(uint64_t record_id) {
|
||||
constexpr uint64_t thunk_size = 165;
|
||||
constexpr uint64_t thunk_size = 34;
|
||||
|
||||
if (g_unresolved_stub_thunk_pages.empty() ||
|
||||
g_unresolved_stub_thunk_offset + thunk_size > UNRESOLVED_STUB_PAGE_SIZE) {
|
||||
@@ -164,105 +158,18 @@ static uint64_t AllocateUnresolvedImportThunk(uint64_t record_id) {
|
||||
g_unresolved_stub_thunk_offset);
|
||||
g_unresolved_stub_thunk_offset += thunk_size;
|
||||
|
||||
const auto target = reinterpret_cast<uint64_t>(ResolveImportStubWithId);
|
||||
uint8_t bytes[thunk_size] {};
|
||||
size_t i = 0;
|
||||
const auto emit = [&](uint8_t b) { bytes[i++] = b; };
|
||||
const auto emit64 = [&](uint64_t v) {
|
||||
std::memcpy(bytes + i, &v, sizeof(v));
|
||||
i += sizeof(v);
|
||||
uint8_t bytes[thunk_size] = {
|
||||
0x48, 0x83, 0xec, 0x08, // sub rsp, 8
|
||||
0x48, 0xbf, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdi, record_id
|
||||
0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, UnresolvedImportStub
|
||||
0xff, 0xd0, // call rax
|
||||
0x48, 0x83, 0xc4, 0x08, // add rsp, 8
|
||||
0x0f, 0x57, 0xc0, // xorps xmm0, xmm0
|
||||
0xc3, // ret
|
||||
};
|
||||
const auto emit32 = [&](uint32_t v) {
|
||||
std::memcpy(bytes + i, &v, sizeof(v));
|
||||
i += sizeof(v);
|
||||
};
|
||||
const auto save_xmm = [&](uint8_t reg, uint8_t offset) {
|
||||
emit(0xf3);
|
||||
emit(0x0f);
|
||||
emit(0x7f);
|
||||
if (offset == 0) {
|
||||
emit(static_cast<uint8_t>(0x04u | (reg << 3u)));
|
||||
emit(0x24);
|
||||
} else {
|
||||
emit(static_cast<uint8_t>(0x44u | (reg << 3u)));
|
||||
emit(0x24);
|
||||
emit(offset);
|
||||
}
|
||||
};
|
||||
const auto load_xmm = [&](uint8_t reg, uint8_t offset) {
|
||||
emit(0xf3);
|
||||
emit(0x0f);
|
||||
emit(0x6f);
|
||||
if (offset == 0) {
|
||||
emit(static_cast<uint8_t>(0x04u | (reg << 3u)));
|
||||
emit(0x24);
|
||||
} else {
|
||||
emit(static_cast<uint8_t>(0x44u | (reg << 3u)));
|
||||
emit(0x24);
|
||||
emit(offset);
|
||||
}
|
||||
};
|
||||
|
||||
emit(0x50); // push rax; preserve AL for variadic SysV calls
|
||||
emit(0x57); // push rdi
|
||||
emit(0x56); // push rsi
|
||||
emit(0x52); // push rdx
|
||||
emit(0x51); // push rcx
|
||||
emit(0x41);
|
||||
emit(0x50); // push r8
|
||||
emit(0x41);
|
||||
emit(0x51); // push r9
|
||||
emit(0x48);
|
||||
emit(0x81);
|
||||
emit(0xec);
|
||||
emit32(0x80); // sub rsp, 0x80
|
||||
for (uint8_t reg = 0; reg < 8; reg++) {
|
||||
save_xmm(reg, static_cast<uint8_t>(reg * 0x10u));
|
||||
}
|
||||
emit(0x48);
|
||||
emit(0xbf);
|
||||
emit64(record_id); // mov rdi, record_id
|
||||
emit(0x48);
|
||||
emit(0xb8);
|
||||
emit64(target); // mov rax, ResolveImportStubWithId
|
||||
emit(0xff);
|
||||
emit(0xd0); // call rax
|
||||
emit(0x49);
|
||||
emit(0x89);
|
||||
emit(0xc3); // mov r11, rax
|
||||
for (uint8_t reg = 0; reg < 8; reg++) {
|
||||
load_xmm(reg, static_cast<uint8_t>(reg * 0x10u));
|
||||
}
|
||||
emit(0x48);
|
||||
emit(0x81);
|
||||
emit(0xc4);
|
||||
emit32(0x80); // add rsp, 0x80
|
||||
emit(0x41);
|
||||
emit(0x59); // pop r9
|
||||
emit(0x41);
|
||||
emit(0x58); // pop r8
|
||||
emit(0x59); // pop rcx
|
||||
emit(0x5a); // pop rdx
|
||||
emit(0x5e); // pop rsi
|
||||
emit(0x5f); // pop rdi
|
||||
emit(0x58); // pop rax
|
||||
emit(0x4d);
|
||||
emit(0x85);
|
||||
emit(0xdb); // test r11, r11
|
||||
emit(0x74);
|
||||
emit(0x03); // jz +3
|
||||
emit(0x41);
|
||||
emit(0xff);
|
||||
emit(0xe3); // jmp r11
|
||||
// Match the integer fallback for floating-point return values.
|
||||
emit(0x0f);
|
||||
emit(0x57);
|
||||
emit(0xc0); // xorps xmm0, xmm0
|
||||
emit(0x31);
|
||||
emit(0xc0); // xor eax, eax
|
||||
emit(0xc3); // ret
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(i != thunk_size);
|
||||
const auto target = reinterpret_cast<uint64_t>(UnresolvedImportStub);
|
||||
std::memcpy(bytes + 6, &record_id, sizeof(record_id));
|
||||
std::memcpy(bytes + 16, &target, sizeof(target));
|
||||
std::memcpy(code, bytes, sizeof(bytes));
|
||||
Common::VirtualMemory::FlushInstructionCache(reinterpret_cast<uint64_t>(code), thunk_size);
|
||||
return reinterpret_cast<uint64_t>(code);
|
||||
@@ -270,15 +177,8 @@ static uint64_t AllocateUnresolvedImportThunk(uint64_t record_id) {
|
||||
|
||||
static uint64_t RegisterStubbedImport(uint32_t index, const Program* program,
|
||||
const RelocationInfo& ri) {
|
||||
const auto program_name = program != nullptr ? Common::PathToString(program->file_name) : "";
|
||||
|
||||
for (auto& record: g_stubbed_imports) {
|
||||
for (const auto& record: g_stubbed_imports) {
|
||||
if (record.patch_vaddr == ri.vaddr) {
|
||||
record.index = index;
|
||||
record.name = ri.name;
|
||||
record.type = ri.type;
|
||||
record.bind = ri.bind;
|
||||
record.program = program_name;
|
||||
return record.thunk_vaddr;
|
||||
}
|
||||
}
|
||||
@@ -289,7 +189,7 @@ static uint64_t RegisterStubbedImport(uint32_t index, const Program* program,
|
||||
record.name = ri.name;
|
||||
record.type = ri.type;
|
||||
record.bind = ri.bind;
|
||||
record.program = program_name;
|
||||
record.program = Common::PathToString(program->file_name);
|
||||
g_stubbed_imports.push_back(record);
|
||||
const auto record_id = g_stubbed_imports.size() - 1;
|
||||
const auto thunk = AllocateUnresolvedImportThunk(record_id);
|
||||
@@ -297,31 +197,7 @@ static uint64_t RegisterStubbedImport(uint32_t index, const Program* program,
|
||||
return thunk;
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI uint64_t ResolveImportStubWithId(uint64_t record_id) {
|
||||
if (record_id < g_stubbed_imports.size()) {
|
||||
auto& record = g_stubbed_imports[record_id];
|
||||
auto nid = record.name;
|
||||
auto pos = nid.find('[');
|
||||
if (pos != std::string::npos) {
|
||||
nid.resize(pos);
|
||||
}
|
||||
|
||||
SymbolRecord resolved {};
|
||||
if (!nid.empty() &&
|
||||
Common::Singleton<RuntimeLinker>::Instance()->ResolveLoadedSymbolByNid(nid, record.type,
|
||||
&resolved) &&
|
||||
resolved.vaddr != 0 && resolved.vaddr != record.thunk_vaddr) {
|
||||
LOGF("Late-resolved import: %s -> %s [0x%016" PRIx64 "]\n", record.name.c_str(),
|
||||
resolved.name.c_str(), resolved.vaddr);
|
||||
|
||||
if (record.patch_vaddr != 0) {
|
||||
PatchGuestMemory64(record.patch_vaddr, resolved.vaddr);
|
||||
}
|
||||
|
||||
return resolved.vaddr;
|
||||
}
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI uint64_t UnresolvedImportStub(uint64_t record_id) {
|
||||
const auto log_index = g_unresolved_stub_call_log_count.fetch_add(1);
|
||||
if (log_index < 1024) {
|
||||
if (record_id < g_stubbed_imports.size()) {
|
||||
@@ -1007,135 +883,56 @@ static RelocationInfo GetRelocationInfo(Elf64_Rela* r, Program* program) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void RelocateRecord(uint32_t index, Elf64_Rela* r, Program* program, bool jmprela_table,
|
||||
bool imports_only, std::vector<std::string>* unresolved) {
|
||||
static bool RelocateRecord(uint32_t index, Elf64_Rela* r, Program* program, bool jmprela_table) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
auto ri = GetRelocationInfo(r, program);
|
||||
|
||||
if (imports_only &&
|
||||
(ri.bind_self || (ri.bind != BindType::Global && ri.bind != BindType::Weak))) {
|
||||
return;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool patched = false;
|
||||
bool stubbed_import = false;
|
||||
bool stubbed_func = false;
|
||||
|
||||
// KYTY_PROFILER_BLOCK("patch");
|
||||
|
||||
if (ri.resolved) {
|
||||
patched = PatchGuestMemory64(ri.vaddr, ri.value);
|
||||
} else {
|
||||
uint64_t value = 0;
|
||||
bool weak = (ri.bind == BindType::Weak || !program->fail_if_global_not_resolved);
|
||||
if (ri.type == SymbolType::Object && weak) {
|
||||
const auto ri = GetRelocationInfo(r, program);
|
||||
auto value = ri.value;
|
||||
bool stubbed = false;
|
||||
if (!ri.resolved) {
|
||||
const bool weak = ri.bind == BindType::Weak || !program->fail_if_global_not_resolved;
|
||||
if (!weak) {
|
||||
LOGF("Stubbed: %s\n",
|
||||
fmt::format("[{:016x}] <- {:016x}, {}, {}, {}, {}", ri.vaddr, ri.value, ri.name,
|
||||
magic_enum::enum_name(ri.type), magic_enum::enum_name(ri.bind),
|
||||
ri.dbg_name).c_str());
|
||||
}
|
||||
if (ri.type == SymbolType::Object) {
|
||||
value = g_invalid_memory;
|
||||
} else if (ri.type == SymbolType::Func && jmprela_table && weak) {
|
||||
value = RegisterStubbedImport(index, program, ri);
|
||||
stubbed_import = true;
|
||||
stubbed_func = true;
|
||||
} else if (ri.type == SymbolType::Func && !jmprela_table && weak) {
|
||||
value = RegisterStubbedImport(index, program, ri);
|
||||
stubbed_func = true;
|
||||
} else if (ri.type == SymbolType::NoType && weak) {
|
||||
value = RuntimeLinker::ReadFromElf(program, ri.vaddr) + ri.base_vaddr;
|
||||
}
|
||||
|
||||
if (value != 0) {
|
||||
patched = PatchGuestMemory64(ri.vaddr, value);
|
||||
} else {
|
||||
auto dbg_str = fmt::format("[{:016x}] <- {:016x}, {}, {}, {}, {}", ri.vaddr, ri.value,
|
||||
ri.name.c_str(), magic_enum::enum_name(ri.type),
|
||||
magic_enum::enum_name(ri.bind), ri.dbg_name.c_str());
|
||||
|
||||
if (unresolved != nullptr) {
|
||||
unresolved->push_back(dbg_str);
|
||||
} else {
|
||||
EXIT("Can't resolve: %s\n", dbg_str.c_str());
|
||||
}
|
||||
|
||||
if (ri.type == SymbolType::Object) {
|
||||
value = g_invalid_memory;
|
||||
} else if (ri.type == SymbolType::Func || ri.type == SymbolType::NoType) {
|
||||
value = RegisterStubbedImport(index, program, ri);
|
||||
stubbed_func = true;
|
||||
if (jmprela_table) {
|
||||
stubbed_import = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != 0) {
|
||||
patched = PatchGuestMemory64(ri.vaddr, value);
|
||||
}
|
||||
value = RegisterStubbedImport(index, program, ri);
|
||||
stubbed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// KYTY_PROFILER_END_BLOCK;
|
||||
|
||||
if (patched && stubbed_import) {
|
||||
const auto thunk = RegisterStubbedImport(index, program, ri);
|
||||
LOGF("Relocate: unresolved PLT import patched to stub [%u] [%016" PRIx64 "] <- %016" PRIx64
|
||||
const bool patched = PatchGuestMemory64(ri.vaddr, value);
|
||||
if (patched && stubbed) {
|
||||
LOGF("Relocate: unresolved %s import patched to stub [%u] [%016" PRIx64 "] <- %016" PRIx64
|
||||
", %s, %s, %s, %s\n",
|
||||
index, ri.vaddr, thunk, ri.name.c_str(), magic_enum::enum_name(ri.type),
|
||||
magic_enum::enum_name(ri.bind), Common::PathToString(program->file_name).c_str());
|
||||
} else if (patched && stubbed_func) {
|
||||
const auto thunk = RegisterStubbedImport(index, program, ri);
|
||||
LOGF("Relocate: unresolved non-PLT function patched to stub [%u] [%016" PRIx64
|
||||
"] <- %016" PRIx64 ", %s, %s, %s, %s\n",
|
||||
index, ri.vaddr, thunk, ri.name.c_str(), magic_enum::enum_name(ri.type),
|
||||
magic_enum::enum_name(ri.bind), Common::PathToString(program->file_name).c_str());
|
||||
jmprela_table ? "PLT" : "non-PLT", index, ri.vaddr, value, ri.name.c_str(),
|
||||
magic_enum::enum_name(ri.type), magic_enum::enum_name(ri.bind),
|
||||
Common::PathToString(program->file_name).c_str());
|
||||
}
|
||||
|
||||
if (program->dbg_print_reloc) {
|
||||
if (patched && !ri.bind_self &&
|
||||
(ri.bind == BindType::Global || ri.bind == BindType::Weak ||
|
||||
ri.type == SymbolType::TlsModule)) {
|
||||
auto dbg_str = fmt::format("[{:016x}] <- {:016x}, {}, {}, {}, {}", ri.vaddr, ri.value,
|
||||
ri.name.c_str(), magic_enum::enum_name(ri.type),
|
||||
magic_enum::enum_name(ri.bind), ri.dbg_name.c_str());
|
||||
|
||||
LOGF("Relocate: %s\n", dbg_str.c_str());
|
||||
}
|
||||
if (program->dbg_print_reloc && patched && !ri.bind_self &&
|
||||
(ri.bind == BindType::Global || ri.bind == BindType::Weak ||
|
||||
ri.type == SymbolType::TlsModule)) {
|
||||
LOGF("Relocate: %s\n",
|
||||
fmt::format("[{:016x}] <- {:016x}, {}, {}, {}, {}", ri.vaddr, ri.value, ri.name,
|
||||
magic_enum::enum_name(ri.type), magic_enum::enum_name(ri.bind),
|
||||
ri.dbg_name).c_str());
|
||||
}
|
||||
return ri.resolved;
|
||||
}
|
||||
|
||||
static void RelocateRecords(Elf64_Rela* records, uint64_t size, Program* program,
|
||||
bool jmprela_table, bool imports_only,
|
||||
std::vector<std::string>* unresolved) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
uint32_t index = 0;
|
||||
for (auto* r = records;
|
||||
reinterpret_cast<uint8_t*>(r) < reinterpret_cast<uint8_t*>(records) + size; r++, index++) {
|
||||
RelocateRecord(index, r, program, jmprela_table, imports_only, unresolved);
|
||||
static void ForEachRelocation(Program* program, auto&& func) {
|
||||
const auto& info = *program->dynamic_info;
|
||||
for (uint32_t i = 0; i < info.rela_table_total_size / sizeof(Elf64_Rela); ++i) {
|
||||
func(info.rela_table + i, i, false);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((naked)) static KYTY_SYSV_ABI void RelocateHandlerReturnStub() {
|
||||
asm volatile("addq $8, %rsp\n\t"
|
||||
"retq\n");
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI uint64_t RelocateHandler(RelocateHandlerStack s) {
|
||||
auto* stack = s.stack;
|
||||
auto* program = reinterpret_cast<Program*>(stack[-1]);
|
||||
auto rel_index = stack[0];
|
||||
std::string name = "<unknown function>";
|
||||
|
||||
if (program != nullptr && program->dynamic_info != nullptr &&
|
||||
program->dynamic_info->jmprela_table != nullptr) {
|
||||
auto ri = GetRelocationInfo(program->dynamic_info->jmprela_table + rel_index, program);
|
||||
|
||||
name = ri.name.c_str();
|
||||
for (uint32_t i = 0; i < info.jmprela_table_size / sizeof(Elf64_Rela); ++i) {
|
||||
func(info.jmprela_table + i, i, true);
|
||||
}
|
||||
|
||||
// Restore return address (for stack trace)
|
||||
stack[-1] = reinterpret_cast<uint64_t>(RelocateHandlerReturnStub);
|
||||
|
||||
LOGF("=== Stubbed function, returning OK ===\n[%d]\t%s\n", Common::Thread::GetThreadIdUnique(),
|
||||
name.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static KYTY_MS_ABI uint8_t* TlsMainGetAddr() {
|
||||
@@ -1314,8 +1111,6 @@ void RuntimeLinker::RelocateAll() {
|
||||
for (auto* p: m_programs) {
|
||||
Relocate(p);
|
||||
}
|
||||
|
||||
m_relocated = true;
|
||||
}
|
||||
|
||||
void RuntimeLinker::RelocateProgram(Program* program) {
|
||||
@@ -1335,17 +1130,52 @@ void RuntimeLinker::UnloadProgram(Program* program) {
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (auto it = std::find(m_programs.begin(), m_programs.end(), program);
|
||||
it != m_programs.end()) {
|
||||
DeleteProgram(*it);
|
||||
m_programs.erase(it);
|
||||
} else {
|
||||
EXIT("program not found");
|
||||
}
|
||||
auto it = std::find(m_programs.begin(), m_programs.end(), program);
|
||||
EXIT_IF(it == m_programs.end());
|
||||
m_programs.erase(it);
|
||||
|
||||
if (m_relocated) {
|
||||
RelocateAll();
|
||||
for (auto* importer: m_programs) {
|
||||
const auto& info = *importer->dynamic_info;
|
||||
const auto rela_count =
|
||||
static_cast<uint32_t>(info.rela_table_total_size / sizeof(Elf64_Rela));
|
||||
ForEachRelocation(importer, [&](Elf64_Rela* r, uint32_t index, bool jmprela) {
|
||||
const auto bit_index = (jmprela ? rela_count : 0) + index;
|
||||
auto& bits = importer->rela_bits[bit_index >> 3u];
|
||||
const auto mask = 1u << (bit_index & 7u);
|
||||
const auto type = r->GetType();
|
||||
if ((bits & mask) == 0 || (type != R_X86_64_64 && type != R_X86_64_GLOB_DAT &&
|
||||
type != R_X86_64_JUMP_SLOT)) {
|
||||
return;
|
||||
}
|
||||
const auto& symbol = info.symbol_table[r->GetSymbol()];
|
||||
if (symbol.GetBind() != STB_GLOBAL && symbol.GetBind() != STB_WEAK) {
|
||||
return;
|
||||
}
|
||||
const auto value =
|
||||
*reinterpret_cast<const uint64_t*>(importer->base_vaddr + r->r_offset);
|
||||
const auto address = value - (type == R_X86_64_64 ? r->r_addend : 0);
|
||||
if (address < program->base_vaddr ||
|
||||
address >= program->base_vaddr + program->mapped_size) {
|
||||
return;
|
||||
}
|
||||
SymbolType symbol_type;
|
||||
switch (symbol.GetType()) {
|
||||
case STT_NOTYPE: symbol_type = SymbolType::NoType; break;
|
||||
case STT_FUNC: symbol_type = SymbolType::Func; break;
|
||||
case STT_OBJECT: symbol_type = SymbolType::Object; break;
|
||||
default: return;
|
||||
}
|
||||
const std::string name = info.str_table + symbol.st_name;
|
||||
if (program->export_symbols->FindByNid(name.substr(0, name.find('#')), symbol_type,
|
||||
address) != nullptr) {
|
||||
bits &= ~mask;
|
||||
if (RelocateRecord(index, r, importer, jmprela)) {
|
||||
bits |= mask;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
DeleteProgram(program);
|
||||
}
|
||||
|
||||
RuntimeLinker::RuntimeLinker(): m_symbols(std::make_unique<SymbolDatabase>()) {
|
||||
@@ -1499,7 +1329,6 @@ void RuntimeLinker::Clear() {
|
||||
g_tls_cached_main_tcb = nullptr;
|
||||
g_desired_base_addr = SYSTEM_RESERVED + CODE_BASE_OFFSET;
|
||||
m_symbols.reset();
|
||||
m_relocated = false;
|
||||
}
|
||||
|
||||
void RuntimeLinker::Resolve(const std::string& name, SymbolType type, Program* program,
|
||||
@@ -1559,10 +1388,20 @@ void RuntimeLinker::Resolve(const std::string& name, SymbolType type, Program* p
|
||||
}
|
||||
|
||||
if (rec == nullptr) {
|
||||
if (auto* p = FindProgram(*m, *l); p != nullptr && p->export_symbols != nullptr) {
|
||||
for (auto* p: m_programs) {
|
||||
const auto& libs = p->dynamic_info->export_libs;
|
||||
const auto& modules = p->dynamic_info->export_modules;
|
||||
if (p->export_symbols == nullptr ||
|
||||
std::find(libs.begin(), libs.end(), *l) == libs.end() ||
|
||||
std::find(modules.begin(), modules.end(), *m) == modules.end()) {
|
||||
continue;
|
||||
}
|
||||
rec = p->export_symbols->Find(sr);
|
||||
if (bind_self != nullptr) {
|
||||
*bind_self = (p == program);
|
||||
if (rec != nullptr) {
|
||||
if (bind_self != nullptr) {
|
||||
*bind_self = (p == program);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1575,7 +1414,6 @@ void RuntimeLinker::Resolve(const std::string& name, SymbolType type, Program* p
|
||||
}
|
||||
|
||||
if (rec != nullptr) {
|
||||
//*out_vaddr = rec->vaddr;
|
||||
*out_info = *rec;
|
||||
} else {
|
||||
out_info->vaddr = 0;
|
||||
@@ -1598,33 +1436,6 @@ void RuntimeLinker::Resolve(const std::string& name, SymbolType type, Program* p
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLinker::ResolveLoadedSymbolByNid(const std::string& nid, SymbolType type,
|
||||
SymbolRecord* out_info) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
EXIT_IF(out_info == nullptr);
|
||||
|
||||
for (auto* p: m_programs) {
|
||||
if (p != nullptr && p->export_symbols != nullptr) {
|
||||
if (const auto* rec = p->export_symbols->FindByNid(nid, type); rec != nullptr) {
|
||||
*out_info = *rec;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_symbols != nullptr) {
|
||||
if (const auto* rec = m_symbols->FindByNid(nid, type); rec != nullptr) {
|
||||
*out_info = *rec;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t RuntimeLinker::ReadFromElf(Program* program, uint64_t vaddr) {
|
||||
EXIT_IF(program == nullptr);
|
||||
EXIT_IF(program->base_vaddr == 0 || program->base_size == 0);
|
||||
@@ -2208,11 +2019,6 @@ void RuntimeLinker::DeleteProgram(Program* p) {
|
||||
EXIT_IF(
|
||||
!Libs::LibKernel::Memory::FreeGuestMemory(program->base_vaddr, program->mapped_size));
|
||||
}
|
||||
|
||||
if (program->custom_call_plt_vaddr != 0 || program->custom_call_plt_num != 0) {
|
||||
const auto size = Jit::CallPlt::GetSize(program->custom_call_plt_num);
|
||||
EXIT_IF(!Libs::LibKernel::Memory::FreeGuestMemory(program->custom_call_plt_vaddr, size));
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeLinker::ParseProgramDynamicInfo(Program* program) {
|
||||
@@ -2255,10 +2061,6 @@ void RuntimeLinker::ParseProgramDynamicInfo(Program* program) {
|
||||
GetDynValue(elf, &program->dynamic_info->fini_array_size, DT_FINI_ARRAYSZ);
|
||||
GetDynValue(elf, &program->dynamic_info->preinit_array_size, DT_PREINIT_ARRAYSZ);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(elf->HasDynValue(DT_OS_PLTGOT) && elf->HasDynValue(DT_PLTGOT));
|
||||
GetDynPtr(elf, &program->dynamic_info->pltgot_vaddr, DT_OS_PLTGOT);
|
||||
GetDynPtr(elf, &program->dynamic_info->pltgot_vaddr, DT_PLTGOT);
|
||||
|
||||
Elf64_Sxword jmprel_type = 0;
|
||||
EXIT_NOT_IMPLEMENTED(elf->HasDynValue(DT_OS_PLTREL) && elf->HasDynValue(DT_PLTREL));
|
||||
GetDynValue(elf, &jmprel_type, DT_OS_PLTREL);
|
||||
@@ -2281,6 +2083,11 @@ void RuntimeLinker::ParseProgramDynamicInfo(Program* program) {
|
||||
GetDynValue(elf, &program->dynamic_info->rela_table_total_size, DT_RELASZ);
|
||||
GetDynValue(elf, &program->dynamic_info->rela_table_entry_size, DT_OS_RELAENT);
|
||||
GetDynValue(elf, &program->dynamic_info->rela_table_entry_size, DT_RELAENT);
|
||||
EXIT_NOT_IMPLEMENTED(program->dynamic_info->rela_table_total_size % sizeof(Elf64_Rela) != 0 ||
|
||||
program->dynamic_info->jmprela_table_size % sizeof(Elf64_Rela) != 0);
|
||||
const auto rela_count = program->dynamic_info->rela_table_total_size / sizeof(Elf64_Rela);
|
||||
const auto jmprela_count = program->dynamic_info->jmprela_table_size / sizeof(Elf64_Rela);
|
||||
program->rela_bits.resize((rela_count + jmprela_count + 7u) / 8u);
|
||||
|
||||
GetDynValue(elf, &program->dynamic_info->relative_count, DT_RELACOUNT);
|
||||
|
||||
@@ -2327,43 +2134,6 @@ void RuntimeLinker::ParseProgramDynamicInfo(Program* program) {
|
||||
DT_OS_EXPORT_LIB_1);
|
||||
}
|
||||
|
||||
static void InstallRelocateHandler(Program* program) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
uint64_t pltgot_vaddr = program->dynamic_info->pltgot_vaddr + program->base_vaddr;
|
||||
uint64_t pltgot_size = static_cast<uint64_t>(3) * 8;
|
||||
void** pltgot = reinterpret_cast<void**>(pltgot_vaddr);
|
||||
|
||||
Common::VirtualMemory::Mode old_mode {};
|
||||
EXIT_IF(!Libs::LibKernel::Memory::ProtectGuestMemory(
|
||||
pltgot_vaddr, pltgot_size, Common::VirtualMemory::Mode::Write, &old_mode));
|
||||
|
||||
pltgot[1] = program;
|
||||
pltgot[2] = reinterpret_cast<void*>(RelocateHandler);
|
||||
|
||||
EXIT_IF(!Libs::LibKernel::Memory::ProtectGuestMemory(pltgot_vaddr, pltgot_size, old_mode));
|
||||
|
||||
if (Common::VirtualMemory::IsExecute(old_mode)) {
|
||||
Common::VirtualMemory::FlushInstructionCache(pltgot_vaddr, pltgot_size);
|
||||
}
|
||||
|
||||
// TODO(): check if this table already generated by compiler (sometimes it is missing)
|
||||
if (program->custom_call_plt_vaddr == 0) {
|
||||
program->custom_call_plt_num =
|
||||
program->dynamic_info->jmprela_table_size / sizeof(Elf64_Rela);
|
||||
auto size = Jit::CallPlt::GetSize(program->custom_call_plt_num);
|
||||
program->custom_call_plt_vaddr = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
||||
SYSTEM_RESERVED, size, Common::VirtualMemory::Mode::Write, "custom_call_plt");
|
||||
EXIT_NOT_IMPLEMENTED(program->custom_call_plt_vaddr == 0);
|
||||
auto* code = new (reinterpret_cast<void*>(program->custom_call_plt_vaddr))
|
||||
Jit::CallPlt(program->custom_call_plt_num);
|
||||
code->SetPltGot(pltgot_vaddr);
|
||||
EXIT_IF(!Libs::LibKernel::Memory::ProtectGuestMemory(program->custom_call_plt_vaddr, size,
|
||||
Common::VirtualMemory::Mode::Execute));
|
||||
Common::VirtualMemory::FlushInstructionCache(program->custom_call_plt_vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeLinker::Relocate(Program* program) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -2383,46 +2153,23 @@ void RuntimeLinker::Relocate(Program* program) {
|
||||
EXIT_NOT_IMPLEMENTED(program->dynamic_info->jmprela_table == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(program->dynamic_info->rela_table == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(program->dynamic_info->symbol_table == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(program->dynamic_info->pltgot_vaddr == 0);
|
||||
|
||||
InstallRelocateHandler(program);
|
||||
const auto rela_count = static_cast<uint32_t>(
|
||||
program->dynamic_info->rela_table_total_size / sizeof(Elf64_Rela));
|
||||
|
||||
std::vector<std::string> unresolved;
|
||||
const bool imports_only = program->relocated;
|
||||
|
||||
RelocateRecords(program->dynamic_info->rela_table, program->dynamic_info->rela_table_total_size,
|
||||
program, false, imports_only, &unresolved);
|
||||
RelocateRecords(program->dynamic_info->jmprela_table, program->dynamic_info->jmprela_table_size,
|
||||
program, true, imports_only, &unresolved);
|
||||
program->relocated = true;
|
||||
ForEachRelocation(program, [&](Elf64_Rela* r, uint32_t index, bool jmprela) {
|
||||
const auto bit_index = (jmprela ? rela_count : 0) + index;
|
||||
if ((program->rela_bits[bit_index >> 3u] & (1u << (bit_index & 7u))) == 0 &&
|
||||
RelocateRecord(index, r, program, jmprela)) {
|
||||
program->rela_bits[bit_index >> 3u] |= 1u << (bit_index & 7u);
|
||||
}
|
||||
});
|
||||
|
||||
if (program->tls.image_vaddr != 0 && program->tls.init_size != 0 &&
|
||||
program->tls.init_image.empty()) {
|
||||
const auto* src = reinterpret_cast<const uint8_t*>(program->tls.image_vaddr);
|
||||
program->tls.init_image.assign(src, src + program->tls.init_size);
|
||||
}
|
||||
|
||||
if (!unresolved.empty()) {
|
||||
LOGF("--- Stubbed unresolved imports: %zu ---\n", unresolved.size());
|
||||
for (const auto& symbol: unresolved) {
|
||||
LOGF("Stubbed: %s\n", symbol.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Program* RuntimeLinker::FindProgram(const ModuleId& m, const LibraryId& l) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (auto* p: m_programs) {
|
||||
const auto& export_libs = p->dynamic_info->export_libs;
|
||||
const auto& export_modules = p->dynamic_info->export_modules;
|
||||
|
||||
if (std::find(export_libs.begin(), export_libs.end(), l) != export_libs.end() &&
|
||||
std::find(export_modules.begin(), export_modules.end(), m) != export_modules.end()) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ModuleId* RuntimeLinker::FindModule(const Program& program, const std::string& id) {
|
||||
|
||||
@@ -86,7 +86,6 @@ struct DynamicInfo {
|
||||
uint64_t init_array_size = 0;
|
||||
uint64_t fini_array_size = 0;
|
||||
uint64_t preinit_array_size = 0;
|
||||
uint64_t pltgot_vaddr = 0;
|
||||
|
||||
Elf64_Rela* jmprela_table = nullptr;
|
||||
uint64_t jmprela_table_size = 0;
|
||||
@@ -116,6 +115,7 @@ struct Program {
|
||||
|
||||
int32_t unique_id = -1;
|
||||
RuntimeLinker* rt = nullptr;
|
||||
uint32_t load_count = 0; // Successful sceKernelLoadStartModule calls.
|
||||
std::filesystem::path file_name;
|
||||
std::unique_ptr<Elf64> elf;
|
||||
std::unique_ptr<DynamicInfo> dynamic_info;
|
||||
@@ -132,10 +132,8 @@ struct Program {
|
||||
ThreadLocalStorage tls;
|
||||
bool fail_if_global_not_resolved = true;
|
||||
bool dbg_print_reloc = false;
|
||||
bool relocated = false;
|
||||
std::vector<uint8_t> rela_bits;
|
||||
uint64_t proc_param_vaddr = 0;
|
||||
uint64_t custom_call_plt_vaddr = 0;
|
||||
uint32_t custom_call_plt_num = 0;
|
||||
};
|
||||
|
||||
class RuntimeLinker {
|
||||
@@ -171,7 +169,6 @@ public:
|
||||
|
||||
void Resolve(const std::string& name, SymbolType type, Program* program, SymbolRecord* out_info,
|
||||
bool* bind_self);
|
||||
bool ResolveLoadedSymbolByNid(const std::string& nid, SymbolType type, SymbolRecord* out_info);
|
||||
|
||||
SymbolDatabase* Symbols() { return m_symbols.get(); }
|
||||
|
||||
@@ -194,14 +191,11 @@ private:
|
||||
static void SetupTlsHandler(Program* program);
|
||||
void PreloadAdjacentPrograms();
|
||||
|
||||
Program* FindProgram(const ModuleId& m, const LibraryId& l);
|
||||
|
||||
static const ModuleId* FindModule(const Program& program, const std::string& id);
|
||||
static const LibraryId* FindLibrary(const Program& program, const std::string& id);
|
||||
|
||||
std::vector<Program*> m_programs;
|
||||
std::unique_ptr<SymbolDatabase> m_symbols;
|
||||
bool m_relocated = false;
|
||||
Common::Mutex m_mutex;
|
||||
|
||||
application_heap_malloc_func_t m_application_heap_malloc = nullptr;
|
||||
|
||||
@@ -65,12 +65,14 @@ const SymbolRecord* SymbolDatabase::Find(const SymbolResolve& s) const {
|
||||
return &m_symbols[index];
|
||||
}
|
||||
|
||||
const SymbolRecord* SymbolDatabase::FindByNid(const std::string& nid, SymbolType type) const {
|
||||
const SymbolRecord* SymbolDatabase::FindByNid(const std::string& nid, SymbolType type,
|
||||
uint64_t vaddr) const {
|
||||
auto prefix = nid + "[";
|
||||
auto suffix = fmt::format("[{}]", magic_enum::enum_name(type));
|
||||
|
||||
for (const auto& symbol: m_symbols) {
|
||||
if (symbol.name.starts_with(prefix) && symbol.name.ends_with(suffix)) {
|
||||
if ((vaddr == 0 || symbol.vaddr == vaddr) && symbol.name.starts_with(prefix) &&
|
||||
symbol.name.ends_with(suffix)) {
|
||||
return &symbol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ public:
|
||||
void Add(const SymbolResolve& s, uint64_t vaddr, const std::string& dbg_name);
|
||||
|
||||
[[nodiscard]] const SymbolRecord* Find(const SymbolResolve& s) const;
|
||||
[[nodiscard]] const SymbolRecord* FindByNid(const std::string& nid, SymbolType type) const;
|
||||
[[nodiscard]] const SymbolRecord* FindByNid(const std::string& nid, SymbolType type,
|
||||
uint64_t vaddr = 0) const;
|
||||
[[nodiscard]] const SymbolRecord* FindByName(const std::string& name, SymbolType type) const;
|
||||
|
||||
void DbgDump(const std::string& folder, const std::string& file_name);
|
||||
|
||||
+38
-6
@@ -133,6 +133,26 @@ static bool ParseConsoleLanguage(const std::string& value, uint32_t& out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseUint32(const std::string& value, uint32_t& out) {
|
||||
uint32_t number = 0;
|
||||
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
|
||||
if (error != std::errc {} || end != value.data() + value.size()) {
|
||||
return false;
|
||||
}
|
||||
out = number;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseInt32(const std::string& value, int32_t& out) {
|
||||
int32_t number = 0;
|
||||
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
|
||||
if (error != std::errc {} || end != value.data() + value.size()) {
|
||||
return false;
|
||||
}
|
||||
out = number;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseUserId(const std::string& value, int32_t& out) {
|
||||
int32_t user_id = 0;
|
||||
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), user_id);
|
||||
@@ -246,9 +266,17 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
}
|
||||
options.game_patch = path;
|
||||
} else if (arg == "--screen-width") {
|
||||
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
|
||||
if (!ParseUint32(value, options.config.screen_width) ||
|
||||
options.config.screen_width == 0) {
|
||||
::printf("invalid screen width: %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--screen-height") {
|
||||
options.config.screen_height = static_cast<uint32_t>(Common::ToInt32(value));
|
||||
if (!ParseUint32(value, options.config.screen_height) ||
|
||||
options.config.screen_height == 0) {
|
||||
::printf("invalid screen height: %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--user-name") {
|
||||
if (value.empty() || value.size() > Config::MAX_USER_NAME_LENGTH) {
|
||||
::printf("invalid user name: must contain 1-%zu bytes\n",
|
||||
@@ -269,11 +297,15 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--gpu") {
|
||||
options.config.gpu_index = Common::ToInt32(value);
|
||||
if (!ParseInt32(value, options.config.gpu_index)) {
|
||||
::printf("invalid gpu index: %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--vblank-frequency") {
|
||||
const int32_t vblank_frequency = Common::ToInt32(value);
|
||||
options.config.vblank_frequency =
|
||||
static_cast<uint32_t>(vblank_frequency < 0 ? 0 : vblank_frequency);
|
||||
if (!ParseUint32(value, options.config.vblank_frequency)) {
|
||||
::printf("invalid vblank frequency: %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--console-language") {
|
||||
if (!ParseConsoleLanguage(value, options.config.console_language)) {
|
||||
::printf("invalid console language: %s\n", value.c_str());
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@@ -16,14 +17,16 @@ namespace {
|
||||
|
||||
namespace AudioOut2 = Libs::Audio::AudioOut2;
|
||||
|
||||
std::mutex g_device_mutex;
|
||||
std::condition_variable g_device_cv;
|
||||
std::vector<int> g_live_devices;
|
||||
std::vector<int> g_device_backed_handles;
|
||||
std::vector<bool> g_output_blocking;
|
||||
int g_next_device = 1;
|
||||
int g_open_waiters = 0;
|
||||
bool g_block_opens = false;
|
||||
std::mutex g_device_mutex;
|
||||
std::condition_variable g_device_cv;
|
||||
std::vector<int> g_live_devices;
|
||||
std::vector<int> g_device_backed_handles;
|
||||
std::vector<bool> g_output_blocking;
|
||||
std::vector<std::vector<uint8_t>> g_output_pcm;
|
||||
size_t g_capture_bytes = 0;
|
||||
int g_next_device = 1;
|
||||
int g_open_waiters = 0;
|
||||
bool g_block_opens = false;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
@@ -146,6 +149,17 @@ std::vector<bool> OutputCalls() {
|
||||
return g_output_blocking;
|
||||
}
|
||||
|
||||
void CaptureOutputPcm(size_t bytes) {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_capture_bytes = bytes;
|
||||
g_output_pcm.clear();
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint8_t>> OutputPcm() {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
return g_output_pcm;
|
||||
}
|
||||
|
||||
void TestSlotReuse() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
@@ -327,6 +341,41 @@ void TestHandleWithoutPcmDoesNotBypassQueue() {
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
void TestPcmCopiedBeforeScratchBufferReuse() {
|
||||
const auto context = CreateContext();
|
||||
const auto param = MakeParam();
|
||||
AudioOut2::AudioOut2PortHandle first = 0;
|
||||
AudioOut2::AudioOut2PortHandle second = 0;
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &first) == OK,
|
||||
"first port create failed");
|
||||
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(¶m), &second) == OK,
|
||||
"second port create failed");
|
||||
|
||||
std::vector<float> scratch(512 * 2);
|
||||
std::vector<float> first_pcm(scratch.size(), 0.25f);
|
||||
std::vector<float> second_pcm(scratch.size(), -0.5f);
|
||||
std::copy(first_pcm.begin(), first_pcm.end(), scratch.begin());
|
||||
SetPcm(first, scratch.data());
|
||||
std::copy(second_pcm.begin(), second_pcm.end(), scratch.begin());
|
||||
SetPcm(second, scratch.data());
|
||||
std::fill(scratch.begin(), scratch.end(), 0.0f);
|
||||
|
||||
const auto pcm_bytes = scratch.size() * sizeof(float);
|
||||
CaptureOutputPcm(pcm_bytes);
|
||||
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "shared-buffer push failed");
|
||||
const auto output = OutputPcm();
|
||||
Check(output.size() == 2, "shared-buffer push did not output both ports");
|
||||
Check(std::memcmp(output[0].data(), first_pcm.data(), pcm_bytes) == 0,
|
||||
"first port lost PCM when scratch buffer was reused");
|
||||
Check(std::memcmp(output[1].data(), second_pcm.data(), pcm_bytes) == 0,
|
||||
"second port lost PCM when scratch buffer was reused");
|
||||
|
||||
CaptureOutputPcm(0);
|
||||
AudioOut2::AudioOut2PortDestroy(first);
|
||||
AudioOut2::AudioOut2PortDestroy(second);
|
||||
AudioOut2::AudioOut2ContextDestroy(context);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::Audio::AudioInternal {
|
||||
@@ -363,9 +412,15 @@ bool AudioOutHasDevice(int handle) {
|
||||
g_device_backed_handles.end();
|
||||
}
|
||||
|
||||
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool blocking) {
|
||||
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
|
||||
std::lock_guard lock(g_device_mutex);
|
||||
g_output_blocking.push_back(blocking);
|
||||
if (g_capture_bytes != 0) {
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(params[i].data);
|
||||
g_output_pcm.emplace_back(bytes, bytes + g_capture_bytes);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -389,6 +444,7 @@ int main() {
|
||||
TestFloat12ChannelPortOutputsPcm();
|
||||
TestAsynchronousDevicePushKeepsQueueBounded();
|
||||
TestHandleWithoutPcmDoesNotBypassQueue();
|
||||
TestPcmCopiedBeforeScratchBufferReuse();
|
||||
std::printf("AudioOut2PortTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <array>
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -31,6 +32,10 @@ namespace Libs::LibKernelApr {
|
||||
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
|
||||
}
|
||||
|
||||
namespace Libs::LibNet {
|
||||
void InitNet_1_Net(Loader::SymbolDatabase *symbols);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
namespace FileSystem = Libs::LibKernel::FileSystem;
|
||||
@@ -407,6 +412,22 @@ void CheckAprPaths(const std::filesystem::path &root) {
|
||||
|
||||
void CheckSocketWakeup() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::LibNet::InitNet_1_Net(&symbols);
|
||||
const auto *send_symbol = symbols.Find(
|
||||
{"beRjXBn-z+o", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *recv_symbol = symbols.Find(
|
||||
{"9wO9XrMsNhc", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *errno_symbol = symbols.Find(
|
||||
{"HQOwnfMGipQ", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
Check(send_symbol && recv_symbol && errno_symbol,
|
||||
"Net send, receive and errno exports resolve with the guest ABI versions");
|
||||
using Send = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int);
|
||||
using Recv = int (KYTY_SYSV_ABI *)(int, void *, size_t, int);
|
||||
using Errno = int *(KYTY_SYSV_ABI *)();
|
||||
const auto net_send = reinterpret_cast<Send>(send_symbol->vaddr);
|
||||
const auto net_recv = reinterpret_cast<Recv>(recv_symbol->vaddr);
|
||||
auto *net_errno = reinterpret_cast<Errno>(errno_symbol->vaddr)();
|
||||
// Guest sockaddr_in: length, family, network-order port/address, padding.
|
||||
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
|
||||
const int listener = Net::Socket(2, 1, 0);
|
||||
@@ -441,26 +462,61 @@ void CheckSocketWakeup() {
|
||||
immediate.data()) == 0 && readable[reader / 64] == 0,
|
||||
"empty socket is not readable");
|
||||
const char payload[] = "wake";
|
||||
Check(Net::Send(writer, payload, sizeof(payload), 0x20000) == sizeof(payload),
|
||||
"send wake bytes with guest MSG_NOSIGNAL");
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
Check(net_send(writer, payload, sizeof(payload), 0) == sizeof(payload) &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL,
|
||||
"Net send forwards bytes and preserves errno on success");
|
||||
readable[reader / 64] = bit;
|
||||
const std::array<int64_t, 2> deadline {1, 0};
|
||||
Check(Net::Select(reader + 1, readable.data(), nullptr, nullptr,
|
||||
deadline.data()) == 1 && readable[reader / 64] == bit,
|
||||
"select reports the guest descriptor after wake");
|
||||
std::array<char, sizeof(payload)> received {};
|
||||
Check(Net::Recv(reader, received.data(), received.size(), 0x42) == sizeof(payload) &&
|
||||
Check(net_recv(reader, received.data(), received.size(), 0x42) == sizeof(payload) &&
|
||||
std::memcmp(received.data(), payload, sizeof(payload)) == 0 &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL,
|
||||
"Net receive forwards PEEK and WAITALL without consuming bytes");
|
||||
received.fill(0);
|
||||
Check(net_recv(reader, received.data(), received.size(), 0x40) == sizeof(payload) &&
|
||||
std::memcmp(received.data(), payload, sizeof(payload)) == 0,
|
||||
"guest PEEK and WAITALL preserve the wake bytes");
|
||||
Check(Net::Recv(reader, received.data(), received.size(), 0x40) == sizeof(payload),
|
||||
"consume wake bytes with guest WAITALL");
|
||||
"Net receive consumes the same bytes after peeking");
|
||||
#if !defined(_WIN32)
|
||||
Check(Net::Recv(reader, received.data(), received.size(), 0x80) == -1 &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"empty nonblocking receive translates guest errno");
|
||||
Check(net_recv(reader, received.data(), received.size(), 0x80) ==
|
||||
Libs::Network::NET_ERROR_EWOULDBLOCK &&
|
||||
*net_errno == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"Net nonblocking receive translates POSIX failure and Net errno");
|
||||
#endif
|
||||
Check(Net::SocketClose(reader) == 0 && Net::SocketClose(writer) == 0,
|
||||
"close wake sockets");
|
||||
Check(net_send(-1, payload, sizeof(payload), 0) == Libs::Network::NET_ERROR_EBADF &&
|
||||
*net_errno == Libs::Posix::POSIX_EBADF,
|
||||
"Net send translates an invalid socket instead of returning POSIX minus one");
|
||||
Check(net_recv(reader, nullptr, received.size(), 0) == Libs::Network::NET_ERROR_EFAULT &&
|
||||
*net_errno == Libs::Posix::POSIX_EFAULT,
|
||||
"Net receive translates an invalid output buffer");
|
||||
Check(net_send(writer, payload, sizeof(payload), 0x100000) ==
|
||||
Libs::Network::NET_ERROR_EOPNOTSUPP &&
|
||||
*net_errno == Libs::Posix::POSIX_EOPNOTSUPP,
|
||||
"Net send preserves the backend's unsupported crypto flag error");
|
||||
#if defined(__linux__)
|
||||
const int disconnected = Net::Socket(2, 1, 0);
|
||||
Check(disconnected >= 0, "create unconnected socket for broken pipe check");
|
||||
const auto previous_sigpipe = std::signal(SIGPIPE, SIG_DFL);
|
||||
Check(previous_sigpipe != SIG_ERR, "set default SIGPIPE disposition for Net send");
|
||||
const auto broken_send = net_send(disconnected, payload, sizeof(payload), 0);
|
||||
std::signal(SIGPIPE, previous_sigpipe);
|
||||
Check(broken_send == Libs::Network::NET_ERROR_EPIPE &&
|
||||
*net_errno == Libs::Posix::POSIX_EPIPE,
|
||||
"Net send reports a broken pipe without raising host SIGPIPE");
|
||||
Check(Net::SocketClose(disconnected) == 0, "close unconnected socket");
|
||||
#endif
|
||||
Check(Net::SocketClose(writer) == 0, "close wake writer");
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
Check(net_recv(reader, received.data(), received.size(), 0) == 0 &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL,
|
||||
"Net receive returns EOF without replacing errno");
|
||||
Check(Net::SocketClose(reader) == 0, "close wake reader");
|
||||
readable[reader / 64] = bit;
|
||||
Check(Net::Select(reader + 1, readable.data(), nullptr, nullptr,
|
||||
immediate.data()) == -1 &&
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
#include "common/lruCache.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
void Check(bool value, const char *message) {
|
||||
if (!value) {
|
||||
std::fprintf(stderr, "LruCacheTests: failed: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the objects reachable through ForEachItemBelow(), in visit order.
|
||||
std::vector<std::string> Collect(Common::LeastRecentlyUsedCache<std::string, uint64_t> &cache,
|
||||
uint64_t tick) {
|
||||
std::vector<std::string> visited;
|
||||
cache.ForEachItemBelow(tick, [&](const std::string &object) { visited.push_back(object); });
|
||||
return visited;
|
||||
}
|
||||
|
||||
void TestInsertAndVisitOrder() {
|
||||
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
||||
const auto a = cache.Insert("a", 10);
|
||||
const auto b = cache.Insert("b", 20);
|
||||
const auto c = cache.Insert("c", 30);
|
||||
|
||||
Check(a != b && b != c && a != c, "Insert returned duplicate ids");
|
||||
|
||||
const auto all = Collect(cache, UINT64_MAX);
|
||||
Check(all.size() == 3 && all[0] == "a" && all[1] == "b" && all[2] == "c",
|
||||
"inserted items are not visited in insertion order");
|
||||
|
||||
const auto below = Collect(cache, 20);
|
||||
Check(below.size() == 2 && below[0] == "a" && below[1] == "b",
|
||||
"ForEachItemBelow visited an item above the tick");
|
||||
|
||||
const auto none = Collect(cache, 5);
|
||||
Check(none.empty(), "ForEachItemBelow visited items below an empty tick");
|
||||
}
|
||||
|
||||
void TestEarlyExitCallback() {
|
||||
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
||||
(void)cache.Insert("a", 10);
|
||||
(void)cache.Insert("b", 20);
|
||||
(void)cache.Insert("c", 30);
|
||||
|
||||
std::vector<std::string> visited;
|
||||
cache.ForEachItemBelow(UINT64_MAX, [&](const std::string &object) {
|
||||
visited.push_back(object);
|
||||
return true; // stop after the first item
|
||||
});
|
||||
Check(visited.size() == 1 && visited[0] == "a", "boolean callback did not stop the walk");
|
||||
|
||||
std::vector<std::string> all_visited;
|
||||
cache.ForEachItemBelow(UINT64_MAX, [&](const std::string &object) {
|
||||
all_visited.push_back(object);
|
||||
return false;
|
||||
});
|
||||
Check(all_visited.size() == 3, "false-returning callback did not visit every item");
|
||||
}
|
||||
|
||||
void TestTouchReordersAndSkips() {
|
||||
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
||||
const auto a = cache.Insert("a", 10);
|
||||
const auto b = cache.Insert("b", 20);
|
||||
const auto c = cache.Insert("c", 30);
|
||||
|
||||
// Touching the last item must not change the order.
|
||||
cache.Touch(c, 35);
|
||||
auto order = Collect(cache, UINT64_MAX);
|
||||
Check(order.size() == 3 && order[0] == "a" && order[1] == "b" && order[2] == "c",
|
||||
"touching the newest item changed the order");
|
||||
|
||||
// An older or equal tick must be ignored.
|
||||
cache.Touch(a, 10);
|
||||
cache.Touch(a, 5);
|
||||
order = Collect(cache, UINT64_MAX);
|
||||
Check(order[0] == "a" && order[1] == "b" && order[2] == "c",
|
||||
"an ignored touch changed the order");
|
||||
|
||||
// Touching the oldest item moves it to the newest end.
|
||||
cache.Touch(a, 40);
|
||||
order = Collect(cache, UINT64_MAX);
|
||||
Check(order.size() == 3 && order[0] == "b" && order[1] == "c" && order[2] == "a",
|
||||
"touching an item did not move it to the newest end");
|
||||
|
||||
// The touched item now carries its new tick for tick filtering
|
||||
// (a=40, b=20, c=35 after the touches above). A cutoff of 30 detects
|
||||
// a stale c tick: c is visited only if Touch(c, 35) updated it.
|
||||
const auto below = Collect(cache, 30);
|
||||
Check(below.size() == 1 && below[0] == "b",
|
||||
"touched item did not adopt its new tick");
|
||||
|
||||
// Touch the middle item.
|
||||
cache.Touch(b, 45);
|
||||
order = Collect(cache, UINT64_MAX);
|
||||
Check(order.size() == 3 && order[0] == "c" && order[1] == "a" && order[2] == "b",
|
||||
"touching a middle item did not move it to the end");
|
||||
}
|
||||
|
||||
void TestFreeAndIdReuse() {
|
||||
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
|
||||
const auto a = cache.Insert("a", 10);
|
||||
const auto b = cache.Insert("b", 20);
|
||||
const auto c = cache.Insert("c", 30);
|
||||
|
||||
// Free the first and the last items.
|
||||
cache.Free(a);
|
||||
cache.Free(c);
|
||||
auto order = Collect(cache, UINT64_MAX);
|
||||
Check(order.size() == 1 && order[0] == "b", "Free left a freed item in the walk");
|
||||
|
||||
// Freed ids must be reused by later inserts.
|
||||
const auto d = cache.Insert("d", 40);
|
||||
Check(d == a || d == c, "Insert did not reuse a freed id");
|
||||
const auto e = cache.Insert("e", 50);
|
||||
Check(e != d && (e == a || e == c), "Insert reused the same freed id twice");
|
||||
|
||||
order = Collect(cache, UINT64_MAX);
|
||||
Check(order.size() == 3 && order[0] == "b" && order[1] == "d" && order[2] == "e",
|
||||
"reused ids are not visited in tick order");
|
||||
|
||||
// Touching a reused id must not disturb the other items.
|
||||
cache.Touch(d, 60);
|
||||
order = Collect(cache, UINT64_MAX);
|
||||
Check(order.size() == 3 && order[0] == "b" && order[1] == "e" && order[2] == "d",
|
||||
"touching a reused id changed the wrong items");
|
||||
|
||||
// Free everything, then rebuild.
|
||||
cache.Free(b);
|
||||
cache.Free(d);
|
||||
cache.Free(e);
|
||||
Check(Collect(cache, UINT64_MAX).empty(), "freeing every item left items in the walk");
|
||||
|
||||
const auto again = cache.Insert("again", 70);
|
||||
const auto order2 = Collect(cache, UINT64_MAX);
|
||||
Check(order2.size() == 1 && order2[0] == "again", "insert after a full free failed");
|
||||
cache.Touch(again, 70);
|
||||
Check(Collect(cache, 70).size() == 1, "touching a rebuilt item failed");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestInsertAndVisitOrder();
|
||||
TestEarlyExitCallback();
|
||||
TestTouchReordersAndSkips();
|
||||
TestFreeAndIdReuse();
|
||||
std::puts("LruCacheTests: all cases passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,945 @@
|
||||
// Exercise sampler controls and rendering without a host audio device.
|
||||
#include "libs/ngs2.cpp"
|
||||
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <numbers>
|
||||
|
||||
using namespace Libs::Audio::Ngs2;
|
||||
|
||||
namespace {
|
||||
|
||||
void Check(bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "Ngs2SamplerTests: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
Ngs2Internal system;
|
||||
Ngs2RackInternal rack {};
|
||||
Ngs2VoiceInternal voice;
|
||||
|
||||
Fixture(uint32_t rate, uint32_t channels = 1, Ngs2RackType type = Ngs2RackType::Sampler) {
|
||||
system.option.sample_rate = 48000;
|
||||
rack.ngs = &system;
|
||||
rack.type = type;
|
||||
voice.rack = &rack;
|
||||
struct Setup {
|
||||
Ngs2VoiceParamHeader header {40, 0, 0x10000000};
|
||||
Ngs2WaveformFormat format;
|
||||
uint32_t flags = 0, reserved = 0;
|
||||
} setup;
|
||||
setup.header.id = type == Ngs2RackType::Sampler ? 0x10000000 : 0x40010000;
|
||||
setup.format = {0x12, channels, rate};
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&voice), &setup.header) == OK,
|
||||
"sampler setup failed");
|
||||
voice.state = Ngs2VoicePlayState::Playing;
|
||||
}
|
||||
|
||||
void Queue(const int16_t* data, uint32_t frames, uint32_t flags, uint32_t repeats = 0,
|
||||
uint32_t skip = 0) {
|
||||
Ngs2WaveformBlock block {
|
||||
0, size_t(frames + skip) * voice.channels * sizeof(int16_t), repeats, skip, frames, 0,
|
||||
123};
|
||||
struct Blocks {
|
||||
Ngs2VoiceParamHeader header {32, 0, 0x10000001};
|
||||
const void* data;
|
||||
uint32_t flags, count;
|
||||
const Ngs2WaveformBlock* blocks;
|
||||
} param {{32, 0, 0x10000001}, data, flags, 1, &block};
|
||||
param.header.id = rack.type == Ngs2RackType::Sampler ? 0x10000001 : 0x40010001;
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&voice), ¶m.header) == OK,
|
||||
"sampler queue failed");
|
||||
}
|
||||
|
||||
void Render(uint32_t frames) {
|
||||
voice.rendered = false;
|
||||
Ngs2RenderVoice(voice, {}, frames);
|
||||
}
|
||||
};
|
||||
|
||||
void TestStreamingResample() {
|
||||
Fixture f(44100, 2);
|
||||
std::vector<int16_t> pcm(441 * 2);
|
||||
for (int i = 0; i < 441; ++i) {
|
||||
pcm[i * 2] = static_cast<int16_t>(i * 32);
|
||||
pcm[i * 2 + 1] = static_cast<int16_t>(-i * 32);
|
||||
}
|
||||
f.Queue(pcm.data(), 200, 1);
|
||||
f.Queue(pcm.data() + 400, 241, 0);
|
||||
for (uint32_t grain = 0; grain < 3; ++grain) {
|
||||
f.Render(160);
|
||||
for (uint32_t i = 0; i < 160; ++i) {
|
||||
const float expected = std::min((grain * 160 + i) * 44100.0 / 48000.0, 440.0) / 1024.0;
|
||||
Check(std::abs(f.voice.samples[i] - expected) < 0.00001f,
|
||||
"resampling discontinuity across grain or block boundary");
|
||||
Check(std::abs(f.voice.samples[160 + i] + expected) < 0.00001f,
|
||||
"stereo channels were mixed");
|
||||
}
|
||||
}
|
||||
Check(f.voice.blocks.empty(), "final streaming block did not finish");
|
||||
Check(f.voice.state == Ngs2VoicePlayState::Empty, "finished voice remained active");
|
||||
Ngs2SamplerVoiceState state {};
|
||||
Ngs2VoiceGetState(reinterpret_cast<uintptr_t>(&f.voice), &state.voice_state, sizeof(state));
|
||||
Check(state.num_decoded_samples == 441 && state.decoded_data_size == pcm.size() * 2,
|
||||
"streaming progress does not match consumed source data");
|
||||
}
|
||||
|
||||
void TestAtrac9ResampleAcrossFrames() {
|
||||
const auto make_voice = [] {
|
||||
auto f = std::make_unique<Fixture>(24000);
|
||||
f->voice.SetupSampler({NGS2_WAVEFORM_TYPE_ATRAC9, 1, 24000, 0xfe4005f0});
|
||||
f->voice.state = Ngs2VoicePlayState::Playing;
|
||||
f->voice.decoded_frame[0] = 0.0f;
|
||||
f->voice.decoded_frame[1] = 0.5f;
|
||||
f->voice.frame_samples = 2;
|
||||
f->voice.next_decoded_frame.resize(f->voice.decoded_frame.size());
|
||||
f->voice.next_decoded_frame[0] = 1.0f;
|
||||
f->voice.next_decoded_frame[1] = 1.5f;
|
||||
f->voice.next_frame_samples = 2;
|
||||
f->voice.accepts_blocks = false;
|
||||
return f;
|
||||
};
|
||||
auto whole = make_voice();
|
||||
whole->Render(8);
|
||||
const std::vector<float> expected {0.0f, 0.25f, 0.5f, 0.75f,
|
||||
1.0f, 1.25f, 1.5f, 1.5f};
|
||||
Check(whole->voice.samples == expected && whole->voice.decoded_samples == 4 &&
|
||||
whole->voice.state == Ngs2VoicePlayState::Empty,
|
||||
"24 kHz ATRAC9 interpolation or source progress failed at frame boundary");
|
||||
auto split = make_voice();
|
||||
split->Render(3);
|
||||
const auto first = split->voice.samples;
|
||||
split->Render(5);
|
||||
std::vector<float> joined = first;
|
||||
joined.insert(joined.end(), split->voice.samples.begin(), split->voice.samples.end());
|
||||
Check(joined == expected && split->voice.decoded_samples == 4,
|
||||
"ATRAC9 resampling changed across render grains");
|
||||
auto pitched = make_voice();
|
||||
struct Pitch {
|
||||
Ngs2VoiceParamHeader header {16, 0, 0x10000005};
|
||||
float ratio = 2.0f;
|
||||
uint32_t reserved = 0;
|
||||
} pitch;
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&pitched->voice), &pitch.header) == OK,
|
||||
"ATRAC9 pitch control failed");
|
||||
pitched->Render(4);
|
||||
Check(pitched->voice.samples == std::vector<float> {0.0f, 0.5f, 1.0f, 1.5f} &&
|
||||
pitched->voice.decoded_samples == 4,
|
||||
"ATRAC9 pitch did not use source-rate phase advancement");
|
||||
}
|
||||
|
||||
void TestAtrac9ResampleAfterStarvation() {
|
||||
Fixture f(24000);
|
||||
f.voice.SetupSampler({NGS2_WAVEFORM_TYPE_ATRAC9, 1, 24000, 0xfe4005f0});
|
||||
f.voice.state = Ngs2VoicePlayState::Playing;
|
||||
f.voice.accepts_blocks = true;
|
||||
f.voice.decoded_frame[0] = 0.0f;
|
||||
f.voice.decoded_frame[1] = 0.5f;
|
||||
f.voice.frame_samples = 2;
|
||||
f.Render(4);
|
||||
Check(f.voice.samples == std::vector<float> {0.0f, 0.25f, 0.5f, 0.5f} &&
|
||||
f.voice.decoded_samples == 2,
|
||||
"ATRAC9 resampling lost the final sample before starvation");
|
||||
f.Render(2);
|
||||
Check(!f.voice.has_samples && f.voice.state == Ngs2VoicePlayState::Playing &&
|
||||
f.voice.decoded_samples == 2,
|
||||
"starved ATRAC9 stream consumed source samples");
|
||||
f.voice.next_decoded_frame.resize(f.voice.decoded_frame.size());
|
||||
f.voice.next_decoded_frame[0] = 1.0f;
|
||||
f.voice.next_decoded_frame[1] = 1.5f;
|
||||
f.voice.next_frame_samples = 2;
|
||||
f.Render(4);
|
||||
Check(f.voice.samples == std::vector<float> {1.0f, 1.25f, 1.5f, 1.5f} &&
|
||||
f.voice.decoded_samples == 4,
|
||||
"ATRAC9 refill lost source-rate phase");
|
||||
}
|
||||
|
||||
void TestWaveformBlockGeometry() {
|
||||
const Ngs2WaveformFormat format {NGS2_WAVEFORM_TYPE_ATRAC9, 1, 24000, 0xfe4005f0};
|
||||
Ngs2WaveformBlock block {};
|
||||
Check(Ngs2CalcWaveformBlock(&format, 0, 34534, &block) == OK &&
|
||||
block.data_offset == 0 && block.data_size == 13056 &&
|
||||
block.num_skip_samples == 0 && block.num_samples == 34534,
|
||||
"ATRAC9 block calculation omitted encoded superframes");
|
||||
Check(Ngs2CalcWaveformBlock(&format, 513, 1, &block) == OK &&
|
||||
block.data_offset == 192 && block.data_size == 192 &&
|
||||
block.num_skip_samples == 1 && block.num_samples == 1,
|
||||
"ATRAC9 block calculation lost the source offset");
|
||||
Check(Ngs2CalcWaveformBlock(&format, 0, 0, &block) == OK && block.data_size == 0 &&
|
||||
block.num_samples == 0,
|
||||
"empty ATRAC9 block acquired encoded data");
|
||||
Fixture f(24000);
|
||||
const int16_t silence = 0;
|
||||
f.Queue(&silence, 0, 0, UINT32_MAX);
|
||||
f.Render(1);
|
||||
Check(f.voice.blocks.empty() && f.voice.state == Ngs2VoicePlayState::Empty,
|
||||
"empty repeated block kept the sampler active");
|
||||
}
|
||||
|
||||
void TestSubmixerSetupAndRouting() {
|
||||
Fixture f(48000, 2);
|
||||
Ngs2RackInternal submixer_rack {};
|
||||
submixer_rack.ngs = &f.system;
|
||||
submixer_rack.type = Ngs2RackType::Submixer;
|
||||
submixer_rack.option.submixer.max_channels = 8;
|
||||
Ngs2VoiceInternal submixer;
|
||||
submixer.rack = &submixer_rack;
|
||||
submixer.ports.resize(1);
|
||||
submixer.matrices.resize(1);
|
||||
Ngs2RackInternal mastering_rack {};
|
||||
mastering_rack.type = Ngs2RackType::Mastering;
|
||||
Ngs2VoiceInternal mastering;
|
||||
mastering.rack = &mastering_rack;
|
||||
mastering.channels = 2;
|
||||
mastering.state = Ngs2VoicePlayState::Playing;
|
||||
submixer.state = Ngs2VoicePlayState::Playing;
|
||||
submixer.ports[0].dest = &mastering;
|
||||
submixer.matrices[0] = {1.0f};
|
||||
f.voice.ports.push_back({&submixer, 0, 1.0f, -1});
|
||||
const int16_t pcm[] = {8192, -8192, 16384, -16384};
|
||||
f.Queue(pcm, 2, 0);
|
||||
const std::vector<Ngs2VoiceInternal*> voices {&f.voice, &submixer, &mastering};
|
||||
Ngs2RenderVoice(mastering, voices, 1);
|
||||
Check(!submixer.has_samples && !mastering.has_samples && f.voice.decoded_samples == 0,
|
||||
"unconfigured zero-channel submixer claimed to produce samples");
|
||||
Ngs2SubmixerVoiceSetupParam setup {{16, 0, 0x20000000}, 2, 0};
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&submixer), &setup.header) == OK &&
|
||||
submixer.channels == 2 && submixer.state == Ngs2VoicePlayState::Empty &&
|
||||
submixer.ports[0].dest == nullptr && submixer.matrices[0].empty(),
|
||||
"submixer setup did not establish channels and clear old routing");
|
||||
submixer.state = Ngs2VoicePlayState::Playing;
|
||||
submixer.ports[0] = {&mastering, 0, 1.0f, 0};
|
||||
submixer.matrices[0] = {1.0f, 0.0f, 0.0f, 1.0f};
|
||||
for (float expected: {0.25f, 0.5f}) {
|
||||
for (auto* voice: voices) {
|
||||
voice->rendered = false;
|
||||
}
|
||||
Ngs2RenderVoice(mastering, voices, 1);
|
||||
Check(mastering.has_samples && mastering.samples[0] == expected &&
|
||||
mastering.samples[1] == -expected,
|
||||
"submixer lost routed samples across render grains");
|
||||
}
|
||||
}
|
||||
|
||||
void TestPcm16RenderOutput() {
|
||||
Ngs2Internal system;
|
||||
system.option = Ngs2DefaultSystemOption();
|
||||
system.option.num_grain_samples = 4;
|
||||
Ngs2RackOptionUnion sampler_option {}, mastering_option {};
|
||||
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
|
||||
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
|
||||
sampler_option.common.max_voices = 2;
|
||||
sampler_option.common.max_ports = 1;
|
||||
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
|
||||
std::byte sampler_storage[sizeof(Ngs2RackInternal) + 2 * sizeof(Ngs2VoiceInternal)];
|
||||
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
|
||||
std::byte mastering_storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
|
||||
Ngs2ContextBufferInfo sampler_buffer {sampler_storage, sizeof(sampler_storage)};
|
||||
Ngs2ContextBufferInfo mastering_buffer {mastering_storage, sizeof(mastering_storage)};
|
||||
uintptr_t sampler_handle = 0, mastering_handle = 0;
|
||||
const auto system_handle = reinterpret_cast<uintptr_t>(&system);
|
||||
Check(Ngs2RackCreate(system_handle, 0x1000, &sampler_option.common, &sampler_buffer,
|
||||
&sampler_handle) == OK &&
|
||||
Ngs2RackCreate(system_handle, 0x3000, &mastering_option.common,
|
||||
&mastering_buffer, &mastering_handle) == OK,
|
||||
"PCM16 render rack creation failed");
|
||||
auto* samplers = reinterpret_cast<Ngs2VoiceInternal*>(
|
||||
reinterpret_cast<Ngs2RackInternal*>(sampler_handle) + 1);
|
||||
auto& mastering = *reinterpret_cast<Ngs2VoiceInternal*>(
|
||||
reinterpret_cast<Ngs2RackInternal*>(mastering_handle) + 1);
|
||||
mastering.channels = 1;
|
||||
mastering.state = Ngs2VoicePlayState::Playing;
|
||||
const int16_t source[2][4] = {{16384, -16384, 32767, -32768},
|
||||
{8192, -8192, 32767, -32768}};
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
samplers[i].SetupSampler({0x12, 1, 48000});
|
||||
samplers[i].blocks.push_back(
|
||||
{reinterpret_cast<const uint8_t*>(source[i]), {0, sizeof(source[i]), 0, 0, 4}});
|
||||
samplers[i].ports[0].dest = &mastering;
|
||||
samplers[i].SetEvent(1);
|
||||
}
|
||||
int16_t output_samples[4] = {};
|
||||
Ngs2RenderBufferInfo output {output_samples, sizeof(output_samples), 0x12, 1};
|
||||
Check(Ngs2SystemRender(system_handle, &output, 1) == OK &&
|
||||
output_samples[0] == 24576 && output_samples[1] == -24576 &&
|
||||
output_samples[2] == 32767 && output_samples[3] == -32768,
|
||||
"PCM16 output failed to mix or saturate mastering voices");
|
||||
Check(Ngs2SystemRender(system_handle, &output, 1) == OK &&
|
||||
std::ranges::all_of(output_samples, [](int16_t sample) { return sample == 0; }),
|
||||
"PCM16 output retained samples from the previous render");
|
||||
Check(Ngs2RackDestroy(mastering_handle, nullptr) == OK &&
|
||||
Ngs2RackDestroy(sampler_handle, nullptr) == OK,
|
||||
"PCM16 render rack cleanup failed");
|
||||
}
|
||||
|
||||
void TestPitchLoopAndSkip() {
|
||||
Fixture f(24000);
|
||||
const int16_t pcm[] = {-30000, 8192, 16384};
|
||||
f.Queue(pcm, 2, 0, 1, 1);
|
||||
struct Pitch {
|
||||
Ngs2VoiceParamHeader header {16, 0, 0x10000005};
|
||||
float ratio = 2.0f;
|
||||
uint32_t reserved = 0;
|
||||
} pitch;
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), &pitch.header);
|
||||
f.Render(5);
|
||||
const float expected[] = {0.25f, 0.5f, 0.25f, 0.5f, 0.0f};
|
||||
for (size_t i = 0; i < 5; ++i) {
|
||||
Check(std::abs(f.voice.samples[i] - expected[i]) < 0.00001f,
|
||||
"pitch, repeat count, or skipped samples incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
void TestPitchClamps() {
|
||||
Fixture f(48000);
|
||||
const int16_t pcm[] = {8192, 0, 0, 0, -8192};
|
||||
f.Queue(pcm, 5, 0);
|
||||
struct Pitch {
|
||||
Ngs2VoiceParamHeader header {16, 0, 0x10000005};
|
||||
float ratio = 5.0f;
|
||||
uint32_t reserved = 0;
|
||||
} pitch;
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), &pitch.header) == OK,
|
||||
"out-of-range pitch was rejected");
|
||||
f.Render(2);
|
||||
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == -0.25f,
|
||||
"pitch was not clamped to four times playback speed");
|
||||
}
|
||||
|
||||
void TestStarvationAndPause() {
|
||||
Fixture f(48000);
|
||||
const int16_t pcm[] = {16384, 8192};
|
||||
f.Queue(pcm, 2, 1);
|
||||
f.Render(4);
|
||||
Check(f.voice.state == Ngs2VoicePlayState::Playing, "stream starvation stopped voice");
|
||||
Check(f.voice.samples[2] == 0.0f, "starved output was not silent");
|
||||
f.Queue(pcm, 2, 0);
|
||||
f.voice.SetEvent(16);
|
||||
f.Render(2);
|
||||
Check(!f.voice.has_samples && f.voice.blocks.front().cursor == 0, "paused voice consumed data");
|
||||
f.voice.SetEvent(32);
|
||||
f.Render(2);
|
||||
Check(f.voice.samples[0] == 0.5f && f.voice.samples[1] == 0.25f,
|
||||
"stream failed to resume after starvation/pause");
|
||||
}
|
||||
|
||||
void TestMonoRateAndRouting() {
|
||||
Fixture f(22050);
|
||||
std::vector<int16_t> pcm(441, 16384);
|
||||
f.Queue(pcm.data(), 441, 0);
|
||||
Ngs2RackInternal mastering_rack {};
|
||||
mastering_rack.type = Ngs2RackType::Mastering;
|
||||
Ngs2VoiceInternal mastering;
|
||||
mastering.rack = &mastering_rack;
|
||||
mastering.channels = 2;
|
||||
mastering.state = Ngs2VoicePlayState::Playing;
|
||||
f.voice.matrices = {{0.5f, 0.25f}};
|
||||
f.voice.ports.push_back({&mastering, 0, 1.0f, 0});
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
mastering.rendered = false;
|
||||
f.voice.rendered = false;
|
||||
Ngs2RenderVoice(mastering, {&f.voice, &mastering}, 320);
|
||||
Check(mastering.has_samples, "standard sampler did not reach mastering voice");
|
||||
for (uint32_t j = 0; j < 320; ++j) {
|
||||
Check(mastering.samples[j] == 0.25f && mastering.samples[320 + j] == 0.125f,
|
||||
"mono PCM routing matrix was not applied");
|
||||
}
|
||||
}
|
||||
Check(f.voice.decoded_samples == 441 && f.voice.blocks.empty(),
|
||||
"22.05 kHz effect duration was incorrect");
|
||||
}
|
||||
|
||||
void TestMatrixOutputChannels() {
|
||||
Fixture f(48000, 2);
|
||||
Ngs2RackInternal mastering_rack {};
|
||||
mastering_rack.type = Ngs2RackType::Mastering;
|
||||
Ngs2VoiceInternal mastering;
|
||||
mastering.rack = &mastering_rack;
|
||||
mastering.channels = 8;
|
||||
mastering.state = Ngs2VoicePlayState::Playing;
|
||||
f.voice.matrices = {{1.0f, 0.0f, 0.0f, 1.0f}};
|
||||
f.voice.ports.push_back({&mastering, 0, 1.0f, 0});
|
||||
const int16_t pcm[] = {8192, -8192};
|
||||
f.Queue(pcm, 1, 0);
|
||||
Ngs2RenderVoice(mastering, {&f.voice, &mastering}, 1);
|
||||
Check(mastering.samples[0] == 0.25f && mastering.samples[1] == -0.25f,
|
||||
"stereo matrix did not route its output channels");
|
||||
for (size_t channel = 2; channel < mastering.channels; ++channel) {
|
||||
Check(mastering.samples[channel] == 0.0f,
|
||||
"matrix routed audio to an unspecified output channel");
|
||||
}
|
||||
}
|
||||
|
||||
void TestCustomPcmStillPlays() {
|
||||
Fixture f(48000, 1, Ngs2RackType::CustomSampler);
|
||||
const int16_t pcm[] = {8192, -16384};
|
||||
f.Queue(pcm, 2, 0);
|
||||
f.Render(4);
|
||||
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == -0.5f &&
|
||||
f.voice.samples[2] == 0.0f && f.voice.blocks.empty(),
|
||||
"custom PCM sampler regressed");
|
||||
}
|
||||
|
||||
void TestSamplerReuseResetsRouting() {
|
||||
for (auto type: {Ngs2RackType::Sampler, Ngs2RackType::CustomSampler}) {
|
||||
Fixture f(48000, 1, type);
|
||||
Ngs2RackInternal mastering_rack {};
|
||||
mastering_rack.type = Ngs2RackType::Mastering;
|
||||
Ngs2VoiceInternal mastering;
|
||||
mastering.rack = &mastering_rack;
|
||||
mastering.channels = 2;
|
||||
mastering.state = Ngs2VoicePlayState::Playing;
|
||||
f.voice.ports.push_back({&mastering, 1, 0.25f, 0});
|
||||
f.voice.matrices = {{0.5f, 0.25f}};
|
||||
f.voice.SetupSampler({0x12, 2, 48000});
|
||||
Check(f.voice.ports.size() == 1 && f.voice.ports[0].dest == nullptr &&
|
||||
f.voice.ports[0].input == 0 && f.voice.ports[0].volume == 1.0f &&
|
||||
f.voice.ports[0].matrix == -1 && f.voice.matrices.size() == 1 &&
|
||||
f.voice.matrices[0].empty(),
|
||||
"sampler setup retained routing from the previous channel layout");
|
||||
const int16_t pcm[] = {8192, 16384, -8192, -16384};
|
||||
f.Queue(pcm, 2, 0);
|
||||
f.Render(1);
|
||||
Check(!f.voice.has_samples && f.voice.blocks.front().cursor == 0,
|
||||
"sampler setup started the new waveform without a play event");
|
||||
f.voice.ports[0].dest = &mastering;
|
||||
f.voice.SetEvent(1);
|
||||
f.voice.rendered = false;
|
||||
Ngs2RenderVoice(mastering, {&f.voice, &mastering}, 2);
|
||||
Check(mastering.samples == std::vector<float> {0.25f, -0.25f, 0.5f, -0.5f},
|
||||
"reused stereo sampler did not restore default channel routing");
|
||||
}
|
||||
}
|
||||
|
||||
void TestDeferredSamplerSetup() {
|
||||
Fixture f(48000);
|
||||
struct Setup {
|
||||
Ngs2VoiceParamHeader header {40, 0, 0x10000000};
|
||||
Ngs2WaveformFormat format;
|
||||
uint32_t flags = 0, reserved = 0;
|
||||
} setup;
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), &setup.header) == OK,
|
||||
"deferred stream setup failed");
|
||||
Check(f.voice.state == Ngs2VoicePlayState::Empty && f.voice.channels == 0 &&
|
||||
f.voice.sample_rate == 0 && f.voice.decoder == nullptr,
|
||||
"deferred stream setup did not clear the sampler");
|
||||
setup.format = {0x12, 1, 48000};
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), &setup.header) == OK,
|
||||
"sampler setup after deferred stream failed");
|
||||
const int16_t pcm = 8192;
|
||||
f.Queue(&pcm, 1, 0);
|
||||
f.voice.SetEvent(1);
|
||||
f.Render(1);
|
||||
Check(f.voice.samples[0] == 0.25f,
|
||||
"sampler did not play after deferred stream setup");
|
||||
}
|
||||
|
||||
void TestPlayStateBeforeRender() {
|
||||
Fixture f(48000);
|
||||
const int16_t pcm[] = {8192, -16384};
|
||||
f.voice.state = Ngs2VoicePlayState::Empty;
|
||||
f.Queue(pcm, 2, 0);
|
||||
Ngs2VoiceEventParam event {{sizeof(Ngs2VoiceEventParam), 0, 6}, 1};
|
||||
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
|
||||
Ngs2VoiceControl(handle, &event.header);
|
||||
uint32_t flags = 0;
|
||||
Ngs2VoiceGetStateFlags(handle, &flags);
|
||||
Check(flags == 1, "play must reserve the voice before rendering starts playback");
|
||||
Ngs2SamplerVoiceState state {};
|
||||
Ngs2VoiceGetState(handle, &state.voice_state, sizeof(state));
|
||||
Check(state.voice_state.state_flags == flags && state.num_decoded_samples == 0,
|
||||
"state queries disagreed or play consumed samples before render");
|
||||
f.Render(4);
|
||||
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == -0.5f,
|
||||
"one-shot effect was dropped before its first render");
|
||||
}
|
||||
|
||||
void TestStatePublication() {
|
||||
Ngs2Internal system;
|
||||
system.option = Ngs2DefaultSystemOption();
|
||||
Ngs2RackOptionUnion option {};
|
||||
Ngs2FillDefaultRackOption(0x1000, &option);
|
||||
option.common.max_voices = 1;
|
||||
alignas(Ngs2RackInternal) alignas(Ngs2VoiceInternal)
|
||||
std::byte storage[sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal)];
|
||||
Ngs2ContextBufferInfo buffer {storage, sizeof(storage)};
|
||||
uintptr_t rack_handle = 0;
|
||||
const auto system_handle = reinterpret_cast<uintptr_t>(&system);
|
||||
Check(Ngs2RackCreate(system_handle, 0x1000, &option.common, &buffer, &rack_handle) == OK,
|
||||
"state publication rack creation failed");
|
||||
auto& voice =
|
||||
*reinterpret_cast<Ngs2VoiceInternal*>(reinterpret_cast<Ngs2RackInternal*>(rack_handle) + 1);
|
||||
voice.SetupSampler({0x12, 1, 48000});
|
||||
const auto handle = reinterpret_cast<uintptr_t>(&voice);
|
||||
auto check_flags = [&](uint32_t expected) {
|
||||
uint32_t flags = 0;
|
||||
Ngs2SamplerVoiceState state {};
|
||||
Ngs2VoiceGetStateFlags(handle, &flags);
|
||||
Ngs2VoiceGetState(handle, &state.voice_state, sizeof(state));
|
||||
Check(flags == expected && state.voice_state.state_flags == expected,
|
||||
"voice state was published at the wrong render boundary");
|
||||
};
|
||||
struct Transition {
|
||||
uint32_t event, before_render, after_render;
|
||||
};
|
||||
const Transition transitions[] = {{1, 1, 3}, {16, 3, 5}, {32, 5, 3}, {2, 3, 0},
|
||||
{1, 1, 3}, {16, 3, 5}, {2, 5, 0}, {1, 1, 3},
|
||||
{4, 3, 0}, {1, 1, 3}, {8, 3, 0}};
|
||||
Ngs2RenderBufferInfo output {};
|
||||
for (const auto& transition: transitions) {
|
||||
Ngs2VoiceEventParam event {{sizeof(Ngs2VoiceEventParam), 0, 6}, transition.event};
|
||||
Ngs2VoiceControl(handle, &event.header);
|
||||
check_flags(transition.before_render);
|
||||
Ngs2SystemRender(system_handle, &output, 1);
|
||||
check_flags(transition.after_render);
|
||||
}
|
||||
Check(Ngs2RackDestroy(rack_handle, nullptr) == OK, "state publication rack cleanup failed");
|
||||
}
|
||||
|
||||
void TestStopThenPlayBeforeRender() {
|
||||
Fixture f(48000);
|
||||
const int16_t pcm[] = {16384, -8192};
|
||||
f.Queue(pcm, 2, 0);
|
||||
f.voice.SetEvent(2);
|
||||
f.voice.SetEvent(1);
|
||||
Check(Ngs2GetStateFlags(&f.voice) == 1, "stopped voice could not restart");
|
||||
f.voice.SetEvent(8);
|
||||
f.voice.SetEvent(1);
|
||||
Check(Ngs2GetStateFlags(&f.voice) == 1, "kill/play commands lost their ordering");
|
||||
f.Render(2);
|
||||
Check(f.voice.samples[0] == 0.5f && f.voice.samples[1] == -0.25f,
|
||||
"restarted voice produced no audio");
|
||||
}
|
||||
|
||||
void SetLowPass(Fixture& f, float frequency = 1000.0f, uint64_t mask = 0) {
|
||||
struct Param {
|
||||
Ngs2VoiceParamHeader header {56, 0, 0x1000000a};
|
||||
Ngs2SamplerFilterParam filter {0, 1, 1, 0, 1000.0f, 0.70710678f, 1.0f, {}};
|
||||
} param;
|
||||
param.filter.frequency = frequency;
|
||||
param.filter.channel_mask = mask;
|
||||
f.rack.option.sampler.max_filters = 8;
|
||||
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice), ¶m.header) == OK,
|
||||
"filter ABI command failed");
|
||||
}
|
||||
|
||||
void TestLowPassResponse() {
|
||||
// A second-order Butterworth LPF has unity DC gain and -3 dB at cutoff.
|
||||
for (double frequency: {100.0, 1000.0, 10000.0}) {
|
||||
Fixture f(48000);
|
||||
SetLowPass(f);
|
||||
std::vector<int16_t> pcm(48000);
|
||||
for (size_t i = 0; i < pcm.size(); ++i) {
|
||||
pcm[i] =
|
||||
static_cast<int16_t>(8192 * std::sin((2.0 * std::numbers::pi) * frequency * i / 48000));
|
||||
}
|
||||
f.Queue(pcm.data(), static_cast<uint32_t>(pcm.size()), 0);
|
||||
f.Render(static_cast<uint32_t>(pcm.size()));
|
||||
double input = 0, output = 0;
|
||||
for (size_t i = 24000; i < pcm.size(); ++i) {
|
||||
input += std::pow(pcm[i] / 32768.0, 2);
|
||||
output += std::pow(f.voice.samples[i], 2);
|
||||
}
|
||||
const double ratio = std::sqrt(output / input);
|
||||
if (frequency == 100) {
|
||||
Check(std::abs(ratio - 1.0) < 0.002, "low-pass lost bass");
|
||||
}
|
||||
if (frequency == 1000) {
|
||||
Check(std::abs(ratio - std::sqrt(0.5)) < 0.002, "cutoff gain incorrect");
|
||||
}
|
||||
if (frequency == 10000) {
|
||||
Check(ratio < 0.01, "low-pass did not reject high frequencies");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestFilterHistoryAndChannels() {
|
||||
Fixture whole(48000, 2), split(48000, 2);
|
||||
SetLowPass(whole, 1000, 1);
|
||||
SetLowPass(split, 1000, 1);
|
||||
std::vector<int16_t> pcm(256 * 2, 0);
|
||||
pcm[0] = pcm[1] = 16384;
|
||||
whole.Queue(pcm.data(), 256, 0);
|
||||
split.Queue(pcm.data(), 32, 1);
|
||||
split.Queue(pcm.data() + 64, 224, 0);
|
||||
whole.Render(256);
|
||||
for (uint32_t g = 0; g < 8; ++g) {
|
||||
// Games resend unchanged parameters; this must not reset the delay line.
|
||||
SetLowPass(split, 1000, 1);
|
||||
split.Render(32);
|
||||
for (uint32_t c = 0; c < 2; ++c) {
|
||||
for (uint32_t i = 0; i < 32; ++i) {
|
||||
Check(std::abs(split.voice.samples[c * 32 + i] -
|
||||
whole.voice.samples[c * 256 + g * 32 + i]) < 1e-7,
|
||||
"filter history changed at a grain/block/control boundary");
|
||||
}
|
||||
}
|
||||
}
|
||||
Check(whole.voice.samples[0] == 0.5f && whole.voice.samples[1] == 0.0f &&
|
||||
whole.voice.samples[256] > 0.0f && whole.voice.samples[256] < 0.5f,
|
||||
"channel mask must bypass selected channels and filter the others");
|
||||
Fixture stereo(48000, 2);
|
||||
SetLowPass(stereo);
|
||||
pcm[1] = 0;
|
||||
stereo.Queue(pcm.data(), 256, 0);
|
||||
stereo.Render(256);
|
||||
for (size_t i = 256; i < 512; ++i) {
|
||||
Check(stereo.voice.samples[i] == 0.0f, "filter leaked history between channels");
|
||||
}
|
||||
stereo.voice.SetupSampler({0x12, 2, 48000});
|
||||
Check(stereo.voice.filters.empty(), "reused voice retained old filters");
|
||||
}
|
||||
|
||||
void TestFilterUsesOutputRate() {
|
||||
Fixture f(22050);
|
||||
SetLowPass(f, 6833.0f);
|
||||
const auto& filter = f.voice.filters[0];
|
||||
// Rear filtering operates at the 48 kHz system rate, not the PCM source rate.
|
||||
const double w = (2.0 * std::numbers::pi) * 6833 / 48000;
|
||||
const double expected = (1 - std::cos(w)) / (2 * (1 + std::sin(w) / (2 * double(0.70710678f))));
|
||||
Check(std::abs(filter.b0 - expected) < 1e-10, "rear filter used source sample rate");
|
||||
SetLowPass(f, 48000);
|
||||
const int16_t pcm[] = {16384, 16384};
|
||||
f.Queue(pcm, 2, 0);
|
||||
f.Render(2);
|
||||
Check(f.voice.samples[0] == 0.5f && f.voice.samples[1] == 0.5f,
|
||||
"above-Nyquist cutoff was unstable");
|
||||
}
|
||||
|
||||
void TestFilterControlLayout() {
|
||||
Fixture f(48000, 2);
|
||||
f.rack.option.sampler.max_filters = 8;
|
||||
// A 56-byte control command with nonzero trailing padding.
|
||||
alignas(8) uint32_t words[] = {56, 0x1000000a, 0, 1, 1, 0, 0,
|
||||
0, 0x45d58800, 0x3fd55555, 0x3f800000, 0, 0, 0x13f};
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&f.voice),
|
||||
reinterpret_cast<const Ngs2VoiceParamHeader*>(words));
|
||||
Check(f.voice.filters.size() == 1 && f.voice.filters[0].enabled,
|
||||
"FCQ filter command was not recognized");
|
||||
const double omega = (2.0 * std::numbers::pi) * 6833.0 / 48000.0;
|
||||
const double q = static_cast<double>(std::bit_cast<float>(0x3fd55555u));
|
||||
const double expected = (1 - std::cos(omega)) / (2 * (1 + std::sin(omega) / (2 * q)));
|
||||
Check(std::abs(f.voice.filters[0].b0 - expected) < 1e-10,
|
||||
"FCQ fields were read at the wrong offsets");
|
||||
words[6] = 1; // Bypass channel 0 in the low half of the 64-bit mask.
|
||||
words[7] = 1; // Bypass channel 32 in the high half.
|
||||
Fixture wide(48000, 33);
|
||||
wide.rack.option.sampler.max_filters = 8;
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&wide.voice),
|
||||
reinterpret_cast<const Ngs2VoiceParamHeader*>(words));
|
||||
const std::vector<int16_t> pcm(33, 16384);
|
||||
wide.Queue(pcm.data(), 1, 1);
|
||||
wide.Render(1);
|
||||
Check(wide.voice.samples[0] == 0.5f && wide.voice.samples[32] == 0.5f &&
|
||||
std::abs(wide.voice.samples[1] - expected * 0.5) < 1e-7,
|
||||
"channel mask was not decoded as a 64-bit bypass mask");
|
||||
words[4] = 0; // OFF ignores unused FCQ values and clears the old delay line.
|
||||
words[8] = 0x7fc00000;
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&wide.voice),
|
||||
reinterpret_cast<const Ngs2VoiceParamHeader*>(words));
|
||||
Check(!wide.voice.filters[0].enabled && wide.voice.filters[0].history.empty() &&
|
||||
!wide.voice.filters[0].warned,
|
||||
"filter OFF was treated as an unsupported FCQ configuration");
|
||||
wide.Queue(pcm.data(), 1, 1);
|
||||
wide.Render(1);
|
||||
Check(wide.voice.samples[1] == 0.5f, "filter OFF did not bypass audio");
|
||||
SetLowPass(wide, 0);
|
||||
wide.Queue(pcm.data(), 1, 1);
|
||||
wide.Render(1);
|
||||
Check(wide.voice.samples[1] == 0.0f, "zero-Hz low-pass leaked source audio");
|
||||
}
|
||||
|
||||
struct LoopCallbacks {
|
||||
std::vector<std::pair<uint32_t, uint32_t>> events;
|
||||
bool pause = false;
|
||||
};
|
||||
|
||||
void KYTY_SYSV_ABI RecordLoopCallback(const Ngs2VoiceCallbackInfo* info) {
|
||||
auto& log = *reinterpret_cast<LoopCallbacks*>(info->data);
|
||||
log.events.emplace_back(info->flag, info->repeats);
|
||||
Check(info->user == 123 && info->attributes == 0, "loop callback metadata changed");
|
||||
if (log.pause && info->flag == 2) {
|
||||
reinterpret_cast<Ngs2VoiceInternal*>(info->voice)->SetEvent(16);
|
||||
}
|
||||
}
|
||||
|
||||
void TestLoopCallbacksAndExit() {
|
||||
const int16_t pcm[] = {8192, 16384, 24576};
|
||||
Fixture f(48000);
|
||||
LoopCallbacks log;
|
||||
f.voice.callback = reinterpret_cast<uintptr_t>(&RecordLoopCallback);
|
||||
f.voice.callback_data = reinterpret_cast<uintptr_t>(&log);
|
||||
f.voice.callback_flags = 3;
|
||||
f.Queue(pcm, 2, 0, 2);
|
||||
f.Render(6);
|
||||
Check(log.events == std::vector<std::pair<uint32_t, uint32_t>> {{2, 1}, {2, 2}, {1, 2}},
|
||||
"loop and completion callbacks lost the completed repeat count");
|
||||
Check(f.voice.blocks.empty(), "finite loop did not complete");
|
||||
|
||||
Fixture active(48000);
|
||||
log.events.clear();
|
||||
log.pause = true;
|
||||
active.voice.callback = reinterpret_cast<uintptr_t>(&RecordLoopCallback);
|
||||
active.voice.callback_data = reinterpret_cast<uintptr_t>(&log);
|
||||
active.voice.callback_flags = 2;
|
||||
active.Queue(pcm, 2, 1, UINT32_MAX);
|
||||
active.Queue(pcm + 2, 1, 0);
|
||||
active.Render(6);
|
||||
Check(log.events.size() == 1 && active.voice.decoded_samples == 2 &&
|
||||
active.voice.blocks.front().cursor == 0 && active.voice.samples[2] == 0,
|
||||
"loop callback pause consumed another iteration");
|
||||
Ngs2VoiceParamHeader exit_loop {sizeof(Ngs2VoiceParamHeader), 0, 0x10000004};
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&active.voice), &exit_loop);
|
||||
active.voice.SetEvent(32);
|
||||
active.Render(3);
|
||||
Check(active.voice.samples == std::vector<float> {0.25f, 0.5f, 0.75f} &&
|
||||
active.voice.blocks.empty() && log.events.size() == 1,
|
||||
"exiting infinite loop lost its final iteration or ignored callback flags");
|
||||
|
||||
Fixture pending(48000);
|
||||
pending.Queue(pcm, 1, 1);
|
||||
pending.Queue(pcm + 1, 1, 1, UINT32_MAX);
|
||||
pending.Queue(pcm + 2, 1, 0);
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&pending.voice), &exit_loop);
|
||||
pending.Render(3);
|
||||
Check(pending.voice.samples == std::vector<float> {0.25f, 0.5f, 0.75f} &&
|
||||
pending.voice.blocks.empty(),
|
||||
"exiting a pending loop skipped its first iteration");
|
||||
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&pending.voice), &exit_loop);
|
||||
}
|
||||
|
||||
void TestWaveformReadAddress() {
|
||||
Fixture f(48000, 2);
|
||||
const int16_t pcm[] = {-100, -100, 8192, 4096, 16384, 8192};
|
||||
f.Queue(pcm, 2, 1, 1, 1);
|
||||
const auto next_data = [&]() {
|
||||
Ngs2SamplerVoiceState state {};
|
||||
Ngs2VoiceGetState(reinterpret_cast<uintptr_t>(&f.voice), &state.voice_state, sizeof(state));
|
||||
return state.waveform_data;
|
||||
};
|
||||
Check(next_data() == pcm + 2, "read address ignored skipped source samples");
|
||||
f.Render(1);
|
||||
Check(next_data() == pcm + 4, "read address did not advance by a stereo frame");
|
||||
f.Render(1);
|
||||
Check(next_data() == pcm + 2, "read address did not wrap at a loop boundary");
|
||||
f.Render(2);
|
||||
Check(f.voice.blocks.empty() && next_data() == pcm + 6,
|
||||
"starved read address did not preserve the consumed waveform end");
|
||||
f.Queue(pcm + 2, 2, 0);
|
||||
Check(next_data() == pcm + 2, "refilled read address retained the previous waveform end");
|
||||
f.Render(2);
|
||||
Check(next_data() == pcm + 6, "completed read address did not preserve waveform end");
|
||||
f.voice.SetupSampler({0x12, 2, 48000});
|
||||
Check(next_data() == nullptr, "setup retained an old waveform address");
|
||||
}
|
||||
|
||||
struct CallbackEvent {
|
||||
Ngs2VoiceInternal* voice;
|
||||
uint32_t event;
|
||||
uint32_t calls = 0;
|
||||
};
|
||||
|
||||
void KYTY_SYSV_ABI ApplyCallbackEvent(const Ngs2VoiceCallbackInfo* info) {
|
||||
auto& context = *reinterpret_cast<CallbackEvent*>(info->data);
|
||||
if (++context.calls == 1) {
|
||||
context.voice->SetEvent(context.event);
|
||||
}
|
||||
}
|
||||
|
||||
void TestCallbackStopsConsumption() {
|
||||
for (auto rack: {Ngs2RackType::Sampler, Ngs2RackType::CustomSampler}) {
|
||||
for (uint32_t event: {2u, 4u, 8u, 16u}) {
|
||||
Fixture f(48000, 1, rack);
|
||||
const int16_t pcm[] = {8192, 16384};
|
||||
f.Queue(pcm, 1, 1);
|
||||
f.Queue(pcm + 1, 1, 1);
|
||||
CallbackEvent context {&f.voice, event};
|
||||
f.voice.callback = reinterpret_cast<uintptr_t>(&ApplyCallbackEvent);
|
||||
f.voice.callback_data = reinterpret_cast<uintptr_t>(&context);
|
||||
f.voice.callback_flags = 1;
|
||||
f.Render(2);
|
||||
Check(f.voice.state != Ngs2VoicePlayState::Playing && context.calls == 1,
|
||||
"completion callback failed to interrupt playback");
|
||||
Check(f.voice.samples[0] == 0.25f && f.voice.samples[1] == 0.0f &&
|
||||
f.voice.decoded_samples == 1 && f.voice.blocks.size() == 1 &&
|
||||
f.voice.blocks.front().cursor == 0,
|
||||
"callback pause/stop consumed the next queued block");
|
||||
if (event == 16) {
|
||||
f.voice.SetEvent(32);
|
||||
f.Render(1);
|
||||
Check(f.voice.samples[0] == 0.5f && f.voice.decoded_samples == 2,
|
||||
"callback pause failed to preserve queued audio for resume");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestCallbackPauseDuringLargeStep() {
|
||||
Fixture f(48000);
|
||||
const int16_t pcm[] = {8192, 16384, 24576, 4096};
|
||||
f.Queue(pcm, 1, 1);
|
||||
f.Queue(pcm + 1, 1, 1);
|
||||
f.Queue(pcm + 2, 2, 1);
|
||||
f.voice.sample_step = uint64_t(120000) << 32u;
|
||||
CallbackEvent context {&f.voice, 16};
|
||||
f.voice.callback = reinterpret_cast<uintptr_t>(&ApplyCallbackEvent);
|
||||
f.voice.callback_data = reinterpret_cast<uintptr_t>(&context);
|
||||
f.voice.callback_flags = 1;
|
||||
f.Render(2);
|
||||
Check(context.calls == 1 && f.voice.decoded_samples == 1 &&
|
||||
f.voice.sample_phase == (uint64_t(72000) << 32u),
|
||||
"pause advanced across additional blocks in a large resampling step");
|
||||
f.voice.SetEvent(32);
|
||||
f.Render(1);
|
||||
Check(std::abs(f.voice.samples[0] - 0.4375f) < 1e-7f && context.calls == 3,
|
||||
"resume lost fractional position or read past the next short block");
|
||||
}
|
||||
|
||||
void TestUpstreamCustomSamplerControls() {
|
||||
Fixture f(24000, 1, Ngs2RackType::CustomSampler);
|
||||
const int16_t pcm[] = {8192, 16384, 24576, 4096};
|
||||
f.Queue(pcm, 4, 1);
|
||||
f.Render(1);
|
||||
Check(f.voice.samples[0] == 0.25f, "custom sampler source rate was rejected");
|
||||
f.Queue(pcm + 2, 2, 4);
|
||||
f.Render(1);
|
||||
Check(f.voice.samples[0] == 0.75f && f.voice.blocks.size() == 1,
|
||||
"replacement blocks retained old data or fractional position");
|
||||
struct Pitch {
|
||||
Ngs2VoiceParamHeader header {16, 0, 0x40010005};
|
||||
float ratio = 0.0f;
|
||||
uint32_t reserved = 0;
|
||||
} pitch;
|
||||
const auto handle = reinterpret_cast<uintptr_t>(&f.voice);
|
||||
Ngs2VoiceControl(handle, &pitch.header);
|
||||
f.Render(2);
|
||||
Check(f.voice.samples[0] == 0.4375f && f.voice.samples[1] == 0.4375f,
|
||||
"zero custom sampler pitch advanced playback");
|
||||
pitch.ratio = 4.0f;
|
||||
Ngs2VoiceControl(handle, &pitch.header);
|
||||
f.Render(1);
|
||||
Check(f.voice.blocks.empty(), "custom sampler pitch did not advance playback");
|
||||
f.rack.option.custom_sampler.custom_rack_option.state_size =
|
||||
sizeof(Ngs2CustomSamplerVoiceState);
|
||||
Ngs2CustomSamplerVoiceState state {};
|
||||
Check(Ngs2VoiceGetState(handle, &state.voice_state, sizeof(state)) == OK &&
|
||||
state.voice_state.state_flags == Ngs2GetStateFlags(&f.voice),
|
||||
"custom sampler state layout regressed");
|
||||
}
|
||||
|
||||
void TestFilterTailDuringStarvation() {
|
||||
Fixture reference(48000), stream(48000);
|
||||
SetLowPass(reference);
|
||||
SetLowPass(stream);
|
||||
std::vector<int16_t> pcm(128, 0);
|
||||
pcm[0] = pcm[64] = 16384;
|
||||
reference.Queue(pcm.data(), 128, 1);
|
||||
reference.Render(128);
|
||||
stream.Queue(pcm.data(), 1, 1);
|
||||
stream.Render(1);
|
||||
Check(stream.voice.samples[0] == reference.voice.samples[0], "initial filter sample differs");
|
||||
stream.Render(63);
|
||||
Check(stream.voice.has_samples, "starved filter tail was not available for routing");
|
||||
for (size_t i = 0; i < 63; ++i) {
|
||||
Check(std::abs(stream.voice.samples[i] - reference.voice.samples[i + 1]) < 1e-7f,
|
||||
"filter tail changed at a starvation/grain boundary");
|
||||
}
|
||||
stream.Queue(pcm.data() + 64, 64, 1);
|
||||
stream.Render(64);
|
||||
for (size_t i = 0; i < 64; ++i) {
|
||||
Check(std::abs(stream.voice.samples[i] - reference.voice.samples[i + 64]) < 1e-7f,
|
||||
"refilled stream reused stale filter history");
|
||||
}
|
||||
stream.Render(8192);
|
||||
stream.Render(32);
|
||||
Check(!stream.voice.has_samples && !stream.voice.filters[0].HasHistory(),
|
||||
"fully decayed filter kept a silent stream active");
|
||||
}
|
||||
|
||||
void TestResamplingPhaseAfterStarvation() {
|
||||
for (auto rack: {Ngs2RackType::Sampler, Ngs2RackType::CustomSampler}) {
|
||||
for (uint32_t rate: {44100u, 96000u}) {
|
||||
Fixture reference(rate, 1, rack), stream(rate, 1, rack);
|
||||
const int16_t pcm[] = {0, 0, 16384, 8192};
|
||||
const uint32_t frames = rate == 44100 ? 2 : 1;
|
||||
reference.Queue(pcm, 4, 0);
|
||||
stream.Queue(pcm, 1, 1);
|
||||
reference.Render(frames);
|
||||
stream.Render(frames);
|
||||
Check(stream.voice.blocks.empty() && stream.voice.state == Ngs2VoicePlayState::Playing,
|
||||
"open stream did not enter starvation");
|
||||
stream.Render(8);
|
||||
Check(!stream.voice.has_samples, "starved stream produced audio without a filter");
|
||||
stream.Queue(pcm + 1, 3, 0);
|
||||
reference.Render(1);
|
||||
stream.Render(1);
|
||||
Check(std::abs(stream.voice.samples[0] - reference.voice.samples[0]) < 1e-7f,
|
||||
"stream refill lost resampling phase");
|
||||
Check(stream.voice.decoded_samples == reference.voice.decoded_samples,
|
||||
"stream refill lost pending source advancement");
|
||||
stream.voice.SetupSampler({0x12, 1, rate});
|
||||
Check(stream.voice.sample_phase == 0, "sampler setup retained old resampling phase");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestFiniteFilterTail() {
|
||||
Fixture whole(48000), split(48000);
|
||||
SetLowPass(whole);
|
||||
SetLowPass(split);
|
||||
const int16_t impulse = 16384;
|
||||
whole.Queue(&impulse, 1, 0);
|
||||
split.Queue(&impulse, 1, 0);
|
||||
whole.Render(256);
|
||||
for (size_t i = 0; i < whole.voice.samples.size(); ++i) {
|
||||
split.Render(1);
|
||||
Check(std::abs(split.voice.samples[0] - whole.voice.samples[i]) < 1e-7f,
|
||||
"finite filter tail changed with render grain size");
|
||||
}
|
||||
Check(split.voice.state == Ngs2VoicePlayState::Playing,
|
||||
"finite voice stopped before its filter tail decayed");
|
||||
split.Render(8192);
|
||||
Check(split.voice.state == Ngs2VoicePlayState::Empty,
|
||||
"finite voice remained active after its filter tail decayed");
|
||||
split.Render(32);
|
||||
Check(!split.voice.has_samples, "completed finite filter tail still produced samples");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestPcm16RenderOutput();
|
||||
TestSubmixerSetupAndRouting();
|
||||
TestWaveformBlockGeometry();
|
||||
TestAtrac9ResampleAfterStarvation();
|
||||
TestAtrac9ResampleAcrossFrames();
|
||||
TestResamplingPhaseAfterStarvation();
|
||||
TestSamplerReuseResetsRouting();
|
||||
TestDeferredSamplerSetup();
|
||||
TestLoopCallbacksAndExit();
|
||||
TestWaveformReadAddress();
|
||||
TestFilterTailDuringStarvation();
|
||||
TestFiniteFilterTail();
|
||||
TestLowPassResponse();
|
||||
TestFilterHistoryAndChannels();
|
||||
TestFilterUsesOutputRate();
|
||||
TestFilterControlLayout();
|
||||
TestUpstreamCustomSamplerControls();
|
||||
TestCallbackStopsConsumption();
|
||||
TestCallbackPauseDuringLargeStep();
|
||||
TestStreamingResample();
|
||||
TestPitchLoopAndSkip();
|
||||
TestPitchClamps();
|
||||
TestStarvationAndPause();
|
||||
TestMonoRateAndRouting();
|
||||
TestMatrixOutputChannels();
|
||||
TestCustomPcmStillPlays();
|
||||
TestPlayStateBeforeRender();
|
||||
TestStatePublication();
|
||||
TestStopThenPlayBeforeRender();
|
||||
std::puts("Ngs2SamplerTests: all cases passed");
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// Exercise the production module with real SDL streams and fake USB endpoints.
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
void Check(bool condition, const char* text) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "PadHapticsTests: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
struct Device {
|
||||
SDL_AudioDeviceID id;
|
||||
const char* name;
|
||||
int channels;
|
||||
};
|
||||
struct Rumble {
|
||||
Uint16 large, small;
|
||||
Uint32 duration;
|
||||
};
|
||||
std::vector<Device> devices;
|
||||
std::vector<SDL_AudioStream*> streams;
|
||||
Rumble rumble {};
|
||||
Uint64 now = 1000;
|
||||
int audio_refs = 0, opens = 0, rumble_calls = 0;
|
||||
bool fail_open = false, fail_resume = false;
|
||||
int actual_channels = 4;
|
||||
SDL_AudioDeviceID opened_device = 0;
|
||||
SDL_AudioSpec opened_spec {};
|
||||
} // namespace
|
||||
|
||||
namespace Fake {
|
||||
Uint64 GetTicks() {
|
||||
return now;
|
||||
}
|
||||
bool InitSubSystem(SDL_InitFlags flags) {
|
||||
const bool result = SDL_InitSubSystem(flags);
|
||||
if (result && (flags & SDL_INIT_AUDIO)) {
|
||||
audio_refs++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void QuitSubSystem(SDL_InitFlags flags) {
|
||||
if (flags & SDL_INIT_AUDIO) {
|
||||
audio_refs--;
|
||||
}
|
||||
SDL_QuitSubSystem(flags);
|
||||
}
|
||||
SDL_AudioDeviceID* GetAudioPlaybackDevices(int* count) {
|
||||
*count = static_cast<int>(devices.size());
|
||||
auto* ids = static_cast<SDL_AudioDeviceID*>(
|
||||
SDL_malloc((devices.size() + 1) * sizeof(SDL_AudioDeviceID)));
|
||||
for (size_t i = 0; i < devices.size(); i++) {
|
||||
ids[i] = devices[i].id;
|
||||
}
|
||||
ids[devices.size()] = 0;
|
||||
return ids;
|
||||
}
|
||||
const char* GetAudioDeviceName(SDL_AudioDeviceID id) {
|
||||
for (const auto& device: devices) {
|
||||
if (device.id == id) {
|
||||
return device.name;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool GetAudioDeviceFormat(SDL_AudioDeviceID id, SDL_AudioSpec* spec, int*) {
|
||||
if (id == 999) {
|
||||
*spec = {SDL_AUDIO_F32, actual_channels, 48000};
|
||||
return true;
|
||||
}
|
||||
for (const auto& device: devices) {
|
||||
if (device.id == id) {
|
||||
*spec = {SDL_AUDIO_F32, device.channels, 48000};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SDL_AudioStream* OpenAudioDeviceStream(SDL_AudioDeviceID id, const SDL_AudioSpec* spec,
|
||||
SDL_AudioStreamCallback callback, void* userdata) {
|
||||
opens++;
|
||||
opened_device = id;
|
||||
opened_spec = *spec;
|
||||
if (fail_open) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* stream = SDL_CreateAudioStream(spec, spec);
|
||||
Check(stream != nullptr && SDL_SetAudioStreamGetCallback(stream, callback, userdata),
|
||||
"real SDL stream/callback creation failed");
|
||||
streams.push_back(stream);
|
||||
return stream;
|
||||
}
|
||||
bool ResumeAudioStreamDevice(SDL_AudioStream*) {
|
||||
return !fail_resume;
|
||||
}
|
||||
void DestroyAudioStream(SDL_AudioStream* stream) {
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto it = std::find(streams.begin(), streams.end(), stream);
|
||||
Check(it != streams.end(), "stream destroyed twice");
|
||||
streams.erase(it);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
}
|
||||
SDL_AudioDeviceID GetAudioStreamDevice(SDL_AudioStream*) {
|
||||
return 999;
|
||||
}
|
||||
SDL_GamepadType GetGamepadTypeForID(SDL_JoystickID id) {
|
||||
return id == 1 ? SDL_GAMEPAD_TYPE_PS5 : SDL_GAMEPAD_TYPE_UNKNOWN;
|
||||
}
|
||||
SDL_Gamepad* GetGamepadFromID(SDL_JoystickID id) {
|
||||
return id == 1 ? reinterpret_cast<SDL_Gamepad*>(static_cast<uintptr_t>(id)) : nullptr;
|
||||
}
|
||||
bool RumbleGamepad(SDL_Gamepad*, Uint16 large, Uint16 small, Uint32 duration) {
|
||||
rumble = {large, small, duration};
|
||||
rumble_calls++;
|
||||
return true;
|
||||
}
|
||||
} // namespace Fake
|
||||
|
||||
#define SDL_GetTicks Fake::GetTicks
|
||||
#define SDL_InitSubSystem Fake::InitSubSystem
|
||||
#define SDL_QuitSubSystem Fake::QuitSubSystem
|
||||
#define SDL_GetAudioPlaybackDevices Fake::GetAudioPlaybackDevices
|
||||
#define SDL_GetAudioDeviceName Fake::GetAudioDeviceName
|
||||
#define SDL_GetAudioDeviceFormat Fake::GetAudioDeviceFormat
|
||||
#define SDL_OpenAudioDeviceStream Fake::OpenAudioDeviceStream
|
||||
#define SDL_ResumeAudioStreamDevice Fake::ResumeAudioStreamDevice
|
||||
#define SDL_DestroyAudioStream Fake::DestroyAudioStream
|
||||
#define SDL_GetAudioStreamDevice Fake::GetAudioStreamDevice
|
||||
#define SDL_GetGamepadTypeForID Fake::GetGamepadTypeForID
|
||||
#define SDL_GetGamepadFromID Fake::GetGamepadFromID
|
||||
#define SDL_RumbleGamepad Fake::RumbleGamepad
|
||||
#include "libs/dualSenseHaptics.cpp"
|
||||
#undef SDL_GetTicks
|
||||
#undef SDL_InitSubSystem
|
||||
#undef SDL_QuitSubSystem
|
||||
#undef SDL_GetAudioPlaybackDevices
|
||||
#undef SDL_GetAudioDeviceName
|
||||
#undef SDL_GetAudioDeviceFormat
|
||||
#undef SDL_OpenAudioDeviceStream
|
||||
#undef SDL_ResumeAudioStreamDevice
|
||||
#undef SDL_DestroyAudioStream
|
||||
#undef SDL_GetAudioStreamDevice
|
||||
#undef SDL_GetGamepadTypeForID
|
||||
#undef SDL_GetGamepadFromID
|
||||
#undef SDL_RumbleGamepad
|
||||
|
||||
namespace {
|
||||
namespace Haptics = Libs::Controller::DualSenseHaptics;
|
||||
using Port = std::unique_ptr<Haptics::Stream, decltype(&Haptics::Close)>;
|
||||
constexpr std::array<int, 2> unity {32768, 32768};
|
||||
constexpr std::array<float, 4> pcm {0.5f, 0.5f, 0.5f, 0.5f};
|
||||
|
||||
struct Fixture {
|
||||
Fixture() {
|
||||
devices = {{10, "Speakers (DualSense Wireless Controller)", 4}};
|
||||
rumble = {};
|
||||
now = 1000;
|
||||
opens = rumble_calls = 0;
|
||||
fail_open = fail_resume = false;
|
||||
actual_channels = 4;
|
||||
}
|
||||
~Fixture() {
|
||||
Haptics::Shutdown();
|
||||
Check(streams.empty() && audio_refs == 0, "stream or audio subsystem reference leaked");
|
||||
}
|
||||
};
|
||||
|
||||
Port Open() {
|
||||
auto* stream = Haptics::Open(48000);
|
||||
Check(stream != nullptr, "port open failed");
|
||||
return Port(stream, Haptics::Close);
|
||||
}
|
||||
void Queue(const Port& port, const void* data, uint32_t frames = 2, uint32_t channels = 2,
|
||||
bool is_float = true, const int* volume = unity.data()) {
|
||||
Haptics::Queue(port.get(), 1, data, frames, channels, is_float, volume);
|
||||
}
|
||||
void Pull() {
|
||||
std::array<float, 64> buffer {};
|
||||
Check(!streams.empty() &&
|
||||
SDL_GetAudioStreamData(streams.back(), buffer.data(), sizeof(buffer)) >= 0,
|
||||
"audio device pull failed");
|
||||
}
|
||||
void ExpectPcm(std::array<float, 8> expected) {
|
||||
std::array<float, 8> actual {};
|
||||
Check(!streams.empty() && SDL_GetAudioStreamData(streams.back(), actual.data(),
|
||||
sizeof(actual)) == sizeof(actual),
|
||||
"wrong output byte count");
|
||||
for (size_t i = 0; i < actual.size(); i++) {
|
||||
Check(std::abs(actual[i] - expected[i]) < 1e-6f, "haptics mapping or volume is wrong");
|
||||
}
|
||||
}
|
||||
|
||||
void TestFormatsAndVolume() {
|
||||
Fixture f;
|
||||
auto port = Open();
|
||||
const std::array<int, 2> volume {32768, 16384};
|
||||
const std::array<float, 4> stereo {0.5f, -0.25f, 1.0f, 0.0f};
|
||||
Queue(port, stereo.data(), 2, 2, true, volume.data());
|
||||
Check(opened_device == 10 && opened_spec.channels == 4 && opened_spec.format == SDL_AUDIO_F32,
|
||||
"did not select quad DualSense PCM");
|
||||
ExpectPcm({0, 0, 0.5f, -0.125f, 0, 0, 1.0f, 0});
|
||||
const std::array<int16_t, 2> mono {16384, -32768};
|
||||
Queue(port, mono.data(), 2, 1, false, volume.data() + 1);
|
||||
ExpectPcm({0, 0, 0.25f, 0.25f, 0, 0, -0.5f, -0.5f});
|
||||
std::array<float, 24> multichannel {};
|
||||
multichannel.fill(0.9f);
|
||||
multichannel[0] = 0.1f;
|
||||
multichannel[1] = 0.2f;
|
||||
multichannel[12] = 0.3f;
|
||||
multichannel[13] = 0.4f;
|
||||
Queue(port, multichannel.data(), 2, 12);
|
||||
ExpectPcm({0, 0, 0.1f, 0.2f, 0, 0, 0.3f, 0.4f});
|
||||
}
|
||||
|
||||
void TestDiscoveryAndHotplug() {
|
||||
Fixture f;
|
||||
devices = {{10, "DualSense Wireless Controller", 2}, {20, "Other speakers", 4}};
|
||||
auto port = Open();
|
||||
Queue(port, pcm.data());
|
||||
Check(streams.empty() && opens == 0, "stereo controller or unrelated speakers selected");
|
||||
devices[0].channels = 4;
|
||||
now += 2001;
|
||||
Queue(port, pcm.data());
|
||||
Check(streams.size() == 1 && opened_device == 10, "late USB connection was not discovered");
|
||||
devices.clear();
|
||||
now += 2001;
|
||||
Queue(port, pcm.data());
|
||||
Check(streams.empty(), "disconnected endpoint retained its stream");
|
||||
}
|
||||
|
||||
void TestFailuresAndBoundedQueue() {
|
||||
Fixture f;
|
||||
auto port = Open();
|
||||
std::array<float, 128> block {};
|
||||
block.fill(0.5f);
|
||||
fail_open = true;
|
||||
Queue(port, block.data(), 64);
|
||||
Check(streams.empty() && audio_refs == 1, "failed open leaked a stream/reference");
|
||||
fail_open = false;
|
||||
fail_resume = true;
|
||||
now += 2001;
|
||||
Queue(port, block.data(), 64);
|
||||
Check(streams.empty() && audio_refs == 1, "failed resume leaked a stream/reference");
|
||||
fail_resume = false;
|
||||
actual_channels = 2;
|
||||
now += 2001;
|
||||
Queue(port, block.data(), 64);
|
||||
Check(streams.empty(), "post-open stereo downgrade accepted");
|
||||
actual_channels = 4;
|
||||
now += 2001;
|
||||
for (int i = 0; i < 70; i++) {
|
||||
Queue(port, block.data(), 64);
|
||||
}
|
||||
constexpr int block_bytes = 64 * 4 * sizeof(float);
|
||||
constexpr int max_queue = 48000 * 4 * sizeof(float) * 80 / 1000;
|
||||
Check(streams.size() == 1 &&
|
||||
SDL_GetAudioStreamQueued(streams.back()) <= max_queue + block_bytes,
|
||||
"queue grows indefinitely when its clock stalls");
|
||||
}
|
||||
|
||||
void TestRumbleLeaseAndDuration() {
|
||||
Fixture f;
|
||||
auto port = Open();
|
||||
Check(Haptics::SetVibration(1, 100, 50), "set rumble failed");
|
||||
Check(rumble.large == 100 * 0x101 && rumble.duration == 65535, "ordinary rumble changed");
|
||||
Queue(port, pcm.data());
|
||||
Check(rumble.large == 0 && rumble.small == 0, "rumble was not stopped before haptics");
|
||||
now = 1100;
|
||||
const std::array<float, 4> silence {};
|
||||
Queue(port, silence.data());
|
||||
now = 1249;
|
||||
Pull();
|
||||
Check(rumble.large == 0, "rumble resumed before lease expiration");
|
||||
now = 1250;
|
||||
Pull();
|
||||
Check(rumble.large == 100 * 0x101 && rumble.small == 50 * 0x101 && rumble.duration == 65285,
|
||||
"idle haptics did not restore remaining rumble duration");
|
||||
now = 66500;
|
||||
Queue(port, pcm.data());
|
||||
now = 66751;
|
||||
Pull();
|
||||
Check(rumble.large == 0 && rumble.duration == 0, "expired rumble restarted after haptics");
|
||||
Haptics::Shutdown();
|
||||
const int calls = rumble_calls;
|
||||
Pull();
|
||||
Check(rumble_calls == calls, "audio pull touched rumble after shutdown");
|
||||
}
|
||||
|
||||
void TestCloseRestoresRumble() {
|
||||
Fixture f;
|
||||
auto first = Open(), second = Open();
|
||||
Haptics::SetVibration(1, 100, 50);
|
||||
Queue(first, pcm.data());
|
||||
Queue(second, pcm.data());
|
||||
Check(rumble.large == 0, "haptics did not suppress rumble");
|
||||
first.reset();
|
||||
Check(rumble.large == 0, "closing one of two streams restored rumble early");
|
||||
second.reset();
|
||||
Check(rumble.large == 100 * 0x101 && rumble.small == 50 * 0x101,
|
||||
"closing the final stream left rumble suppressed");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
TestFormatsAndVolume();
|
||||
TestDiscoveryAndHotplug();
|
||||
TestFailuresAndBoundedQueue();
|
||||
TestRumbleLeaseAndDuration();
|
||||
TestCloseRestoresRumble();
|
||||
std::printf("PadHapticsTests: all cases passed\n");
|
||||
}
|
||||
@@ -2815,6 +2815,31 @@ void TestGraphicsPushConstantLayout() {
|
||||
}
|
||||
|
||||
void TestResourceLimitIsTransactional() {
|
||||
Fixture accepted;
|
||||
MemoryInfo accepted_memory;
|
||||
accepted_memory.kind = ResourceKind::Buffer;
|
||||
for (uint32_t index = 0; index < ShaderInfo::MaxBuffers; index++) {
|
||||
const auto handle = accepted.Buffer(
|
||||
{Value(index), Value(index + 1u), Value(index + 2u), Value(index + 3u)},
|
||||
index * 4u);
|
||||
accepted.Emit(ValueOpcode::LoadBufferU32,
|
||||
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
|
||||
accepted.AddMemory(accepted_memory, index * 4u));
|
||||
}
|
||||
accepted.PlanAndTrack();
|
||||
Check(accepted.program.info.buffers.size() == 64u &&
|
||||
accepted.program.descriptor_sources.size() == 64u &&
|
||||
accepted.program.memory_info.back().resource == 63u,
|
||||
"compute shader did not retain all 64 distinct buffers");
|
||||
ShaderComputeInputInfo compute{};
|
||||
CollectShaderInfo(accepted.program, {.compute = &compute});
|
||||
AllocateBindings(accepted.program);
|
||||
const auto *binding = FindBinding(accepted.program.bindings,
|
||||
DescriptorBindingKind::Buffers);
|
||||
Check(binding != nullptr && binding->resources.size() == 64u &&
|
||||
accepted.program.bindings.memory_offset_count == 64u,
|
||||
"compute shader binding layout truncated the 64 buffers");
|
||||
|
||||
Fixture fixture;
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Buffer;
|
||||
|
||||
+1053
-123
File diff suppressed because it is too large.
Load diff
@@ -86,7 +86,11 @@ ShaderRecompiler::CompileOptions MakeCompileOptions(ShaderType stage) {
|
||||
static const ShaderVertexInputInfo vertex{};
|
||||
static const ShaderPixelInputInfo pixel{};
|
||||
static const ShaderComputeInputInfo compute{};
|
||||
static const std::array<uint32_t, 64> user_data{};
|
||||
static const auto user_data = [] {
|
||||
std::array<uint32_t, 64> data{};
|
||||
data[3] = 3u << 28u; // Default fixture buffer uses raw offset bounds.
|
||||
return data;
|
||||
}();
|
||||
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
@@ -3523,8 +3527,6 @@ void TestNewShaderRecompilerVop1SdwaNotDestination() {
|
||||
check_rejected(0x00260400u, "V_NOT_B32 SDWA accepted source absolute");
|
||||
check_rejected(0x00062400u, "V_NOT_B32 SDWA accepted clamp");
|
||||
check_rejected(0x00064400u, "V_NOT_B32 SDWA accepted output modifier");
|
||||
check_rejected(0x00000400u,
|
||||
"V_NOT_B32 SDWA partial destination accepted a byte source");
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {
|
||||
@@ -6514,6 +6516,7 @@ void TestNewShaderRecompilerNativeWideScalarMemoryIr() {
|
||||
|
||||
void TestNewShaderRecompilerNativeWideBufferIr() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSMovB32(83, 255), 3u << 28u, // Raw bounds for the s[80:83] fixture.
|
||||
EncodeMubuf0(0x0d, 0),
|
||||
EncodeMubuf1(0, 20, 1), // buffer_load_dwordx2 v[0:1]
|
||||
EncodeMubuf0(0x1d, 16),
|
||||
@@ -7855,6 +7858,44 @@ void TestNewShaderRecompilerCfgSharedOuterAndLoopMerge() {
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgLoopExitSharedWithSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // loop condition
|
||||
EncodeSopp(0x04, 10), // loop exit -> end
|
||||
EncodeSopc(0x06, 1, 1), // selection within the loop
|
||||
EncodeSopp(0x05, 3), // choose either arm
|
||||
EncodeSopc(0x06, 2, 2), // first arm
|
||||
EncodeSopp(0x04, 6), // first arm -> shared end
|
||||
EncodeSopp(0x02, 3), // first arm -> repeat
|
||||
EncodeSopc(0x06, 3, 3), // second arm
|
||||
EncodeSopp(0x04, 3), // second arm -> shared end
|
||||
EncodeSopp(0x02, 0), // second arm -> repeat
|
||||
EncodeSop2(0x00, 0, 0, 129), // repeat work
|
||||
EncodeSopp(0x02, 0xfff4u), // backedge
|
||||
0xbf810000u,
|
||||
};
|
||||
|
||||
ShaderRecompiler::Decoder::Program decoded;
|
||||
ShaderRecompiler::Decoder::DecodeProgram(std::span{shader}, decoded);
|
||||
auto graph = ShaderRecompiler::CFG::BuildGraph(decoded);
|
||||
const auto block_count = graph.blocks.size();
|
||||
const auto coverage = CfgInstructionCoverage(graph, decoded.instructions.size());
|
||||
Check(block_count == 8u && graph.natural_loops.size() == 1u,
|
||||
"shared loop-exit fixture has the wrong native CFG");
|
||||
Check(!ShaderRecompiler::CFG::Structurize(graph) &&
|
||||
graph.unsupported_reason.find("duplicate structured merge block") !=
|
||||
std::string::npos,
|
||||
"shared loop exit did not terminate at its structured merge conflict");
|
||||
Check(graph.blocks.size() == block_count &&
|
||||
CfgInstructionCoverage(graph, decoded.instructions.size()) == coverage,
|
||||
"shared loop-exit fallback changed semantic instruction coverage");
|
||||
|
||||
auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Compute));
|
||||
Check(result.program.dispatcher_fallback && SpirvContainsOpcode(result.spirv, 251),
|
||||
"shared loop exit did not emit its dispatcher fallback");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
|
||||
void TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection() {
|
||||
const uint32_t shader[] = {
|
||||
EncodeSopc(0x0a, 0, 129), // loop: s_cmp_lt_u32 s0, 1
|
||||
@@ -13586,6 +13627,7 @@ int main() {
|
||||
TestNewShaderRecompilerCfgLoopHeaderDsReadStructured();
|
||||
TestNewShaderRecompilerCfgLoopHeaderDsRead2B64Structured();
|
||||
TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
|
||||
TestNewShaderRecompilerCfgLoopExitSharedWithSelection();
|
||||
TestNewShaderRecompilerCfgLoopEarlyBreakNoSelection();
|
||||
TestNewShaderRecompilerCfgNestedLoopNonlocalExitDispatcher();
|
||||
TestNewShaderRecompilerCfgNestedLoopLocalExitNoSelection();
|
||||
|
||||
Reference in new issue
Block a user