mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 00:12:40 +00:00
Compare commits
76
Commits
83f8bfd3c9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1be1c6c4f9 | ||
|
|
69fdb03dc2 | ||
|
|
2efd31443f | ||
|
|
9eef2e1434 | ||
|
|
39faa62ebd | ||
|
|
7ff8aff5e6 | ||
|
|
64a554dea7 | ||
|
|
4b0282e343 | ||
|
|
ffc81377b2 | ||
|
|
0685bffb2f | ||
|
|
a5b3673a39 | ||
|
|
09753bf8ff | ||
|
|
9d8c458535 | ||
|
|
358333125d | ||
|
|
fd6b28c4b2 | ||
|
|
6cee2dc0bd | ||
|
|
a526a8b9c9 | ||
|
|
1ec69f8e7d | ||
|
|
2463e3aaf0 | ||
|
|
556538bed5 | ||
|
|
770438cee0 | ||
|
|
db12f0c5fb | ||
|
|
3f9666acbb | ||
|
|
7eb8abb279 | ||
|
|
91a5ff6cb7 | ||
|
|
95a507a8ff | ||
|
|
b2053c2861 | ||
|
|
9801fd862b | ||
|
|
78adb9293d | ||
|
|
0a72641678 | ||
|
|
99f4eba223 | ||
|
|
04bfd9070c | ||
|
|
ffa3992c1a | ||
|
|
1b70938052 | ||
|
|
914a3bb4b8 | ||
|
|
ffb6ad6cab | ||
|
|
ff5d16eefc | ||
|
|
d118afbeae | ||
|
|
a3ac6d0d32 | ||
|
|
f297c0bfe1 | ||
|
|
20bb268a48 | ||
|
|
d3768c00e9 | ||
|
|
dfcf1c0b03 | ||
|
|
de3ed905c8 | ||
|
|
0a140911ad | ||
|
|
2483573651 | ||
|
|
986721f5bc | ||
|
|
f90a8b1589 | ||
|
|
17a0f53d39 | ||
|
|
943bc19f46 | ||
|
|
5429888d4c | ||
|
|
025d1f7edf | ||
|
|
3c62269751 | ||
|
|
ceb154d9e4 | ||
|
|
26ca609236 | ||
|
|
4ebae87a1b | ||
|
|
b4209dde1b | ||
|
|
72d7da7266 | ||
|
|
ce5e57aa36 | ||
|
|
6357740fa1 | ||
|
|
be1c84f4d7 | ||
|
|
560756f34c | ||
|
|
52c80be266 | ||
|
|
22a9392d23 | ||
|
|
95bbc9c65d | ||
|
|
c5f7644751 | ||
|
|
4db225008b | ||
|
|
dc3bd68dee | ||
|
|
c1dfb309a1 | ||
|
|
ba7f9969ef | ||
|
|
7524e8f79e | ||
|
|
153dadd0b3 | ||
|
|
463a096249 | ||
|
|
597137e8b1 | ||
|
|
b2c9ec7609 | ||
|
|
99044c7c49 |
No files matched your search
@@ -32,7 +32,7 @@ jobs:
|
|||||||
windows:
|
windows:
|
||||||
name: Build KytyPS5 (Windows)
|
name: Build KytyPS5 (Windows)
|
||||||
needs: metadata
|
needs: metadata
|
||||||
runs-on: windows-2022
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|||||||
Vendored
+22
@@ -42,6 +42,28 @@ if(NOT KYTY_CLANG_CL)
|
|||||||
endif()
|
endif()
|
||||||
install(FILES "${openxr_SOURCE_DIR}/LICENSE" DESTINATION "licenses/openxr")
|
install(FILES "${openxr_SOURCE_DIR}/LICENSE" DESTINATION "licenses/openxr")
|
||||||
|
|
||||||
|
# Noto Sans v2.015 (SIL Open Font License 1.1) is libFont's fallback font.
|
||||||
|
set(KYTY_NOTO_SANS_REPOSITORY "https://raw.githubusercontent.com/notofonts/notofonts.github.io")
|
||||||
|
set(KYTY_NOTO_SANS_COMMIT 42af67ccd12bc0e31ed85384bf73e39248265374)
|
||||||
|
set(KYTY_NOTO_SANS_BASE_URL "${KYTY_NOTO_SANS_REPOSITORY}/${KYTY_NOTO_SANS_COMMIT}/fonts")
|
||||||
|
set(KYTY_NOTO_SANS_DIR "${FETCHCONTENT_BASE_DIR}/noto-sans")
|
||||||
|
function(kyty_fetch_noto_sans path sha256)
|
||||||
|
get_filename_component(name "${path}" NAME)
|
||||||
|
file(DOWNLOAD "${KYTY_NOTO_SANS_BASE_URL}/${path}" "${KYTY_NOTO_SANS_DIR}/${name}"
|
||||||
|
EXPECTED_HASH SHA256=${sha256})
|
||||||
|
endfunction()
|
||||||
|
kyty_fetch_noto_sans(LICENSE f2095b08bed08b23a6fe26112fcd679a2bee3f002eef077eb05d215ed1051bd8)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Regular.ttf
|
||||||
|
478c558ea716033cd60c03438f628dfa75694dcf6b5f6d505a2f05fd2b4f3823)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Bold.ttf
|
||||||
|
1df075a380fc7cb898acf64c1f7b3b4dd780de3caa860178bf929de35817a913)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-Italic.ttf
|
||||||
|
467e3f89eeca4108bb8710a2b9e0cf2281ac56d5b0609211a83776d0505eecb5)
|
||||||
|
kyty_fetch_noto_sans(NotoSans/hinted/ttf/NotoSans-BoldItalic.ttf
|
||||||
|
1b602a9d6353be42c91df097a4857b69fa2696f26703d7a33b54a15d87c2622c)
|
||||||
|
install(FILES "${KYTY_NOTO_SANS_DIR}/LICENSE" DESTINATION "licenses/noto-sans")
|
||||||
|
set(KYTY_NOTO_SANS_DIR "${KYTY_NOTO_SANS_DIR}" PARENT_SCOPE)
|
||||||
|
|
||||||
# The guest instruction patcher needs a decoder/encoder and a small
|
# The guest instruction patcher needs a decoder/encoder and a small
|
||||||
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
||||||
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
|||||||
+17
-2
@@ -407,6 +407,8 @@ add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
|||||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(build_flags_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/BuildFlagsTests.cpp")
|
||||||
|
|
||||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||||
@@ -650,6 +652,7 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
|
||||||
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
|
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 page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||||
|
add_test(NAME build_flags COMMAND $<TARGET_FILE:build_flags_tests>)
|
||||||
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
add_test(NAME archive_file COMMAND $<TARGET_FILE:archive_file_tests>)
|
||||||
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
add_test(NAME trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||||
@@ -697,8 +700,6 @@ if(BUILD_TESTING)
|
|||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||||
add_test(NAME gpu_command_lane
|
add_test(NAME gpu_command_lane
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||||
add_test(NAME wait_until_safe_for_rendering
|
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
|
||||||
add_test(NAME pm4_flip
|
add_test(NAME pm4_flip
|
||||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||||
add_test(NAME depth_bits
|
add_test(NAME depth_bits
|
||||||
@@ -743,6 +744,7 @@ if(BUILD_TESTING)
|
|||||||
image_page_table_tests
|
image_page_table_tests
|
||||||
memory_tracker_tests
|
memory_tracker_tests
|
||||||
page_manager_tests
|
page_manager_tests
|
||||||
|
build_flags_tests
|
||||||
archive_file_tests
|
archive_file_tests
|
||||||
trophy_system_tests
|
trophy_system_tests
|
||||||
avplayer_file_tests
|
avplayer_file_tests
|
||||||
@@ -811,6 +813,19 @@ add_custom_target(kyty_touch_cursors
|
|||||||
add_dependencies(kyty_emulator kyty_touch_cursors)
|
add_dependencies(kyty_emulator kyty_touch_cursors)
|
||||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/assets/cursors" DESTINATION assets)
|
||||||
|
|
||||||
|
set(KYTY_NOTO_SANS_FONTS
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Regular.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Bold.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-Italic.ttf"
|
||||||
|
"${KYTY_NOTO_SANS_DIR}/NotoSans-BoldItalic.ttf")
|
||||||
|
add_custom_target(kyty_noto_sans_fonts
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${KYTY_NOTO_SANS_FONTS}
|
||||||
|
"$<TARGET_FILE_DIR:kyty_emulator>/assets/fonts"
|
||||||
|
VERBATIM)
|
||||||
|
add_dependencies(kyty_emulator kyty_noto_sans_fonts)
|
||||||
|
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION assets/fonts)
|
||||||
|
|
||||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(kyty_emulator opengl32)
|
target_link_libraries(kyty_emulator opengl32)
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ bool HideCursorEnabled() {
|
|||||||
return g_config->hide_cursor_enabled;
|
return g_config->hide_cursor_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StretchEnabled() {
|
||||||
|
return g_config->stretch_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool VrEnabled() {
|
bool VrEnabled() {
|
||||||
return g_config->vr_enabled;
|
return g_config->vr_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ struct ConfigOptions {
|
|||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
bool hdr_enabled = false;
|
bool hdr_enabled = false;
|
||||||
bool hide_cursor_enabled = false;
|
bool hide_cursor_enabled = false;
|
||||||
|
bool stretch_enabled = false;
|
||||||
bool splash_enabled = true;
|
bool splash_enabled = true;
|
||||||
bool vr_enabled = false;
|
bool vr_enabled = false;
|
||||||
bool amd_cpu_enabled = false;
|
bool amd_cpu_enabled = false;
|
||||||
@@ -125,6 +126,7 @@ int32_t GetGpuIndex();
|
|||||||
bool FullscreenEnabled();
|
bool FullscreenEnabled();
|
||||||
bool HdrEnabled();
|
bool HdrEnabled();
|
||||||
bool HideCursorEnabled();
|
bool HideCursorEnabled();
|
||||||
|
bool StretchEnabled();
|
||||||
bool VrEnabled();
|
bool VrEnabled();
|
||||||
bool AmdCpuEnabled();
|
bool AmdCpuEnabled();
|
||||||
bool IsNetworkConnected();
|
bool IsNetworkConnected();
|
||||||
|
|||||||
@@ -99,10 +99,6 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
|||||||
|
|
||||||
if (Common::File::IsFileExisting(param_json)) {
|
if (Common::File::IsFileExisting(param_json)) {
|
||||||
Loader::SystemContentLoadParamSfo(param_json);
|
Loader::SystemContentLoadParamSfo(param_json);
|
||||||
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
|
||||||
flexible_memory_size != 0) {
|
|
||||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (cfg.splash_enabled) {
|
if (cfg.splash_enabled) {
|
||||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ struct IndirectDrawRegisters {
|
|||||||
uint32_t vertex_offset;
|
uint32_t vertex_offset;
|
||||||
uint32_t instance_offset;
|
uint32_t instance_offset;
|
||||||
uint32_t index_offset;
|
uint32_t index_offset;
|
||||||
|
uint32_t draw_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Pm4Execution {
|
class Pm4Execution {
|
||||||
@@ -97,6 +98,7 @@ public:
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op);
|
void WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op);
|
||||||
|
void MemSemaphore(uint64_t* counter, bool signal, bool write);
|
||||||
void WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control);
|
void WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control);
|
||||||
void WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes);
|
void WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes);
|
||||||
void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
|
void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ constexpr auto MakeOpcodeDispatchTable() {
|
|||||||
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
|
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
|
||||||
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
|
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
|
||||||
g_cp_op_func[Pm4::IT_RELEASE_MEM] = CpOpReleaseMem;
|
g_cp_op_func[Pm4::IT_RELEASE_MEM] = CpOpReleaseMem;
|
||||||
|
g_cp_op_func[Pm4::IT_MEM_SEMAPHORE] = CpOpMemSemaphore;
|
||||||
g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind;
|
g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind;
|
||||||
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
|
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
|
||||||
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
|
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ uint32_t CpOpEventWrite(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
|
|||||||
uint32_t CpOpDmaData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
uint32_t CpOpDmaData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
uint32_t CpOpAcquireMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
uint32_t CpOpAcquireMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
|
uint32_t CpOpMemSemaphore(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
uint32_t CpOpSetContextReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
uint32_t CpOpSetContextReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
uint32_t CpOpSetShaderReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
uint32_t CpOpSetShaderReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
uint32_t CpOpSetUconfigReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
uint32_t CpOpSetUconfigReg(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||||
|
|||||||
@@ -1582,7 +1582,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirect) {
|
|||||||
// The native indexed packet enables the high-half first-index destination in word 3.
|
// The native indexed packet enables the high-half first-index destination in word 3.
|
||||||
const IndirectDrawRegisters registers {
|
const IndirectDrawRegisters registers {
|
||||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||||
indexed && (buffer[2] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
indexed && (buffer[2] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP, Pm4::SH_NOP};
|
||||||
|
|
||||||
cp.DrawIndirect(data_offset, registers, draw_initiator, indexed);
|
cp.DrawIndirect(data_offset, registers, draw_initiator, indexed);
|
||||||
|
|
||||||
@@ -1602,10 +1602,12 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirectMulti) {
|
|||||||
const auto stride_in_bytes = buffer[7];
|
const auto stride_in_bytes = buffer[7];
|
||||||
const auto draw_initiator = buffer[8];
|
const auto draw_initiator = buffer[8];
|
||||||
const bool indexed = (cmd_id == 0xc0083800);
|
const bool indexed = (cmd_id == 0xc0083800);
|
||||||
// Multi-draw moves the first-index enable to word 4; the destinations stay in 2/3.
|
// Multi-draw moves the first-index enable to word 4; the destinations stay in 2/3. Word 4
|
||||||
|
// bit 31 enables the subdraw index, written to the register in its low half.
|
||||||
const IndirectDrawRegisters registers {
|
const IndirectDrawRegisters registers {
|
||||||
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
buffer[1] & 0xffffu, buffer[2] & 0xffffu,
|
||||||
indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP};
|
indexed && (buffer[3] & (1u << 28u)) != 0 ? buffer[1] >> 16u : Pm4::SH_NOP,
|
||||||
|
(buffer[3] >> 31u) != 0 ? buffer[3] & 0xffffu : Pm4::SH_NOP};
|
||||||
|
|
||||||
if (count_indirect == 0) {
|
if (count_indirect == 0) {
|
||||||
count_addr = nullptr;
|
count_addr = nullptr;
|
||||||
@@ -2058,6 +2060,21 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
|||||||
return 7;
|
return 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KYTY_CP_OP_PARSER(CpOpMemSemaphore) {
|
||||||
|
KYTY_PROFILER_FUNCTION();
|
||||||
|
|
||||||
|
const auto control = buffer[2];
|
||||||
|
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0023900 || (control & ~0xe0110000u) != 0 ||
|
||||||
|
(control >> 29u) < 6u);
|
||||||
|
|
||||||
|
auto* counter = reinterpret_cast<uint64_t*>(
|
||||||
|
(buffer[0] & ~0x7u) | (static_cast<uint64_t>(buffer[1] & 0x3ffffu) << 32u));
|
||||||
|
// A signal updates the counter before the next packet, so its mailbox stall never waits.
|
||||||
|
cp.MemSemaphore(counter, (control >> 29u) == 6u, ((control >> 20u) & 0x1u) != 0);
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
KYTY_CP_OP_PARSER(CpOpSetContextReg) {
|
KYTY_CP_OP_PARSER(CpOpSetContextReg) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <semaphore>
|
#include <semaphore>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
@@ -92,12 +91,6 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
|||||||
m_work_available.Signal();
|
m_work_available.Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuestGpu::Wake() {
|
|
||||||
Common::LockGuard lock(m_queue_mutex);
|
|
||||||
m_wake_pending = true;
|
|
||||||
m_work_available.Signal();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GuestGpu::ProcessCommands() {
|
void GuestGpu::ProcessCommands() {
|
||||||
EXIT_IF(!IsGpuThread());
|
EXIT_IF(!IsGpuThread());
|
||||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||||
@@ -281,6 +274,16 @@ template void CommandProcessor::WaitRegMem<uint32_t>(uint32_t, const uint32_t*,
|
|||||||
template void CommandProcessor::WaitRegMem<uint64_t>(uint32_t, const uint64_t*, uint64_t, uint64_t,
|
template void CommandProcessor::WaitRegMem<uint64_t>(uint32_t, const uint64_t*, uint64_t, uint64_t,
|
||||||
uint32_t, uint32_t);
|
uint32_t, uint32_t);
|
||||||
|
|
||||||
|
void CommandProcessor::MemSemaphore(uint64_t* counter, bool signal, bool write) {
|
||||||
|
if (signal) {
|
||||||
|
*counter = write ? 1u : *counter + 1u;
|
||||||
|
} else if (*counter == 0) {
|
||||||
|
SuspendPm4();
|
||||||
|
} else {
|
||||||
|
(*counter)--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num,
|
void CommandProcessor::WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num,
|
||||||
uint32_t write_control) {
|
uint32_t write_control) {
|
||||||
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
|
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
|
||||||
@@ -433,10 +436,7 @@ void GuestGpu::ThreadRun(void* data) {
|
|||||||
}
|
}
|
||||||
if (selected_queue < 0) {
|
if (selected_queue < 0) {
|
||||||
gpu->m_processing = false;
|
gpu->m_processing = false;
|
||||||
// A wake that came before this wait must not be lost.
|
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
|
||||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
|
||||||
}
|
|
||||||
for (auto& queue: gpu->m_queues) {
|
for (auto& queue: gpu->m_queues) {
|
||||||
if (!queue.empty()) {
|
if (!queue.empty()) {
|
||||||
queue.front().blocked = false;
|
queue.front().blocked = false;
|
||||||
@@ -789,7 +789,7 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
|||||||
|
|
||||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||||
uint32_t vertex_offset, uint32_t instance_offset,
|
uint32_t vertex_offset, uint32_t instance_offset,
|
||||||
uint32_t first_index) {
|
uint32_t first_index, uint32_t draw_index) {
|
||||||
const auto write = [&](uint32_t location, uint32_t value) {
|
const auto write = [&](uint32_t location, uint32_t value) {
|
||||||
if (location == Pm4::SH_NOP) {
|
if (location == Pm4::SH_NOP) {
|
||||||
return;
|
return;
|
||||||
@@ -801,6 +801,28 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
|||||||
write(registers.vertex_offset, vertex_offset);
|
write(registers.vertex_offset, vertex_offset);
|
||||||
write(registers.instance_offset, instance_offset);
|
write(registers.instance_offset, instance_offset);
|
||||||
write(registers.index_offset, first_index);
|
write(registers.index_offset, first_index);
|
||||||
|
write(registers.draw_index, draw_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes the GS user-data slots the packet patches; false when the shaders cannot read the
|
||||||
|
// offsets from GPU records: a first-index or subdraw-index destination, or another stage's
|
||||||
|
// user data.
|
||||||
|
static bool IndirectDrawOffsets(IndirectDrawRegisters registers, ShaderIndirectOffsets& offsets) {
|
||||||
|
if (registers.index_offset != Pm4::SH_NOP || registers.draw_index != Pm4::SH_NOP) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto [location, slot]: {std::pair {registers.vertex_offset, &offsets.vertex},
|
||||||
|
std::pair {registers.instance_offset, &offsets.instance}}) {
|
||||||
|
if (location == Pm4::SH_NOP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint32_t index = location - Pm4::SPI_SHADER_USER_DATA_GS_0;
|
||||||
|
if (index >= HW::UserSgprInfo::SGPRS_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*slot = static_cast<uint8_t>(index);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||||
@@ -834,12 +856,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
|||||||
// NUM_INSTANCES keeps the last draw's instance count.
|
// NUM_INSTANCES keeps the last draw's instance count.
|
||||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
// The GPU reads the records if the shaders allow; the VS prolog loads the patched SGPRs from
|
||||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
// Vulkan's bases. With no SGPR patched, Vulkan applies the offsets, which matches while they
|
||||||
// while they are zero.
|
// are zero.
|
||||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
|
||||||
registers.index_offset == Pm4::SH_NOP && (!indexed || m_index_buffer_size != 0) &&
|
|
||||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||||
{.index_count = m_index_buffer_size,
|
{.index_count = m_index_buffer_size,
|
||||||
.index_addr = IndexAddress(0),
|
.index_addr = IndexAddress(0),
|
||||||
@@ -854,7 +875,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
|||||||
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||||
if (!indexed) {
|
if (!indexed) {
|
||||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
||||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0);
|
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0, i);
|
||||||
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||||
{.vertex_count = args.vertexCount,
|
{.vertex_count = args.vertexCount,
|
||||||
.instance_count = args.instanceCount,
|
.instance_count = args.instanceCount,
|
||||||
@@ -866,7 +887,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
|||||||
|
|
||||||
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||||
args.firstInstance, args.firstIndex);
|
args.firstInstance, args.firstIndex, i);
|
||||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||||
{.index_count = std::min(args.indexCount, max_indices),
|
{.index_count = std::min(args.indexCount, max_indices),
|
||||||
.index_addr = IndexAddress(args.firstIndex),
|
.index_addr = IndexAddress(args.firstIndex),
|
||||||
@@ -929,11 +950,10 @@ void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||||
// Stall only this command processor, not the host command lane or other queues.
|
BufferFlush();
|
||||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
|
||||||
static_cast<int>(display_buffer_index))) {
|
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||||
SuspendPm4();
|
static_cast<int>(display_buffer_index));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
||||||
@@ -964,16 +984,28 @@ void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector
|
|||||||
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
||||||
m_renderer.GetOcclusionQueries().Publish();
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
|
|
||||||
|
// A label beside GPU-written bytes goes to the backing store, so they are not downloaded.
|
||||||
|
// Other labels go through the guest mapping: its fault keeps a clean cached copy current and
|
||||||
|
// downloads GPU-written bytes under the label before the store.
|
||||||
|
const auto write_label = [this, address](const void* value, uint64_t size) {
|
||||||
|
const auto vaddr = reinterpret_cast<uint64_t>(address);
|
||||||
|
auto& buffers = m_renderer.GetBufferCache();
|
||||||
|
if (!GuestRange {vaddr, size}.Valid() || !buffers.IsRegionGpuModified(vaddr, size) ||
|
||||||
|
buffers.HasGpuDirtyBytes(vaddr, size) ||
|
||||||
|
!LibKernel::Memory::TryWriteBacking(vaddr, value, size)) {
|
||||||
|
std::memcpy(address, value, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
||||||
switch (data_sel) {
|
switch (data_sel) {
|
||||||
case 0: break;
|
case 0: break;
|
||||||
case 1: {
|
case 1: {
|
||||||
const auto value = static_cast<uint32_t>(data);
|
const auto value = static_cast<uint32_t>(data);
|
||||||
std::memcpy(address, &value, sizeof(value));
|
write_label(&value, sizeof(value));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
||||||
case 2: std::memcpy(address, &data, sizeof(data)); break;
|
case 2: write_label(&data, sizeof(data)); break;
|
||||||
case 5: {
|
case 5: {
|
||||||
GetScheduler().Finish();
|
GetScheduler().Finish();
|
||||||
const auto gds = static_cast<uint32_t>(data);
|
const auto gds = static_cast<uint32_t>(data);
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ public:
|
|||||||
[[nodiscard]] bool IsStopping();
|
[[nodiscard]] bool IsStopping();
|
||||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
|
||||||
void Wake();
|
|
||||||
|
|
||||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||||
void Submit(std::span<const uint32_t> commands);
|
void Submit(std::span<const uint32_t> commands);
|
||||||
@@ -84,7 +82,6 @@ private:
|
|||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
bool m_accepting = true;
|
bool m_accepting = true;
|
||||||
bool m_stopping = false;
|
bool m_stopping = false;
|
||||||
bool m_wake_pending = false;
|
|
||||||
bool m_shutdown_complete = false;
|
bool m_shutdown_complete = false;
|
||||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
@@ -17,14 +18,19 @@ public:
|
|||||||
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
||||||
it = std::prev(it);
|
it = std::prev(it);
|
||||||
}
|
}
|
||||||
uint64_t begin = address;
|
if (it == m_ranges.end() || it->first > address) {
|
||||||
uint64_t last = end;
|
it = m_ranges.emplace_hint(it, address, end);
|
||||||
while (it != m_ranges.end() && it->first <= last) {
|
} else {
|
||||||
begin = std::min(begin, it->first);
|
if (it->second >= end) {
|
||||||
last = std::max(last, it->second);
|
return;
|
||||||
it = m_ranges.erase(it);
|
}
|
||||||
|
it->second = end;
|
||||||
|
}
|
||||||
|
auto next = std::next(it);
|
||||||
|
while (next != m_ranges.end() && next->first <= it->second) {
|
||||||
|
it->second = std::max(it->second, next->second);
|
||||||
|
next = m_ranges.erase(next);
|
||||||
}
|
}
|
||||||
m_ranges.emplace(begin, last);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Subtract(uint64_t address, uint64_t size) {
|
void Subtract(uint64_t address, uint64_t size) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -118,6 +119,13 @@ public:
|
|||||||
|
|
||||||
class const_iterator final {
|
class const_iterator final {
|
||||||
public:
|
public:
|
||||||
|
// libstdc++'s algorithms need these. Input, not forward, because operator* returns a copy.
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
using value_type = OwnerT;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using pointer = void;
|
||||||
|
using reference = OwnerT;
|
||||||
|
|
||||||
[[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); }
|
[[nodiscard]] OwnerT operator*() const noexcept { return m_owners->At(m_index); }
|
||||||
const_iterator& operator++() noexcept {
|
const_iterator& operator++() noexcept {
|
||||||
++m_index;
|
++m_index;
|
||||||
|
|||||||
+52
-31
@@ -488,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||||
const bool source_depth = source.info.IsDepth();
|
// Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth.
|
||||||
const bool destination_depth = destination.info.IsDepth();
|
const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t);
|
||||||
if (source_depth == destination_depth) {
|
if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto& depth = source_depth ? source : destination;
|
auto& depth = source_wide ? source : destination;
|
||||||
auto& color = source_depth ? destination : source;
|
auto& narrow = source_wide ? destination : source;
|
||||||
const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format);
|
|
||||||
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
||||||
color.info.bytes_per_block != sizeof(uint16_t) || transfer_bytes != sizeof(uint32_t)) {
|
narrow.info.bytes_per_block != sizeof(uint16_t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||||
@@ -507,42 +506,42 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
|||||||
|
|
||||||
const auto layers = depth.info.resources.layers;
|
const auto layers = depth.info.resources.layers;
|
||||||
const uint64_t depth_slice =
|
const uint64_t depth_slice =
|
||||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * transfer_bytes;
|
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t);
|
||||||
const uint64_t color_slice =
|
const uint64_t narrow_slice =
|
||||||
static_cast<uint64_t>(color.info.pitch) * color.info.extent.height * sizeof(uint16_t);
|
static_cast<uint64_t>(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t);
|
||||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers);
|
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers);
|
||||||
const auto depth_size = depth_slice * layers;
|
const std::array depth_copies {
|
||||||
const auto color_size = color_slice * layers;
|
|
||||||
const std::array depth_copies {
|
|
||||||
vk::BufferImageCopy {0,
|
vk::BufferImageCopy {0,
|
||||||
depth.info.pitch,
|
depth.info.pitch,
|
||||||
depth.info.extent.height,
|
depth.info.extent.height,
|
||||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||||
{},
|
{},
|
||||||
depth.info.extent}};
|
depth.info.extent}};
|
||||||
const std::array color_copies {
|
const std::array narrow_copies {
|
||||||
vk::BufferImageCopy {0,
|
vk::BufferImageCopy {0,
|
||||||
color.info.pitch,
|
narrow.info.pitch,
|
||||||
color.info.extent.height,
|
narrow.info.extent.height,
|
||||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
{narrow.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||||
|
: vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, 0, layers},
|
||||||
{},
|
{},
|
||||||
color.info.extent}};
|
narrow.info.extent}};
|
||||||
|
|
||||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_size);
|
auto depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers);
|
||||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer);
|
||||||
const TileManager::D16Layout promote_layout {
|
const TileManager::D16Layout promote_layout {
|
||||||
.width = depth.info.extent.width,
|
.width = depth.info.extent.width,
|
||||||
.height = depth.info.extent.height,
|
.height = depth.info.extent.height,
|
||||||
.layers = layers,
|
.layers = layers,
|
||||||
.source_row_stride = static_cast<uint64_t>(color.info.pitch) * sizeof(uint16_t),
|
.source_row_stride = static_cast<uint64_t>(narrow.info.pitch) * sizeof(uint16_t),
|
||||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * transfer_bytes,
|
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * sizeof(uint32_t),
|
||||||
.source_slice_stride = color_slice,
|
.source_slice_stride = narrow_slice,
|
||||||
.target_slice_stride = depth_slice,
|
.target_slice_stride = depth_slice,
|
||||||
};
|
};
|
||||||
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
||||||
if (source_depth) {
|
if (source_wide) {
|
||||||
source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size);
|
source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size);
|
||||||
m_tiler.ConvertD16(depth_buffer, color_buffer, TileManager::D16Direction::Demote, d32,
|
m_tiler.ConvertD16(depth_buffer, narrow_buffer, TileManager::D16Direction::Demote, d32,
|
||||||
{.width = promote_layout.width,
|
{.width = promote_layout.width,
|
||||||
.height = promote_layout.height,
|
.height = promote_layout.height,
|
||||||
.layers = promote_layout.layers,
|
.layers = promote_layout.layers,
|
||||||
@@ -550,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
|||||||
.target_row_stride = promote_layout.source_row_stride,
|
.target_row_stride = promote_layout.source_row_stride,
|
||||||
.source_slice_stride = promote_layout.target_slice_stride,
|
.source_slice_stride = promote_layout.target_slice_stride,
|
||||||
.target_slice_stride = promote_layout.source_slice_stride});
|
.target_slice_stride = promote_layout.source_slice_stride});
|
||||||
destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset,
|
destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||||
color_buffer.size);
|
narrow_buffer.size);
|
||||||
} else {
|
} else {
|
||||||
source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size);
|
source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||||
m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
narrow_buffer.size);
|
||||||
|
m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||||
promote_layout);
|
promote_layout);
|
||||||
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
||||||
depth_buffer.size);
|
depth_buffer.size);
|
||||||
@@ -783,7 +783,15 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
|||||||
if (!cached.registered) {
|
if (!cached.registered) {
|
||||||
return {merged_id};
|
return {merged_id};
|
||||||
}
|
}
|
||||||
if (BcAlias::Matches(requested, cached.info) && !cached.IsCpuDirty()) {
|
// PS5: image heaps keep BC images and raw block views of their layers resident together, so a
|
||||||
|
// raw read of a layer subset with the same levels views the BC image instead of evicting it.
|
||||||
|
const bool raw_view = binding == BindingType::Texture && !requested.IsBlock() &&
|
||||||
|
requested.type == cached.info.type &&
|
||||||
|
requested.data.address == cached.info.data.address &&
|
||||||
|
requested.BlockExtent() == cached.info.BlockExtent() &&
|
||||||
|
requested.resources.levels == cached.info.resources.levels &&
|
||||||
|
requested.resources.layers < cached.info.resources.layers;
|
||||||
|
if (!raw_view && BcAlias::Matches(requested, cached.info) && !cached.IsCpuDirty()) {
|
||||||
BcAlias::Resolve(*this, cached_id, merged_id);
|
BcAlias::Resolve(*this, cached_id, merged_id);
|
||||||
return {merged_id};
|
return {merged_id};
|
||||||
}
|
}
|
||||||
@@ -1302,6 +1310,19 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
|||||||
view_layer = overlap.layer;
|
view_layer = overlap.layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// PS5: image heaps hold a descriptor per dynamic-resolution extent of a render target,
|
||||||
|
// and only one extent at an address holds data, so heap lookups create no second one.
|
||||||
|
if (!result && desc.heap &&
|
||||||
|
std::any_of(candidates.begin(), candidates.end(), [&](ImageId id) {
|
||||||
|
const auto* image = m_slot_images.try_get(id);
|
||||||
|
return image != nullptr && image->registered &&
|
||||||
|
image->heap_refs.load(std::memory_order_relaxed) != 0u &&
|
||||||
|
image->info.data.address == desc.info.data.address &&
|
||||||
|
image->info.type == desc.info.type &&
|
||||||
|
image->info.extent != desc.info.extent;
|
||||||
|
})) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
} else if (block_alias) {
|
} else if (block_alias) {
|
||||||
for (const auto candidate: candidates) {
|
for (const auto candidate: candidates) {
|
||||||
const auto& owner = m_slot_images[candidate];
|
const auto& owner = m_slot_images[candidate];
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
ImageInfo info;
|
ImageInfo info;
|
||||||
ImageViewInfo view_info;
|
ImageViewInfo view_info;
|
||||||
BindingType type = BindingType::Texture;
|
BindingType type = BindingType::Texture;
|
||||||
|
bool heap = false; // PS5: resolved for a resident image heap.
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||||
|
|||||||
@@ -125,6 +125,14 @@ void DynamicState::Commit(const GraphicContext& graphics, vk::CommandBuffer cmdb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (dirty_state.cull_mode) {
|
||||||
|
dirty_state.cull_mode = false;
|
||||||
|
cmdbuf.setCullMode(cull_mode);
|
||||||
|
}
|
||||||
|
if (dirty_state.front_face) {
|
||||||
|
dirty_state.front_face = false;
|
||||||
|
cmdbuf.setFrontFace(front_face);
|
||||||
|
}
|
||||||
if (dirty_state.blend_constants) {
|
if (dirty_state.blend_constants) {
|
||||||
dirty_state.blend_constants = false;
|
dirty_state.blend_constants = false;
|
||||||
cmdbuf.setBlendConstants(blend_constants.data());
|
cmdbuf.setBlendConstants(blend_constants.data());
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ struct DynamicState {
|
|||||||
bool stencil_back_write_mask : 1;
|
bool stencil_back_write_mask : 1;
|
||||||
bool stencil_back_compare_mask : 1;
|
bool stencil_back_compare_mask : 1;
|
||||||
|
|
||||||
|
bool cull_mode : 1;
|
||||||
|
bool front_face : 1;
|
||||||
|
|
||||||
bool blend_constants : 1;
|
bool blend_constants : 1;
|
||||||
bool color_write_enables : 1;
|
bool color_write_enables : 1;
|
||||||
bool line_width : 1;
|
bool line_width : 1;
|
||||||
@@ -86,6 +89,9 @@ struct DynamicState {
|
|||||||
uint32_t stencil_back_write_mask {};
|
uint32_t stencil_back_write_mask {};
|
||||||
uint32_t stencil_back_compare_mask {};
|
uint32_t stencil_back_compare_mask {};
|
||||||
|
|
||||||
|
vk::CullModeFlags cull_mode {};
|
||||||
|
vk::FrontFace front_face {};
|
||||||
|
|
||||||
std::array<float, 4> blend_constants {};
|
std::array<float, 4> blend_constants {};
|
||||||
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> color_write_enables {};
|
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> color_write_enables {};
|
||||||
uint32_t num_color_write_enables {};
|
uint32_t num_color_write_enables {};
|
||||||
@@ -221,6 +227,20 @@ struct DynamicState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetCullMode(const vk::CullModeFlags cull_mode_) {
|
||||||
|
if (cull_mode != cull_mode_) {
|
||||||
|
cull_mode = cull_mode_;
|
||||||
|
dirty_state.cull_mode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFrontFace(const vk::FrontFace front_face_) {
|
||||||
|
if (front_face != front_face_) {
|
||||||
|
front_face = front_face_;
|
||||||
|
dirty_state.front_face = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SetBlendConstants(const std::array<float, 4> blend_constants_) {
|
void SetBlendConstants(const std::array<float, 4> blend_constants_) {
|
||||||
if (blend_constants != blend_constants_) {
|
if (blend_constants != blend_constants_) {
|
||||||
blend_constants = blend_constants_;
|
blend_constants = blend_constants_;
|
||||||
|
|||||||
@@ -425,15 +425,15 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
|||||||
const auto destination_height =
|
const auto destination_height =
|
||||||
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
||||||
const bool copy = source.backing.samples == 1;
|
const bool copy = source.backing.samples == 1;
|
||||||
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
|
EXIT_IF(layers == 0 ||
|
||||||
info.extent.width > destination_width || info.extent.height > destination_height ||
|
|
||||||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
||||||
: source.backing.format != backing.format));
|
: source.backing.format != backing.format));
|
||||||
auto resolved_source_range = source_range;
|
auto resolved_source_range = source_range;
|
||||||
auto resolved_destination_range = destination_range;
|
auto resolved_destination_range = destination_range;
|
||||||
resolved_source_range.layer_count = layers;
|
resolved_source_range.layer_count = layers;
|
||||||
resolved_destination_range.layer_count = layers;
|
resolved_destination_range.layer_count = layers;
|
||||||
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
|
const vk::Extent3D resolve_extent {std::min(source_width, destination_width),
|
||||||
|
std::min(source_height, destination_height), 1};
|
||||||
|
|
||||||
m_scheduler.EndRendering();
|
m_scheduler.EndRendering();
|
||||||
auto command = m_scheduler.Current().Handle();
|
auto command = m_scheduler.Current().Handle();
|
||||||
|
|||||||
@@ -378,6 +378,9 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
|||||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||||
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||||
};
|
};
|
||||||
|
const auto unorm16 = [](uint64_t value) {
|
||||||
|
return static_cast<float>(value & 0xffffu) / 65535.0f;
|
||||||
|
};
|
||||||
const auto float16 = [](uint32_t value) {
|
const auto float16 = [](uint32_t value) {
|
||||||
const auto sign = (value & 0x8000u) << 16u;
|
const auto sign = (value & 0x8000u) << 16u;
|
||||||
const auto exponent = (value >> 10u) & 0x1fu;
|
const auto exponent = (value >> 10u) & 0x1fu;
|
||||||
@@ -403,19 +406,49 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
|||||||
next.float32[1] = float16(packed >> 16u);
|
next.float32[1] = float16(packed >> 16u);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
||||||
case vk::Format::eR16G16Unorm:
|
case vk::Format::eR16G16B16A16Unorm:
|
||||||
next.float32[1] = static_cast<float>(packed >> 16u) / 65535.0f;
|
next.float32[2] = unorm16(clear_words >> 32u);
|
||||||
|
next.float32[3] = unorm16(clear_words >> 48u);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case vk::Format::eR16Unorm:
|
case vk::Format::eR16G16Unorm:
|
||||||
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
|
next.float32[1] = unorm16(packed >> 16u);
|
||||||
|
[[fallthrough]];
|
||||||
|
case vk::Format::eR16Unorm: next.float32[0] = unorm16(packed); break;
|
||||||
|
case vk::Format::eR16G16B16A16Uint:
|
||||||
|
for (uint32_t i = 0; i < 4; i++) {
|
||||||
|
next.uint32[i] = static_cast<uint16_t>(clear_words >> (16u * i));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
// Each clear register stores one raw 32-bit float channel.
|
case vk::Format::eR16G16B16A16Sint:
|
||||||
|
for (uint32_t i = 0; i < 4; i++) {
|
||||||
|
next.int32[i] = static_cast<int16_t>(clear_words >> (16u * i));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Each clear register stores one raw 32-bit channel.
|
||||||
case vk::Format::eR32G32Sfloat:
|
case vk::Format::eR32G32Sfloat:
|
||||||
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
||||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
case vk::Format::eR32G32Uint:
|
||||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
case vk::Format::eR32G32Sint:
|
||||||
|
next.uint32[1] = static_cast<uint32_t>(clear_words >> 32u);
|
||||||
|
[[fallthrough]];
|
||||||
|
case vk::Format::eR32Uint:
|
||||||
|
case vk::Format::eR32Sint: next.uint32[0] = packed; break;
|
||||||
|
// Narrow targets hold their channels in the low bits of the first word. UFC 6 (PPSA23566)
|
||||||
|
// fast-clears an R8_UINT map through CMASK every frame and then writes only part of it;
|
||||||
|
// without these the clear was dropped and old contents piled up.
|
||||||
|
case vk::Format::eR8Uint: next.uint32[0] = packed & 0xffu; break;
|
||||||
|
case vk::Format::eR8Unorm: next.float32[0] = unorm8(packed); break;
|
||||||
|
case vk::Format::eR8G8Uint:
|
||||||
|
next.uint32[0] = packed & 0xffu;
|
||||||
|
next.uint32[1] = (packed >> 8u) & 0xffu;
|
||||||
|
break;
|
||||||
|
case vk::Format::eR8G8Unorm:
|
||||||
|
next.float32[0] = unorm8(packed);
|
||||||
|
next.float32[1] = unorm8(packed >> 8u);
|
||||||
|
break;
|
||||||
|
case vk::Format::eR16Uint: next.uint32[0] = packed & 0xffffu; break;
|
||||||
case vk::Format::eR8G8B8A8Srgb:
|
case vk::Format::eR8G8B8A8Srgb:
|
||||||
next.float32[0] = srgb8(packed);
|
next.float32[0] = srgb8(packed);
|
||||||
next.float32[1] = srgb8(packed >> 8u);
|
next.float32[1] = srgb8(packed >> 8u);
|
||||||
@@ -462,10 +495,16 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
|||||||
// Keep this separate: the first register word alone cannot describe a 64-bit color.
|
// Keep this separate: the first register word alone cannot describe a 64-bit color.
|
||||||
[[nodiscard]] inline bool DecodeColorDwordFill(vk::Format format, uint32_t packed,
|
[[nodiscard]] inline bool DecodeColorDwordFill(vk::Format format, uint32_t packed,
|
||||||
vk::ClearColorValue& clear) {
|
vk::ClearColorValue& clear) {
|
||||||
if ((format == vk::Format::eR16Sfloat || format == vk::Format::eR16Unorm) &&
|
if ((format == vk::Format::eR16Sfloat || format == vk::Format::eR16Unorm ||
|
||||||
|
format == vk::Format::eR16Uint || format == vk::Format::eR8G8Unorm ||
|
||||||
|
format == vk::Format::eR8G8Uint) &&
|
||||||
(packed & 0xffffu) != (packed >> 16u)) {
|
(packed & 0xffffu) != (packed >> 16u)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if ((format == vk::Format::eR8Unorm || format == vk::Format::eR8Uint) &&
|
||||||
|
packed != (packed & 0xffu) * 0x01010101u) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (format == vk::Format::eR16G16B16A16Sfloat) {
|
if (format == vk::Format::eR16G16B16A16Sfloat) {
|
||||||
if (packed != 0) {
|
if (packed != 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
|||||||
vk::AccessFlagBits::eHostWrite |
|
vk::AccessFlagBits::eHostWrite |
|
||||||
vk::AccessFlagBits::eTransferWrite |
|
vk::AccessFlagBits::eTransferWrite |
|
||||||
vk::AccessFlagBits::eShaderWrite;
|
vk::AccessFlagBits::eShaderWrite;
|
||||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||||
command.pipelineBarrier(
|
command.pipelineBarrier(
|
||||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||||
|
|||||||
@@ -604,8 +604,13 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
|||||||
view_levels, desc.info.resources.layers);
|
view_levels, desc.info.resources.layers);
|
||||||
desc.view_info.base_level = view_base;
|
desc.view_info.base_level = view_base;
|
||||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||||
|
desc.heap = resource.heap;
|
||||||
|
|
||||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||||
|
if (!id) {
|
||||||
|
// Another extent at the address holds it; the heap retries once an image is created.
|
||||||
|
return {.desc = std::move(desc), .layout = vk::ImageLayout::eGeneral};
|
||||||
|
}
|
||||||
auto* image = &texture_cache.GetImage(id);
|
auto* image = &texture_cache.GetImage(id);
|
||||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||||
if (stencil_association) {
|
if (stencil_association) {
|
||||||
@@ -801,6 +806,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
|||||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||||
resource, buffer_offset);
|
resource, buffer_offset);
|
||||||
pack_memory_offset(index, buffer_offset);
|
pack_memory_offset(index, buffer_offset);
|
||||||
|
// Null bindings allow 0 bytes, not the shared null buffer's 16, so no store reaches it.
|
||||||
|
prepared.shader_data[layout.MemorySizeDword() + index] =
|
||||||
|
static_cast<uint32_t>(prepared.buffer_sources[index].size + buffer_offset);
|
||||||
}
|
}
|
||||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
||||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "kernel/memory.h"
|
#include "kernel/memory.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
#include <xxhash.h>
|
#include <xxhash.h>
|
||||||
|
|
||||||
namespace Libs::Graphics {
|
namespace Libs::Graphics {
|
||||||
@@ -19,7 +20,7 @@ namespace {
|
|||||||
|
|
||||||
namespace IR = ShaderRecompiler::IR;
|
namespace IR = ShaderRecompiler::IR;
|
||||||
|
|
||||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
// Smaller tables are often written for one draw, so they enter on a second draw of a frame.
|
||||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||||
|
|
||||||
@@ -44,6 +45,20 @@ void Widen(size_t& begin, size_t& end, size_t first, size_t last) {
|
|||||||
end = empty ? last : std::max(end, last);
|
end = empty ? last : std::max(end, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Probes at first + k * step whose first dword lies within a table of size bytes.
|
||||||
|
uint64_t Probes(uint64_t size, uint64_t first, uint64_t step) {
|
||||||
|
return size >= first + 4u ? (size - first - 4u) / step + 1u : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables grow a little on most frames, so their slot words get room to double.
|
||||||
|
std::unique_ptr<Buffer> SlotBuffer(RenderContext& context, uint64_t size) {
|
||||||
|
return std::make_unique<Buffer>(
|
||||||
|
context.GetGraphics(), context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
||||||
|
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||||
|
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||||
|
std::bit_ceil(size + 4u));
|
||||||
|
}
|
||||||
|
|
||||||
// Guest bytes of a table that its tracker pages [begin, end) cover.
|
// Guest bytes of a table that its tracker pages [begin, end) cover.
|
||||||
std::pair<uint64_t, uint64_t> PageRange(uint64_t base, uint64_t size, size_t begin, size_t end) {
|
std::pair<uint64_t, uint64_t> PageRange(uint64_t base, uint64_t size, size_t begin, size_t end) {
|
||||||
const auto origin = Common::AlignDown(base, TRACKER_PAGE_SIZE);
|
const auto origin = Common::AlignDown(base, TRACKER_PAGE_SIZE);
|
||||||
@@ -118,26 +133,25 @@ ImageHeap::~ImageHeap() {
|
|||||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
const auto size = request.size & ~uint64_t {3};
|
const auto size = request.size & ~uint64_t {3};
|
||||||
if (size < MinTableBytes || request.count == 0u || request.step == 0u ||
|
if (request.count == 0u || request.step == 0u ||
|
||||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto memory = std::ranges::find_if(m_memories, [&](const auto& candidate) {
|
auto memory = std::ranges::find_if(
|
||||||
return candidate->base == request.base && candidate->size == size;
|
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
||||||
});
|
if (memory == m_memories.end() && size < MinTableBytes &&
|
||||||
if (memory == m_memories.end()) {
|
m_sightings.try_emplace(request.base, m_draw).first->second == m_draw) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||||
|
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||||
if (!m_context.IsMapped(request.base, size)) return false;
|
if (!m_context.IsMapped(request.base, size)) return false;
|
||||||
auto created = std::make_unique<Memory>();
|
if (memory == m_memories.end()) {
|
||||||
created->base = request.base;
|
std::scoped_lock lock {m_mutex};
|
||||||
created->size = size;
|
memory = m_memories.insert(m_memories.end(),
|
||||||
created->words.resize(size / 4u);
|
std::make_unique<Memory>(Memory {.base = request.base}));
|
||||||
created->dead.resize(size / 4u);
|
}
|
||||||
created->dirty_pages.assign((Common::AlignUp(request.base + size, TRACKER_PAGE_SIZE) -
|
Resize(**memory, size);
|
||||||
Common::AlignDown(request.base, TRACKER_PAGE_SIZE)) /
|
|
||||||
TRACKER_PAGE_SIZE,
|
|
||||||
uint8_t {1});
|
|
||||||
std::scoped_lock lock {m_mutex};
|
|
||||||
memory = m_memories.insert(m_memories.end(), std::move(created));
|
|
||||||
}
|
}
|
||||||
// A draw refreshes a table and a view takes its changes once, so all programs of the draw see
|
// A draw refreshes a table and a view takes its changes once, so all programs of the draw see
|
||||||
// one snapshot and one set of kinds.
|
// one snapshot and one set of kinds.
|
||||||
@@ -146,27 +160,28 @@ bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView&
|
|||||||
(*memory)->draw = m_draw;
|
(*memory)->draw = m_draw;
|
||||||
}
|
}
|
||||||
auto& views = (*memory)->views;
|
auto& views = (*memory)->views;
|
||||||
|
const auto bytes = (*memory)->size;
|
||||||
const auto profile = IR::ImageHeapResource(*request.root, 0u);
|
const auto profile = IR::ImageHeapResource(*request.root, 0u);
|
||||||
|
const auto probes = Probes(size, request.first, request.step);
|
||||||
|
const auto limit = request.count < probes ? request.count : UINT64_MAX;
|
||||||
auto view = std::ranges::find_if(views, [&](const auto& candidate) {
|
auto view = std::ranges::find_if(views, [&](const auto& candidate) {
|
||||||
return candidate->step == request.step && candidate->first == request.first &&
|
return candidate->step == request.step && candidate->first == request.first &&
|
||||||
candidate->count == request.count && candidate->profile == profile;
|
candidate->limit == limit && candidate->profile == profile;
|
||||||
});
|
});
|
||||||
if (view == views.end()) {
|
if (view == views.end()) {
|
||||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||||
|
const auto count = std::min(limit, Probes(bytes, request.first, request.step));
|
||||||
auto created = std::make_unique<View>(View {.memory = **memory,
|
auto created = std::make_unique<View>(View {.memory = **memory,
|
||||||
.step = request.step,
|
.step = request.step,
|
||||||
.first = request.first,
|
.first = request.first,
|
||||||
.count = request.count,
|
.count = count,
|
||||||
|
.limit = limit,
|
||||||
.profile = profile,
|
.profile = profile,
|
||||||
.resolve_epoch = epoch,
|
.resolve_epoch = epoch,
|
||||||
.layout_epoch = epoch});
|
.layout_epoch = epoch});
|
||||||
created->slots.resize(size / 4u + 1u);
|
created->slots.resize(bytes / 4u + 1u);
|
||||||
created->buffer = std::make_unique<Buffer>(
|
created->buffer = SlotBuffer(m_context, bytes);
|
||||||
m_context.GetGraphics(), m_context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
UpdateProbes(*created, 0u, bytes);
|
||||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
|
||||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
|
||||||
size + 4u);
|
|
||||||
UpdateProbes(*created, 0u, size);
|
|
||||||
// The first upload also clears the slots no probe selects.
|
// The first upload also clears the slots no probe selects.
|
||||||
created->dirty_begin = 0;
|
created->dirty_begin = 0;
|
||||||
created->dirty_end = created->slots.size();
|
created->dirty_end = created->slots.size();
|
||||||
@@ -254,10 +269,12 @@ void ImageHeap::Unwatch(const Memory& memory) {
|
|||||||
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||||
std::scoped_lock lock {m_mutex};
|
std::scoped_lock lock {m_mutex};
|
||||||
for (auto& memory: m_memories) {
|
for (auto& memory: m_memories) {
|
||||||
const auto begin = std::max(address, memory->base);
|
// Watches cover whole pages, so a write next to the table must unwatch its page too.
|
||||||
const auto end = std::min(address + size, memory->base + memory->size);
|
|
||||||
if (begin >= end) continue;
|
|
||||||
const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE);
|
const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE);
|
||||||
|
const auto begin = std::max(address, origin);
|
||||||
|
const auto end = std::min(address + size,
|
||||||
|
origin + memory->dirty_pages.size() * TRACKER_PAGE_SIZE);
|
||||||
|
if (begin >= end) continue;
|
||||||
MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE,
|
MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE,
|
||||||
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
||||||
}
|
}
|
||||||
@@ -381,15 +398,56 @@ uint32_t ImageHeap::Allocate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImageHeap::Retire(View& view) {
|
void ImageHeap::Retire(View& view) {
|
||||||
|
if (view.released.empty()) return;
|
||||||
|
// A retired index is reused once its tick ends, so no held entry may keep it.
|
||||||
|
std::erase_if(view.held, [&](uint32_t index) { return m_elements[index].refs == 0u; });
|
||||||
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
||||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||||
view.released.clear();
|
view.released.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageHeap::Resize(Memory& memory, uint64_t size) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock {m_mutex};
|
||||||
|
Unwatch(memory);
|
||||||
|
memory.size = size;
|
||||||
|
// The next refresh rereads the whole table.
|
||||||
|
memory.dirty_pages.assign((Common::AlignUp(memory.base + size, TRACKER_PAGE_SIZE) -
|
||||||
|
Common::AlignDown(memory.base, TRACKER_PAGE_SIZE)) /
|
||||||
|
TRACKER_PAGE_SIZE,
|
||||||
|
uint8_t {1});
|
||||||
|
}
|
||||||
|
memory.words.resize(size / 4u);
|
||||||
|
memory.dead.resize(size / 4u);
|
||||||
|
auto& scheduler = m_context.GetCommandScheduler();
|
||||||
|
for (auto& view: memory.views) {
|
||||||
|
const auto count = std::min(view->limit, Probes(size, view->first, view->step));
|
||||||
|
for (auto probe = count; probe < view->count; ++probe) {
|
||||||
|
Assign(*view, (view->first + probe * view->step) / 4u, 0u);
|
||||||
|
}
|
||||||
|
view->count = count;
|
||||||
|
const auto slots = view->slots.size();
|
||||||
|
view->slots.resize(size / 4u + 1u);
|
||||||
|
view->dirty_end = std::min(view->dirty_end, view->slots.size());
|
||||||
|
view->dirty_begin = std::min(view->dirty_begin, view->dirty_end);
|
||||||
|
if (view->slots.size() * sizeof(uint32_t) > view->buffer->Size()) {
|
||||||
|
if (scheduler.Active()) {
|
||||||
|
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||||
|
}
|
||||||
|
view->buffer = SlotBuffer(m_context, size);
|
||||||
|
Widen(view->dirty_begin, view->dirty_end, 0u, view->slots.size());
|
||||||
|
} else if (slots < view->slots.size()) {
|
||||||
|
// Device words past the old end may hold stale slots.
|
||||||
|
Widen(view->dirty_begin, view->dirty_end, slots, view->slots.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ImageHeap::Rebind(RenderExecutor& executor) {
|
void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
auto& textures = m_context.GetTextureCache();
|
auto& textures = m_context.GetTextureCache();
|
||||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||||
|
const auto serial = Image::serials.load(std::memory_order_relaxed);
|
||||||
for (auto* view: m_active) {
|
for (auto* view: m_active) {
|
||||||
bool stale = false;
|
bool stale = false;
|
||||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||||
@@ -418,6 +476,19 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
|||||||
mark(index);
|
mark(index);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
// Held elements retry once an image is created, such as a render target of their extent.
|
||||||
|
if (view->held_serial != serial) {
|
||||||
|
view->held_serial = serial;
|
||||||
|
std::erase_if(view->held, [&](uint32_t index) {
|
||||||
|
const auto& element = m_elements[index];
|
||||||
|
if (element.refs == 0u || element.stale) return true;
|
||||||
|
const auto binding = executor.ResolveTexture(
|
||||||
|
IR::ImageHeapResource(view->profile, element.kind), element.value);
|
||||||
|
if (binding.image_id) mark(index);
|
||||||
|
// An unmapped texture stays null.
|
||||||
|
return binding.image_id || binding.desc.info.data.Empty();
|
||||||
|
});
|
||||||
|
}
|
||||||
if (stale) {
|
if (stale) {
|
||||||
// A changed image needs a new element; pending draws still read the old one. The new
|
// A changed image needs a new element; pending draws still read the old one. The new
|
||||||
// element keeps the old value, since a view takes table changes only when acquired.
|
// element keeps the old value, since a view takes table changes only when acquired.
|
||||||
@@ -446,6 +517,8 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
|||||||
view->dcc.push_back(index);
|
view->dcc.push_back(index);
|
||||||
}
|
}
|
||||||
view->away.push_back(index);
|
view->away.push_back(index);
|
||||||
|
} else if (!binding.desc.info.data.Empty()) {
|
||||||
|
view->held.push_back(index);
|
||||||
}
|
}
|
||||||
m_elements[index].binding = std::move(binding);
|
m_elements[index].binding = std::move(binding);
|
||||||
m_writes.push_back(index);
|
m_writes.push_back(index);
|
||||||
@@ -524,26 +597,42 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
|||||||
} else {
|
} else {
|
||||||
m_unmapped.push_back({address, size});
|
m_unmapped.push_back({address, size});
|
||||||
}
|
}
|
||||||
auto& scheduler = m_context.GetCommandScheduler();
|
|
||||||
std::scoped_lock lock {m_mutex};
|
std::scoped_lock lock {m_mutex};
|
||||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Unwatch(*memory);
|
Drop(*memory);
|
||||||
for (auto& view: memory->views) {
|
|
||||||
std::erase(m_active, view.get());
|
|
||||||
for (const auto& [value, index]: view->elements) {
|
|
||||||
Unreference(m_elements[index]);
|
|
||||||
view->released.push_back(index);
|
|
||||||
}
|
|
||||||
Retire(*view);
|
|
||||||
if (scheduler.Active()) {
|
|
||||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageHeap::RunGarbageCollector() {
|
||||||
|
// A smaller table rebuilt each frame moves to a new base. Larger ones keep their dead words.
|
||||||
|
std::scoped_lock lock {m_mutex};
|
||||||
|
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||||
|
if (memory->size >= MinTableBytes || memory->draw > m_suspend_draw) return false;
|
||||||
|
Drop(*memory);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
m_suspend_draw = m_draw;
|
||||||
|
m_sightings.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageHeap::Drop(Memory& memory) {
|
||||||
|
auto& scheduler = m_context.GetCommandScheduler();
|
||||||
|
Unwatch(memory);
|
||||||
|
for (auto& view: memory.views) {
|
||||||
|
std::erase(m_active, view.get());
|
||||||
|
for (const auto& [value, index]: view->elements) {
|
||||||
|
Unreference(m_elements[index]);
|
||||||
|
view->released.push_back(index);
|
||||||
|
}
|
||||||
|
Retire(*view);
|
||||||
|
if (scheduler.Active()) {
|
||||||
|
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics
|
} // namespace Libs::Graphics
|
||||||
@@ -22,10 +22,11 @@ class RenderExecutor;
|
|||||||
struct ImageHeapTestAccess;
|
struct ImageHeapTestAccess;
|
||||||
|
|
||||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
||||||
// sampled-image array. Each view of a table owns device slot words, one per table dword, that
|
// sampled-image array. A table is keyed by its base and follows its descriptor's size. Each view
|
||||||
// select an array element. CPU writes to a table fault in its snapshot, which each draw refreshes
|
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||||
// once; a view takes the changes when a draw first acquires it. Changes to the images behind
|
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||||
// elements bump Image::heap_epoch.
|
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||||
|
// Image::heap_epoch. A smaller table leaves at a suspend point if the frame did not acquire it.
|
||||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||||
public:
|
public:
|
||||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||||
@@ -46,6 +47,8 @@ public:
|
|||||||
// CPU write fault, on any thread.
|
// CPU write fault, on any thread.
|
||||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||||
void UnmapMemory(uint64_t address, uint64_t size);
|
void UnmapMemory(uint64_t address, uint64_t size);
|
||||||
|
// At a suspend point.
|
||||||
|
void RunGarbageCollector();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||||
@@ -70,6 +73,7 @@ private:
|
|||||||
uint64_t step = 0;
|
uint64_t step = 0;
|
||||||
uint64_t first = 0;
|
uint64_t first = 0;
|
||||||
uint64_t count = 0;
|
uint64_t count = 0;
|
||||||
|
uint64_t limit = 0; // Probes a key domain allows, if fewer.
|
||||||
ShaderRecompiler::IR::ImageResource profile;
|
ShaderRecompiler::IR::ImageResource profile;
|
||||||
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
||||||
std::vector<uint32_t> kind_keys;
|
std::vector<uint32_t> kind_keys;
|
||||||
@@ -79,6 +83,7 @@ private:
|
|||||||
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
||||||
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
||||||
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
||||||
|
std::vector<uint32_t> held; // Another extent holds their address.
|
||||||
std::unique_ptr<Buffer> buffer;
|
std::unique_ptr<Buffer> buffer;
|
||||||
size_t dirty_begin = 0; // Slots to upload.
|
size_t dirty_begin = 0; // Slots to upload.
|
||||||
size_t dirty_end = 0;
|
size_t dirty_end = 0;
|
||||||
@@ -86,6 +91,7 @@ private:
|
|||||||
size_t changed_end = 0;
|
size_t changed_end = 0;
|
||||||
uint64_t resolve_epoch = 0;
|
uint64_t resolve_epoch = 0;
|
||||||
uint64_t layout_epoch = 0;
|
uint64_t layout_epoch = 0;
|
||||||
|
uint64_t held_serial = 0; // Image::serials when held was last retried.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Memory {
|
struct Memory {
|
||||||
@@ -103,6 +109,7 @@ private:
|
|||||||
template <bool track>
|
template <bool track>
|
||||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||||
void Unwatch(const Memory& memory);
|
void Unwatch(const Memory& memory);
|
||||||
|
void Drop(Memory& memory);
|
||||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||||
void UpdateProbe(View& view, uint64_t probe);
|
void UpdateProbe(View& view, uint64_t probe);
|
||||||
void Assign(View& view, size_t slot, uint32_t entry);
|
void Assign(View& view, size_t slot, uint32_t entry);
|
||||||
@@ -113,6 +120,7 @@ private:
|
|||||||
Image* ImageOf(const Element& element) const;
|
Image* ImageOf(const Element& element) const;
|
||||||
uint32_t Allocate();
|
uint32_t Allocate();
|
||||||
void Retire(View& view);
|
void Retire(View& view);
|
||||||
|
void Resize(Memory& memory, uint64_t size);
|
||||||
void WriteDescriptors();
|
void WriteDescriptors();
|
||||||
|
|
||||||
RenderContext& m_context;
|
RenderContext& m_context;
|
||||||
@@ -120,9 +128,11 @@ private:
|
|||||||
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
||||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||||
std::vector<View*> m_active; // Acquired since the last commit.
|
std::vector<View*> m_active; // Acquired since the last commit.
|
||||||
uint64_t m_draw = 1;
|
uint64_t m_draw = 1;
|
||||||
|
uint64_t m_suspend_draw = 0; // m_draw at the last suspend point.
|
||||||
std::vector<Element> m_elements;
|
std::vector<Element> m_elements;
|
||||||
std::vector<uint32_t> m_free;
|
std::vector<uint32_t> m_free;
|
||||||
|
std::unordered_map<uint64_t, uint64_t> m_sightings; // Smaller tables: base, first draw.
|
||||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||||
std::vector<uint32_t> m_scratch;
|
std::vector<uint32_t> m_scratch;
|
||||||
|
|||||||
@@ -687,6 +687,9 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
vertex_params[0] = PrepareProgram(vertex_regs, context, user_config, vertex_info[0]);
|
||||||
}
|
}
|
||||||
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
const bool mesh_active = vertex_info[0].logical_stage == ShaderType::Mesh;
|
||||||
|
if (!tess_active && !mesh_active) {
|
||||||
|
vertex_info[0].indirect_offsets = state.indirect_offsets;
|
||||||
|
}
|
||||||
if (object_primitive_id) {
|
if (object_primitive_id) {
|
||||||
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
// CxObjectIdMode: register source plus a 32-bit pipelined primitive-ID addition.
|
||||||
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
EXIT_NOT_IMPLEMENTED(sh.m_paClObjprimIdCntl != 2u || mesh_active || tess_active ||
|
||||||
@@ -711,8 +714,13 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
mesh.max_vertices > limits.maxMeshOutputVertices ||
|
mesh.max_vertices > limits.maxMeshOutputVertices ||
|
||||||
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
|
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
|
||||||
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
|
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
|
||||||
EXIT("mesh shader exceeds host limits: threads=%u vertices=%u primitives=%u LDS=%u\n",
|
static std::atomic_bool warned = false;
|
||||||
host_threads, mesh.max_vertices, mesh.max_primitives, mesh.lds_size_dwords);
|
if (!warned.exchange(true, std::memory_order_relaxed)) {
|
||||||
|
Log::WriteToConsoleAndLog(fmt::format(
|
||||||
|
"Warning: dropping mesh draws the host does not support (shader 0x{:016x}).\n",
|
||||||
|
vertex_params[0].hash));
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ShaderParams pixel_params;
|
ShaderParams pixel_params;
|
||||||
@@ -736,6 +744,11 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
const auto& rt = context.GetRenderTarget(slot);
|
const auto& rt = context.GetRenderTarget(slot);
|
||||||
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
|
const auto target_mask = render_target_mask_slot(context.GetRenderTargetMask(), slot);
|
||||||
if (rt.base.addr == 0 || target_mask == 0u) continue;
|
if (rt.base.addr == 0 || target_mask == 0u) continue;
|
||||||
|
if (rt.info.channel_type == Prospero::ChannelType::kUInt) {
|
||||||
|
pixel_info.target_uint_mask |= 1u << export_index;
|
||||||
|
} else if (rt.info.channel_type == Prospero::ChannelType::kSInt) {
|
||||||
|
pixel_info.target_sint_mask |= 1u << export_index;
|
||||||
|
}
|
||||||
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
|
const auto format = Prospero::ResolveRenderTargetFormat(rt.info.format, rt.info.channel_type);
|
||||||
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
|
if (format.buffer_format != Prospero::BufferFormat::k11_11_10UNorm) continue;
|
||||||
const auto mapping = pixel_info.target_export_mapping[export_index];
|
const auto mapping = pixel_info.target_export_mapping[export_index];
|
||||||
@@ -767,9 +780,11 @@ void PipelineCache::GetGraphicsPrograms(const CommandBuffer& command, DrawRender
|
|||||||
(blend.separate_alpha_blend && (BlendFactorIsDualSource(blend.alpha_srcblend) ||
|
(blend.separate_alpha_blend && (BlendFactorIsDualSource(blend.alpha_srcblend) ||
|
||||||
BlendFactorIsDualSource(blend.alpha_destblend))));
|
BlendFactorIsDualSource(blend.alpha_destblend))));
|
||||||
if (pixel_info.dual_source_blending) {
|
if (pixel_info.dual_source_blending) {
|
||||||
// MRT1 supplies the second blend source for target 0.
|
// MRT1 supplies the second blend source for target 0; blended targets are not integer.
|
||||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
||||||
|
pixel_info.target_uint_mask &= ~2u;
|
||||||
|
pixel_info.target_sint_mask &= ~2u;
|
||||||
} else if (blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
} else if (blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||||
pixel_info.target_output_mode[0] != 0 && pixel_info.target_output_mode[0] != 7 &&
|
pixel_info.target_output_mode[0] != 0 && pixel_info.target_output_mode[0] != 7 &&
|
||||||
std::all_of(std::begin(pixel_info.target_output_mode) + 1,
|
std::all_of(std::begin(pixel_info.target_output_mode) + 1,
|
||||||
@@ -991,15 +1006,11 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(const CommandBuffer&
|
|||||||
}
|
}
|
||||||
static_params.alpha_to_coverage_enable = ps_active && ctx.GetShaderRegisters().AlphaToMask();
|
static_params.alpha_to_coverage_enable = ps_active && ctx.GetShaderRegisters().AlphaToMask();
|
||||||
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
||||||
static_params.cull_back = !rect_list && mc.cull_back;
|
|
||||||
static_params.cull_front = !rect_list && mc.cull_front;
|
|
||||||
static_params.face = mc.face;
|
|
||||||
static_params.provoking_vtx_last = mc.provoking_vtx_last;
|
static_params.provoking_vtx_last = mc.provoking_vtx_last;
|
||||||
static_params.polygon_mode =
|
static_params.polygon_mode =
|
||||||
ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back);
|
ResolvePolygonMode(mc, !rect_list && mc.cull_front, !rect_list && mc.cull_back);
|
||||||
if (tessellation) {
|
if (tessellation) {
|
||||||
static_params.patch_control_points =
|
static_params.patch_control_points = vs_input_info.tess.input_control_points;
|
||||||
static_cast<uint8_t>(vs_input_info.tess.input_control_points);
|
|
||||||
} else if (!mesh && static_params.topology == vk::PrimitiveTopology::ePatchList) {
|
} else if (!mesh && static_params.topology == vk::PrimitiveTopology::ePatchList) {
|
||||||
// Rectangle lists expand through generated tessellation stages.
|
// Rectangle lists expand through generated tessellation stages.
|
||||||
static_params.patch_control_points = 3;
|
static_params.patch_control_points = 3;
|
||||||
|
|||||||
@@ -54,15 +54,12 @@ struct PipelineStaticParameters {
|
|||||||
bool depth_clip_enable = true;
|
bool depth_clip_enable = true;
|
||||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||||
// 0 without tessellation, 3 for a rectangle list, else the input control points.
|
// 0 without tessellation, 3 for a rectangle list, else the input control points.
|
||||||
uint8_t patch_control_points = 0;
|
uint32_t patch_control_points = 0;
|
||||||
bool primitive_restart_enable = false;
|
bool primitive_restart_enable = false;
|
||||||
uint32_t samples = 1;
|
uint32_t samples = 1;
|
||||||
bool sample_shading_enable = false;
|
bool sample_shading_enable = false;
|
||||||
bool alpha_to_coverage_enable = false;
|
bool alpha_to_coverage_enable = false;
|
||||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||||
bool cull_front = false;
|
|
||||||
bool cull_back = false;
|
|
||||||
bool face = false;
|
|
||||||
bool provoking_vtx_last = false;
|
bool provoking_vtx_last = false;
|
||||||
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
||||||
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||||
@@ -140,6 +137,8 @@ struct DrawRenderState {
|
|||||||
bool ps_active = false;
|
bool ps_active = false;
|
||||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||||
ShaderPixelInputInfo ps_input_info;
|
ShaderPixelInputInfo ps_input_info;
|
||||||
|
// Input: the user-data slots a GPU-read indirect draw patches.
|
||||||
|
ShaderIndirectOffsets indirect_offsets;
|
||||||
|
|
||||||
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||||
|
|||||||
@@ -219,17 +219,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
|||||||
vk::PipelineViewportStateCreateInfo viewport_state {};
|
vk::PipelineViewportStateCreateInfo viewport_state {};
|
||||||
viewport_state.pNext = &depth_clip_control;
|
viewport_state.pNext = &depth_clip_control;
|
||||||
|
|
||||||
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
|
|
||||||
if (static_params.cull_back) {
|
|
||||||
cull_mode |= vk::CullModeFlagBits::eBack;
|
|
||||||
}
|
|
||||||
if (static_params.cull_front) {
|
|
||||||
cull_mode |= vk::CullModeFlagBits::eFront;
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::FrontFace front_face =
|
|
||||||
(static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise);
|
|
||||||
|
|
||||||
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
|
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
|
||||||
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
||||||
|
|
||||||
@@ -250,8 +239,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
|||||||
provoking_vertex.pNext = rasterizer.pNext;
|
provoking_vertex.pNext = rasterizer.pNext;
|
||||||
rasterizer.pNext = &provoking_vertex;
|
rasterizer.pNext = &provoking_vertex;
|
||||||
}
|
}
|
||||||
rasterizer.cullMode = cull_mode;
|
|
||||||
rasterizer.frontFace = front_face;
|
|
||||||
rasterizer.polygonMode = static_params.polygon_mode;
|
rasterizer.polygonMode = static_params.polygon_mode;
|
||||||
rasterizer.lineWidth = 1.0f;
|
rasterizer.lineWidth = 1.0f;
|
||||||
|
|
||||||
@@ -347,6 +334,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
|||||||
vk::DynamicState::eViewportWithCount,
|
vk::DynamicState::eViewportWithCount,
|
||||||
vk::DynamicState::eScissorWithCount,
|
vk::DynamicState::eScissorWithCount,
|
||||||
vk::DynamicState::eLineWidth,
|
vk::DynamicState::eLineWidth,
|
||||||
|
vk::DynamicState::eCullMode,
|
||||||
|
vk::DynamicState::eFrontFace,
|
||||||
vk::DynamicState::eDepthTestEnable,
|
vk::DynamicState::eDepthTestEnable,
|
||||||
vk::DynamicState::eDepthWriteEnable,
|
vk::DynamicState::eDepthWriteEnable,
|
||||||
vk::DynamicState::eDepthCompareOp,
|
vk::DynamicState::eDepthCompareOp,
|
||||||
|
|||||||
@@ -45,9 +45,10 @@ enum class DrawOffsetSource : uint8_t {
|
|||||||
|
|
||||||
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
||||||
struct DrawIndirectArgs {
|
struct DrawIndirectArgs {
|
||||||
uint64_t address = 0;
|
uint64_t address = 0;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
uint32_t stride = 0;
|
uint32_t stride = 0;
|
||||||
|
ShaderIndirectOffsets offsets;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawIndexArgs {
|
struct DrawIndexArgs {
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ void RenderContext::RunGarbageCollector() {
|
|||||||
m_fault_process_pending = false;
|
m_fault_process_pending = false;
|
||||||
m_buffer_cache.ProcessFaultBuffer();
|
m_buffer_cache.ProcessFaultBuffer();
|
||||||
}
|
}
|
||||||
|
m_image_heap.RunGarbageCollector();
|
||||||
m_texture_cache.ProcessDownloadImages();
|
m_texture_cache.ProcessDownloadImages();
|
||||||
m_texture_cache.RunGarbageCollector();
|
m_texture_cache.RunGarbageCollector();
|
||||||
m_buffer_cache.RunGarbageCollector();
|
m_buffer_cache.RunGarbageCollector();
|
||||||
|
|||||||
@@ -403,6 +403,20 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState
|
|||||||
dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
dynamic_state.SetDepthBias(constant_factor, poly_offset.clamp, slope_factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rect lists are drawn without culling.
|
||||||
|
vk::CullModeFlags cull_mode {};
|
||||||
|
if (!Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) {
|
||||||
|
if (mode.cull_front) {
|
||||||
|
cull_mode |= vk::CullModeFlagBits::eFront;
|
||||||
|
}
|
||||||
|
if (mode.cull_back) {
|
||||||
|
cull_mode |= vk::CullModeFlagBits::eBack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic_state.SetCullMode(cull_mode);
|
||||||
|
dynamic_state.SetFrontFace(mode.face ? vk::FrontFace::eClockwise
|
||||||
|
: vk::FrontFace::eCounterClockwise);
|
||||||
|
|
||||||
dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable);
|
dynamic_state.SetStencilTestEnabled(depth.stencil_test_enable);
|
||||||
if (depth.stencil_test_enable) {
|
if (depth.stencil_test_enable) {
|
||||||
const auto& front = depth.stencil_front;
|
const auto& front = depth.stencil_front;
|
||||||
@@ -843,6 +857,11 @@ bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCal
|
|||||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||||
}
|
}
|
||||||
m_context.GetPipelineCache().GetGraphicsPrograms(buffer, state);
|
m_context.GetPipelineCache().GetGraphicsPrograms(buffer, state);
|
||||||
|
// Vertex programs beyond the host mesh limits or with unsupported call targets are not
|
||||||
|
// compiled.
|
||||||
|
if (state.vertex_info[0].stage.module == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (draw.IsIndexed()) {
|
if (draw.IsIndexed()) {
|
||||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||||
}
|
}
|
||||||
@@ -1156,7 +1175,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
|||||||
if (mesh_active) {
|
if (mesh_active) {
|
||||||
draw_data = {draw.index_count,
|
draw_data = {draw.index_count,
|
||||||
static_cast<uint32_t>(emit.vertex_offset),
|
static_cast<uint32_t>(emit.vertex_offset),
|
||||||
emit.first_instance,
|
|
||||||
index_source.guest_element_size,
|
index_source.guest_element_size,
|
||||||
static_cast<uint32_t>(index_source.address),
|
static_cast<uint32_t>(index_source.address),
|
||||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||||
@@ -1232,8 +1250,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||||
// mesh and quad-list draws do. Indirect records carry their own offsets.
|
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry
|
||||||
|
// their own offsets.
|
||||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||||
const ShaderVertexInputInfo& vs,
|
const ShaderVertexInputInfo& vs,
|
||||||
DrawOffsetSource source,
|
DrawOffsetSource source,
|
||||||
@@ -1242,7 +1261,8 @@ static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig&
|
|||||||
const DrawIndirectArgs& indirect) {
|
const DrawIndirectArgs& indirect) {
|
||||||
if (indirect.address != 0) {
|
if (indirect.address != 0) {
|
||||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||||
|
vs.indirect_offsets != indirect.offsets) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return DrawEmitInfo {.indirect = indirect};
|
return DrawEmitInfo {.indirect = indirect};
|
||||||
@@ -1306,7 +1326,7 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
|||||||
|
|
||||||
hw_check(buffer);
|
hw_check(buffer);
|
||||||
|
|
||||||
DrawRenderState state {};
|
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1411,7 +1431,7 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
|||||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||||
args.vertex_count, args.instance_count, args.first_instance};
|
args.vertex_count, args.instance_count, args.first_instance};
|
||||||
|
|
||||||
DrawRenderState state {};
|
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||||
ResetBindings();
|
ResetBindings();
|
||||||
return true;
|
return true;
|
||||||
@@ -1475,8 +1495,9 @@ bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_
|
|||||||
cache.MarkGpuWritten(dst.image_id);
|
cache.MarkGpuWritten(dst.image_id);
|
||||||
auto& source = cache.GetImage(src.image_id);
|
auto& source = cache.GetImage(src.image_id);
|
||||||
auto& destination = cache.GetImage(dst.image_id);
|
auto& destination = cache.GetImage(dst.image_id);
|
||||||
destination.Resolve(source, {src.guest_mip_level, 1, src.guest_array_layer, 1},
|
destination.Resolve(source,
|
||||||
{dst.guest_mip_level, 1, dst.guest_array_layer, 1});
|
{src.desc.view_info.base_level, 1, src.desc.view_info.base_layer, 1},
|
||||||
|
{dst.desc.view_info.base_level, 1, dst.desc.view_info.base_layer, 1});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
#extension GL_EXT_shader_16bit_storage : require
|
||||||
|
|
||||||
layout(constant_id = 0) const uint D32 = 0u;
|
layout(constant_id = 0) const uint D32 = 0u;
|
||||||
layout(local_size_x = 64) in;
|
layout(local_size_x = 64) in;
|
||||||
|
|
||||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
|
||||||
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
|
layout(set = 0, binding = 1, std430) buffer Output { uint16_t data[]; } output_buffer;
|
||||||
|
|
||||||
layout(push_constant, std430) uniform Conversion {
|
layout(push_constant, std430) uniform Conversion {
|
||||||
uint src_base;
|
uint src_base;
|
||||||
@@ -32,9 +33,5 @@ void main() {
|
|||||||
}
|
}
|
||||||
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
||||||
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
||||||
uint shift = (dst & 2u) * 8u;
|
output_buffer.data[dst >> 1u] = uint16_t(decode(input_buffer.data[src >> 2u]));
|
||||||
uint mask = 0xffffu << shift;
|
|
||||||
uint word = dst >> 2u;
|
|
||||||
atomicAnd(output_buffer.data[word], ~mask);
|
|
||||||
atomicOr(output_buffer.data[word], decode(input_buffer.data[src >> 2u]) << shift);
|
|
||||||
}
|
}
|
||||||
@@ -533,16 +533,16 @@ void DrawMemoryAndIo(const Snapshot& shown) {
|
|||||||
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
||||||
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
||||||
Row("Extended", Bytes(guest.automatic_allocated));
|
Row("Extended", Bytes(guest.automatic_allocated));
|
||||||
// Past the pool size the guest sees no page-table entries available.
|
// Past its total a side sees no page-table entries available.
|
||||||
const auto entries = std::max(guest.cpu_page_entries, guest.gpu_page_entries);
|
const auto percent = std::max(guest.cpu_page_entries * 100 / guest.cpu_page_entries_total,
|
||||||
|
guest.gpu_page_entries * 100 / guest.gpu_page_entries_total);
|
||||||
Row("Page table (2 MB)",
|
Row("Page table (2 MB)",
|
||||||
Fmt("CPU %llu GPU %llu / %llu",
|
Fmt("CPU %llu / %llu GPU %llu / %llu",
|
||||||
static_cast<unsigned long long>(guest.cpu_page_entries),
|
static_cast<unsigned long long>(guest.cpu_page_entries),
|
||||||
|
static_cast<unsigned long long>(guest.cpu_page_entries_total),
|
||||||
static_cast<unsigned long long>(guest.gpu_page_entries),
|
static_cast<unsigned long long>(guest.gpu_page_entries),
|
||||||
static_cast<unsigned long long>(guest.page_entries_total)),
|
static_cast<unsigned long long>(guest.gpu_page_entries_total)),
|
||||||
entries > guest.page_entries_total ? BAD
|
LevelColor(static_cast<double>(percent), 90, 100));
|
||||||
: entries * 10 > guest.page_entries_total * 9 ? MEDIUM
|
|
||||||
: GOOD);
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
if (BeginSection("Emulator process")) {
|
if (BeginSection("Emulator process")) {
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ public:
|
|||||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||||
bool Flip(uint32_t micros);
|
bool Flip(uint32_t micros);
|
||||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
void Wait(VideoOutConfig& cfg, int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||||
@@ -240,7 +240,6 @@ private:
|
|||||||
Graphics::Presenter::Frame* frame;
|
Graphics::Presenter::Frame* frame;
|
||||||
bool premultiplied_alpha;
|
bool premultiplied_alpha;
|
||||||
};
|
};
|
||||||
void SignalDoneLocked();
|
|
||||||
|
|
||||||
Graphics::Presenter& m_presenter;
|
Graphics::Presenter& m_presenter;
|
||||||
Common::Mutex m_mutex;
|
Common::Mutex m_mutex;
|
||||||
@@ -251,7 +250,6 @@ private:
|
|||||||
std::list<Request> m_cpu_requests;
|
std::list<Request> m_cpu_requests;
|
||||||
std::list<Request> m_cancelled_requests;
|
std::list<Request> m_cancelled_requests;
|
||||||
bool m_processing = false;
|
bool m_processing = false;
|
||||||
bool m_gpu_waiting = false;
|
|
||||||
uint64_t m_next_request_id = 1;
|
uint64_t m_next_request_id = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -955,7 +953,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignalDoneLocked();
|
m_done_cond_var.SignalAll();
|
||||||
}
|
}
|
||||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||||
@@ -1035,7 +1033,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
|||||||
it = queue->erase(it);
|
it = queue->erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignalDoneLocked();
|
m_done_cond_var.SignalAll();
|
||||||
m_submit_slot_cond_var.SignalAll();
|
m_submit_slot_cond_var.SignalAll();
|
||||||
m_submit_cond_var.SignalAll();
|
m_submit_cond_var.SignalAll();
|
||||||
m_mutex.Unlock();
|
m_mutex.Unlock();
|
||||||
@@ -1207,24 +1205,17 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlipQueue::SignalDoneLocked() {
|
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||||
m_done_cond_var.SignalAll();
|
|
||||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
|
||||||
if (m_gpu_waiting) {
|
|
||||||
m_gpu_waiting = false;
|
|
||||||
m_presenter.Renderer().GetGpu().Wake();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
|
||||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
|
||||||
Common::LockGuard lock(m_mutex);
|
Common::LockGuard lock(m_mutex);
|
||||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
|
||||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
auto has_request = [this, &cfg, index] {
|
||||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||||
m_gpu_waiting = !safe;
|
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||||
return safe;
|
};
|
||||||
|
while (has_request()) {
|
||||||
|
m_done_cond_var.Wait(&m_mutex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlipQueue::Flip(uint32_t micros) {
|
bool FlipQueue::Flip(uint32_t micros) {
|
||||||
@@ -1320,7 +1311,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
|||||||
it = m_requests.erase(it);
|
it = m_requests.erase(it);
|
||||||
}
|
}
|
||||||
m_processing = false;
|
m_processing = false;
|
||||||
SignalDoneLocked();
|
m_done_cond_var.SignalAll();
|
||||||
m_submit_slot_cond_var.SignalAll();
|
m_submit_slot_cond_var.SignalAll();
|
||||||
m_mutex.Unlock();
|
m_mutex.Unlock();
|
||||||
for (size_t i = count; i != 0; i--) {
|
for (size_t i = count; i != 0; i--) {
|
||||||
@@ -1703,12 +1694,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
|||||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||||
auto* ctx = m_impl->Get(handle);
|
auto* ctx = m_impl->Get(handle);
|
||||||
EXIT_IF(ctx == nullptr);
|
EXIT_IF(ctx == nullptr);
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public:
|
|||||||
void CompleteFlip(uint64_t request_id);
|
void CompleteFlip(uint64_t request_id);
|
||||||
void SubmitFlipPreparation(uint64_t request_id);
|
void SubmitFlipPreparation(uint64_t request_id);
|
||||||
void WaitForSubmitSlot(int handle);
|
void WaitForSubmitSlot(int handle);
|
||||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
void WaitFlipDone(int handle, int index);
|
||||||
|
|
||||||
[[nodiscard]] Impl& State() noexcept;
|
[[nodiscard]] Impl& State() noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
vk::SurfaceFormatKHR SelectFormat();
|
vk::SurfaceFormatKHR SelectFormat();
|
||||||
void Destroy();
|
void Destroy();
|
||||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
|
||||||
|
const vk::Rect2D& rect);
|
||||||
|
|
||||||
WindowContext& m_window;
|
WindowContext& m_window;
|
||||||
vk::SwapchainKHR m_handle = nullptr;
|
vk::SwapchainKHR m_handle = nullptr;
|
||||||
@@ -637,7 +638,23 @@ bool Swapchain::PrepareSystemOverlay() {
|
|||||||
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer) {
|
static vk::Rect2D FitAspect(const vk::Extent3D& source, const vk::Extent2D& target) {
|
||||||
|
const uint64_t src_w = std::max(source.width, 1u);
|
||||||
|
const uint64_t src_h = std::max(source.height, 1u);
|
||||||
|
uint32_t w = target.width;
|
||||||
|
uint32_t h = target.height;
|
||||||
|
if (src_w * h > src_h * w) {
|
||||||
|
h = std::max<uint32_t>(1, static_cast<uint32_t>(src_h * w / src_w));
|
||||||
|
} else {
|
||||||
|
w = std::max<uint32_t>(1, static_cast<uint32_t>(src_w * h / src_h));
|
||||||
|
}
|
||||||
|
return {{static_cast<int32_t>((target.width - w) / 2),
|
||||||
|
static_cast<int32_t>((target.height - h) / 2)},
|
||||||
|
{w, h}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer,
|
||||||
|
const vk::Rect2D& rect) {
|
||||||
auto device = m_window.graphic_ctx.device;
|
auto device = m_window.graphic_ctx.device;
|
||||||
if (m_overlay_pipeline == nullptr) {
|
if (m_overlay_pipeline == nullptr) {
|
||||||
vk::SamplerCreateInfo sampler {};
|
vk::SamplerCreateInfo sampler {};
|
||||||
@@ -756,12 +773,14 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
|
|||||||
rendering.colorAttachmentCount = 1;
|
rendering.colorAttachmentCount = 1;
|
||||||
rendering.pColorAttachments = &attachment;
|
rendering.pColorAttachments = &attachment;
|
||||||
command.beginRendering(&rendering);
|
command.beginRendering(&rendering);
|
||||||
const vk::Viewport viewport {
|
const vk::Viewport viewport {static_cast<float>(rect.offset.x),
|
||||||
0.0f, 0.0f, static_cast<float>(m_extent.width), static_cast<float>(m_extent.height),
|
static_cast<float>(rect.offset.y),
|
||||||
0.0f, 1.0f};
|
static_cast<float>(rect.extent.width),
|
||||||
const vk::Rect2D scissor {{0, 0}, m_extent};
|
static_cast<float>(rect.extent.height),
|
||||||
|
0.0f,
|
||||||
|
1.0f};
|
||||||
command.setViewport(0, 1, &viewport);
|
command.setViewport(0, 1, &viewport);
|
||||||
command.setScissor(0, 1, &scissor);
|
command.setScissor(0, 1, &rect);
|
||||||
command.draw(3, 1, 0, 0);
|
command.draw(3, 1, 0, 0);
|
||||||
command.endRendering();
|
command.endRendering();
|
||||||
}
|
}
|
||||||
@@ -799,6 +818,24 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
|||||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
||||||
nullptr, 0, nullptr, 1, &to_transfer);
|
nullptr, 0, nullptr, 1, &to_transfer);
|
||||||
|
|
||||||
|
const auto* aspect_source = source != nullptr ? source : overlay.frame;
|
||||||
|
const auto game_rect = !Config::StretchEnabled() && aspect_source != nullptr
|
||||||
|
? FitAspect(aspect_source->image.extent, m_extent)
|
||||||
|
: vk::Rect2D {{0, 0}, m_extent};
|
||||||
|
if (source == nullptr || game_rect.extent != m_extent) {
|
||||||
|
const vk::ClearColorValue black {};
|
||||||
|
vk_command.clearColorImage(m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal,
|
||||||
|
&black, 1, &to_transfer.subresourceRange);
|
||||||
|
if (source != nullptr) {
|
||||||
|
vk::ImageMemoryBarrier after_clear = to_transfer;
|
||||||
|
after_clear.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||||
|
after_clear.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||||
|
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||||
|
vk::PipelineStageFlagBits::eTransfer,
|
||||||
|
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
|
||||||
|
&after_clear);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (source != nullptr) {
|
if (source != nullptr) {
|
||||||
vk::ImageBlit region {};
|
vk::ImageBlit region {};
|
||||||
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
@@ -812,16 +849,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
|||||||
region.dstSubresource.mipLevel = 0;
|
region.dstSubresource.mipLevel = 0;
|
||||||
region.dstSubresource.baseArrayLayer = 0;
|
region.dstSubresource.baseArrayLayer = 0;
|
||||||
region.dstSubresource.layerCount = 1;
|
region.dstSubresource.layerCount = 1;
|
||||||
region.dstOffsets[1].x = static_cast<int>(m_extent.width);
|
region.dstOffsets[0].x = game_rect.offset.x;
|
||||||
region.dstOffsets[1].y = static_cast<int>(m_extent.height);
|
region.dstOffsets[0].y = game_rect.offset.y;
|
||||||
|
region.dstOffsets[1].x = game_rect.offset.x + static_cast<int>(game_rect.extent.width);
|
||||||
|
region.dstOffsets[1].y = game_rect.offset.y + static_cast<int>(game_rect.extent.height);
|
||||||
region.dstOffsets[1].z = 1;
|
region.dstOffsets[1].z = 1;
|
||||||
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
|
vk_command.blitImage(source->image.image, vk::ImageLayout::eTransferSrcOptimal,
|
||||||
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
|
m_images[m_image_index], vk::ImageLayout::eTransferDstOptimal, 1,
|
||||||
®ion, vk::Filter::eLinear);
|
®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 {};
|
vk::ImageMemoryBarrier to_present {};
|
||||||
@@ -848,7 +883,7 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, Presenter::Frame*
|
|||||||
&to_present);
|
&to_present);
|
||||||
if (draw_attachment) {
|
if (draw_attachment) {
|
||||||
if (draw_overlay) {
|
if (draw_overlay) {
|
||||||
DrawOverlay(vk_command, overlay);
|
DrawOverlay(vk_command, overlay, game_rect);
|
||||||
if (draw_system_overlay) {
|
if (draw_system_overlay) {
|
||||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ struct VulkanExtensions {
|
|||||||
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
||||||
vk::PhysicalDeviceVulkan11Features features {};
|
vk::PhysicalDeviceVulkan11Features features {};
|
||||||
features.storageBuffer16BitAccess = VK_TRUE;
|
features.storageBuffer16BitAccess = VK_TRUE;
|
||||||
|
features.shaderDrawParameters = VK_TRUE;
|
||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +279,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
|||||||
required_features12.samplerMirrorClampToEdge);
|
required_features12.samplerMirrorClampToEdge);
|
||||||
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
||||||
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
||||||
|
check_feature(features11.shaderDrawParameters, "shaderDrawParameters",
|
||||||
|
WindowContext::RequiredVulkan11Features().shaderDrawParameters);
|
||||||
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
||||||
required_features12.storageBuffer8BitAccess);
|
required_features12.storageBuffer8BitAccess);
|
||||||
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
||||||
@@ -337,6 +340,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
|||||||
|
|
||||||
check_feature(device_features2.features.shaderImageGatherExtended,
|
check_feature(device_features2.features.shaderImageGatherExtended,
|
||||||
"shaderImageGatherExtended");
|
"shaderImageGatherExtended");
|
||||||
|
check_feature(device_features2.features.shaderResourceMinLod, "shaderResourceMinLod");
|
||||||
|
|
||||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||||
@@ -636,6 +640,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
device_features.shaderStorageImageMultisample =
|
device_features.shaderStorageImageMultisample =
|
||||||
supported_features2.features.shaderStorageImageMultisample;
|
supported_features2.features.shaderStorageImageMultisample;
|
||||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||||
|
device_features.shaderResourceMinLod = VK_TRUE;
|
||||||
device_features.independentBlend = VK_TRUE;
|
device_features.independentBlend = VK_TRUE;
|
||||||
device_features.dualSrcBlend = VK_TRUE;
|
device_features.dualSrcBlend = VK_TRUE;
|
||||||
device_features.tessellationShader = VK_TRUE;
|
device_features.tessellationShader = VK_TRUE;
|
||||||
@@ -733,6 +738,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||||
create_info.pNext = &conditional_rendering;
|
create_info.pNext = &conditional_rendering;
|
||||||
}
|
}
|
||||||
|
// The renderer only uses the Vulkan 1.3 core dynamic states, but RADV (Mesa 26.3) creates its
|
||||||
|
// PS epilog cache only when this EXT feature is enabled and crashes in vkCmdDraw when a
|
||||||
|
// depth- or stencil-writing fragment shader needs an epilog.
|
||||||
|
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extended_dynamic_state {};
|
||||||
|
extended_dynamic_state.extendedDynamicState = VK_TRUE;
|
||||||
|
if (HasExtension(device_extensions, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)) {
|
||||||
|
extended_dynamic_state.pNext = const_cast<void*>(create_info.pNext);
|
||||||
|
create_info.pNext = &extended_dynamic_state;
|
||||||
|
}
|
||||||
create_info.pQueueCreateInfos = &queue_create_info;
|
create_info.pQueueCreateInfos = &queue_create_info;
|
||||||
create_info.queueCreateInfoCount = 1;
|
create_info.queueCreateInfoCount = 1;
|
||||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||||
@@ -1111,7 +1125,8 @@ void WindowContext::CreateVulkan() {
|
|||||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
||||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
||||||
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME}) {
|
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME,
|
||||||
|
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME}) {
|
||||||
if (HasExtension(available_extensions, extension)) {
|
if (HasExtension(available_extensions, extension)) {
|
||||||
device_extensions.push_back(extension);
|
device_extensions.push_back(extension);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,6 +467,7 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
|||||||
void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||||
using namespace IR;
|
using namespace IR;
|
||||||
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
||||||
|
source.wave_size = options.wave_size;
|
||||||
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
||||||
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
||||||
source.calls.push_back({.instruction = i});
|
source.calls.push_back({.instruction = i});
|
||||||
@@ -503,7 +504,8 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
|||||||
};
|
};
|
||||||
Register = [&](uint32_t before, uint32_t code) -> Value {
|
Register = [&](uint32_t before, uint32_t code) -> Value {
|
||||||
std::vector<uint32_t> crossed_calls;
|
std::vector<uint32_t> crossed_calls;
|
||||||
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code, crossed_calls);
|
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code,
|
||||||
|
source.wave_size, crossed_calls);
|
||||||
for (const auto crossed: crossed_calls) {
|
for (const auto crossed: crossed_calls) {
|
||||||
std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code);
|
std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code);
|
||||||
}
|
}
|
||||||
@@ -702,10 +704,11 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
|||||||
EXIT_IF(function_pc > UINT32_MAX - function_size);
|
EXIT_IF(function_pc > UINT32_MAX - function_size);
|
||||||
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
||||||
for (auto inst: function) {
|
for (auto inst: function) {
|
||||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) ||
|
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg, source.wave_size) ||
|
||||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u));
|
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u, source.wave_size));
|
||||||
for (uint32_t code = 0; code < site.preserved_registers.size(); ++code) {
|
for (uint32_t code = 0; code < site.preserved_registers.size(); ++code) {
|
||||||
if (site.preserved_registers[code] && CFG::MayWriteScalarRegister(inst, code))
|
if (site.preserved_registers[code] &&
|
||||||
|
CFG::MayWriteScalarRegister(inst, code, source.wave_size))
|
||||||
EXIT("shader call at pc 0x%08x clobbers scalar target dependency s%u", call.pc, code);
|
EXIT("shader call at pc 0x%08x clobbers scalar target dependency s%u", call.pc, code);
|
||||||
}
|
}
|
||||||
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ struct ShaderSource {
|
|||||||
IR::ResourcePlan call_targets;
|
IR::ResourcePlan call_targets;
|
||||||
std::vector<Call> calls;
|
std::vector<Call> calls;
|
||||||
uint32_t unsupported_call_pc = UINT32_MAX;
|
uint32_t unsupported_call_pc = UINT32_MAX;
|
||||||
|
uint32_t wave_size = 64;
|
||||||
std::optional<Linked> linked;
|
std::optional<Linked> linked;
|
||||||
uint64_t revision = 0;
|
uint64_t revision = 0;
|
||||||
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) {
|
|||||||
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
|
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t MrtOutputType(EmitterState& state, uint32_t index) {
|
||||||
|
const auto& pixel = *state.input_info.pixel;
|
||||||
|
if ((pixel.target_sint_mask & (1u << index)) != 0u) return TypeI32Vector(state, 4);
|
||||||
|
if ((pixel.target_uint_mask & (1u << index)) != 0u) return TypeU32Vector(state, 4);
|
||||||
|
return TypeF32Vector(state, 4);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
||||||
return state.per_vertex_variable;
|
return state.per_vertex_variable;
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
|||||||
}
|
}
|
||||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
||||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId ||
|
||||||
|
kind == IR::StageInputKind::BaseVertex || kind == IR::StageInputKind::BaseInstance) {
|
||||||
const auto value = state.builder.AllocateId();
|
const auto value = state.builder.AllocateId();
|
||||||
const auto bits = state.builder.AllocateId();
|
const auto bits = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||||
@@ -291,9 +292,10 @@ uint32_t ExportRawComponent(ValueEmitContext& ctx, uint32_t vector, uint32_t com
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp,
|
uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp,
|
||||||
bool uint_output) {
|
uint32_t vector_type) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
if (exp.compr && !uint_output) {
|
const bool integer_output = vector_type != TypeF32Vector(state, 4);
|
||||||
|
if (exp.compr && !integer_output) {
|
||||||
auto unpack = GLSLstd450UnpackHalf2x16;
|
auto unpack = GLSLstd450UnpackHalf2x16;
|
||||||
switch (MrtOutputMode(state, exp)) {
|
switch (MrtOutputMode(state, exp)) {
|
||||||
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
||||||
@@ -330,9 +332,11 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
|||||||
ConstantU32(state, 0),
|
ConstantU32(state, 0),
|
||||||
ConstantU32(state, 0),
|
ConstantU32(state, 0),
|
||||||
ConstantU32(state, 0),
|
ConstantU32(state, 0),
|
||||||
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
|
ConstantU32(state, integer_output ? 1u : 0x3f800000u),
|
||||||
};
|
};
|
||||||
if (exp.compr) {
|
if (exp.compr) {
|
||||||
|
const auto extract = vector_type == TypeU32Vector(state, 4) ? spv::OpBitFieldUExtract
|
||||||
|
: spv::OpBitFieldSExtract;
|
||||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||||
continue;
|
continue;
|
||||||
@@ -344,8 +348,8 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
raw[component] = state.builder.AllocateId();
|
raw[component] = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
state.builder.AddFunction(extract, TypeU32(state), raw[component], packed,
|
||||||
packed, ConstantU32(state, lane * 16u),
|
ConstantU32(state, lane * 16u),
|
||||||
ConstantU32(state, 16));
|
ConstantU32(state, 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -360,12 +364,10 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
|||||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
||||||
raw[0], raw[1], raw[2], raw[3]);
|
raw[0], raw[1], raw[2], raw[3]);
|
||||||
}
|
}
|
||||||
if (uint_output) {
|
if (vector_type == TypeU32Vector(state, 4)) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
const auto vector = state.builder.AllocateId();
|
return Unary(state, spv::OpBitcast, vector_type, data);
|
||||||
state.builder.AddFunction(spv::OpBitcast, TypeF32Vector(state, 4), vector, data);
|
|
||||||
return vector;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t DistancePointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
uint32_t DistancePointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
||||||
@@ -563,11 +565,11 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
if (state.program.stage != ShaderType::Mesh && variable == 0) {
|
if (state.program.stage != ShaderType::Mesh && variable == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
const auto vector_type = MrtOutputMode(state, exp) != 0u ? MrtOutputType(state, exp.index)
|
||||||
const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
: TypeF32Vector(state, 4);
|
||||||
auto value = ExportVector(ctx, data, exp, uint_output);
|
auto value = ExportVector(ctx, data, exp, vector_type);
|
||||||
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
if (state.program.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
|
||||||
exp.index == 0 && !uint_output &&
|
exp.index == 0 && vector_type == TypeF32Vector(state, 4) &&
|
||||||
state.input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None) {
|
state.input_info.pixel->alpha_blend_source != ShaderAlphaBlendSource::None) {
|
||||||
// Broadcast logical alpha before swizzling the primary output.
|
// Broadcast logical alpha before swizzling the primary output.
|
||||||
const auto blend_output =
|
const auto blend_output =
|
||||||
@@ -711,9 +713,16 @@ void EmitBarrier(EmitterState& state) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
||||||
const auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
||||||
? spv::MemorySemanticsUniformMemoryMask
|
? spv::MemorySemanticsUniformMemoryMask
|
||||||
: spv::MemorySemanticsWorkgroupMemoryMask;
|
: spv::MemorySemanticsWorkgroupMemoryMask;
|
||||||
|
// Shaders read other waves' buffer and image stores with plain loads right after S_BARRIER.
|
||||||
|
const auto written = [](const auto& resource) { return resource.written; };
|
||||||
|
if (std::ranges::any_of(state.program.info.buffers, written) ||
|
||||||
|
std::ranges::any_of(state.program.info.images, written)) {
|
||||||
|
memory = memory | spv::MemorySemanticsUniformMemoryMask |
|
||||||
|
spv::MemorySemanticsImageMemoryMask;
|
||||||
|
}
|
||||||
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
|
const auto semantics = tessellation ? spv::MemorySemanticsMaskNone
|
||||||
: spv::MemorySemanticsAcquireReleaseMask | memory;
|
: spv::MemorySemanticsAcquireReleaseMask | memory;
|
||||||
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||||
@@ -811,6 +820,13 @@ uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate) {
|
|||||||
return ctx.Ballot(predicate);
|
return ctx.Ballot(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t EmitLiveMask(EmitterState& state) {
|
||||||
|
const auto helper =
|
||||||
|
EmitNative<spv::OpLoad, IR::Type::U1>(state, state.helper_invocation_variable);
|
||||||
|
return EmitNative<spv::OpGroupNonUniformBallot, IR::Type::U32x4>(
|
||||||
|
state, ConstantU32(state, spv::ScopeSubgroup), EmitLogicalNotBool(state, helper));
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||||
const auto lane = ctx.FirstLane(ballot);
|
const auto lane = ctx.FirstLane(ballot);
|
||||||
@@ -821,18 +837,28 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
return ctx.Shuffle(inst, 0, ctx.Arg(inst, 1));
|
return ctx.Shuffle(inst, 0, ctx.Arg(inst, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
template <spv::Op Reduce, auto Combine>
|
||||||
|
static uint32_t EmitSubgroupReduceU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
auto value = ctx.Arg(inst, 0);
|
auto value = ctx.Arg(inst, 0);
|
||||||
if (ctx.other_half != nullptr) {
|
if (ctx.other_half != nullptr) {
|
||||||
value = EmitUMin32(state, ctx.HalfArg(inst, 0, 0), ctx.HalfArg(inst, 0, 1));
|
value = Combine(state, ctx.HalfArg(inst, 0, 0), ctx.HalfArg(inst, 0, 1));
|
||||||
}
|
}
|
||||||
const auto result = state.builder.AllocateId();
|
const auto result = state.builder.AllocateId();
|
||||||
state.builder.AddFunction(spv::OpGroupNonUniformUMin, TypeU32(state), result,
|
state.builder.AddFunction(Reduce, TypeU32(state), result,
|
||||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce, value);
|
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce,
|
||||||
|
value);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
|
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMin, EmitUMin32>(ctx, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
|
return EmitSubgroupReduceU32<spv::OpGroupNonUniformUMax, EmitUMax32>(ctx, inst);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||||
auto& state = ctx.state;
|
auto& state = ctx.state;
|
||||||
const auto hit = state.builder.AllocateId();
|
const auto hit = state.builder.AllocateId();
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ bool HasFlag(const IR::MemoryInfo& mem, uint32_t flag) {
|
|||||||
|
|
||||||
uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||||
uint32_t component) {
|
uint32_t component) {
|
||||||
const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component);
|
const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags,
|
||||||
|
mem.image_dimension, component);
|
||||||
const auto packed = layout.bit_offset / 32u;
|
const auto packed = layout.bit_offset / 32u;
|
||||||
if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0);
|
if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0);
|
||||||
auto value = ctx.Def(address.Arg(packed));
|
auto value = ctx.Def(address.Arg(packed));
|
||||||
@@ -55,10 +56,11 @@ uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
|
|||||||
|
|
||||||
uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||||
uint32_t component) {
|
uint32_t component) {
|
||||||
const auto value = AddressU32(ctx, mem, address, component);
|
const auto value = AddressU32(ctx, mem, address, component);
|
||||||
return Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component).bit_width == 16u
|
const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags,
|
||||||
? EmitF16BitsToF32(ctx.state, value)
|
mem.image_dimension, component);
|
||||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
return layout.bit_width == 16u ? EmitF16BitsToF32(ctx.state, value)
|
||||||
|
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||||
@@ -77,6 +79,7 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
|||||||
layout.coord = cursor;
|
layout.coord = cursor;
|
||||||
cursor += info.coordinate_components;
|
cursor += info.coordinate_components;
|
||||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
||||||
|
if (HasFlag(mem, Decoder::ImageSampleFlagLodClamp)) layout.clamp = cursor++;
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,6 +813,25 @@ uint32_t EmitImageResult(ValueEmitContext& ctx, const IR::Inst& inst, const Emit
|
|||||||
return mixed ? value : result_bits(mem.resource, value);
|
return mixed ? value : result_bits(mem.resource, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ordinals outside the table select candidates[0]. Past ordinal 0, a consecutive table is an
|
||||||
|
// offset of ordinal - 1, which wraps at 0 so that one unsigned compare checks both ends.
|
||||||
|
uint32_t SamplerArrayIndex(EmitterState& state, uint32_t ordinal,
|
||||||
|
const std::vector<uint32_t>& candidates) {
|
||||||
|
const auto count = static_cast<uint32_t>(candidates.size());
|
||||||
|
const auto gap = std::adjacent_find(candidates.begin() + 1, candidates.end(),
|
||||||
|
[](uint32_t lhs, uint32_t rhs) { return rhs != lhs + 1u; });
|
||||||
|
if (count == 1u || gap != candidates.end()) {
|
||||||
|
return EmitIndexSwitch(state, ordinal, count, TypeU32(state),
|
||||||
|
[&](uint32_t item) { return ConstantU32(state, candidates[item]); });
|
||||||
|
}
|
||||||
|
const auto offset = Binary(state, spv::OpISub, TypeU32(state), ordinal, ConstantU32(state, 1u));
|
||||||
|
const auto in_table = Binary(state, spv::OpULessThan, TypeBool(state), offset,
|
||||||
|
ConstantU32(state, count - 1u));
|
||||||
|
const auto index = Binary(state, spv::OpIAdd, TypeU32(state), offset,
|
||||||
|
ConstantU32(state, candidates[1]));
|
||||||
|
return Select(state, TypeU32(state), in_table, index, ConstantU32(state, candidates[0]));
|
||||||
|
}
|
||||||
|
|
||||||
struct ImageSampler {
|
struct ImageSampler {
|
||||||
uint32_t id = 0;
|
uint32_t id = 0;
|
||||||
bool dynamic = false;
|
bool dynamic = false;
|
||||||
@@ -845,9 +867,7 @@ std::array<ImageSampler, 2> LoadImageSamplers(ValueEmitContext& ctx, const IR::I
|
|||||||
selected = EmitIndirectResourceIndex(state, selected,
|
selected = EmitIndirectResourceIndex(state, selected,
|
||||||
samplers[index].indirect_mapping_offset,
|
samplers[index].indirect_mapping_offset,
|
||||||
samplers[index].indirect_search_iterations, 0u);
|
samplers[index].indirect_search_iterations, 0u);
|
||||||
array_index = EmitIndexSwitch(
|
array_index = SamplerArrayIndex(state, selected, candidates);
|
||||||
state, selected, static_cast<uint32_t>(candidates.size()),
|
|
||||||
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, candidates[ordinal]); });
|
|
||||||
}
|
}
|
||||||
sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u};
|
sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u};
|
||||||
};
|
};
|
||||||
@@ -1069,7 +1089,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::array<uint32_t, 4> operands {};
|
// _cl clamps the computed LOD from below.
|
||||||
|
uint32_t min_lod = 0;
|
||||||
|
if (layout.clamp != NoImageComponent) {
|
||||||
|
min_lod = AddressF32(ctx, mem, *address, layout.clamp);
|
||||||
|
}
|
||||||
|
std::array<uint32_t, 5> operands {};
|
||||||
size_t operand_count = 0;
|
size_t operand_count = 0;
|
||||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||||
operands[0] = spv::ImageOperandsGradMask;
|
operands[0] = spv::ImageOperandsGradMask;
|
||||||
@@ -1081,6 +1106,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
lod = AddressF32(ctx, mem, *address, layout.lod);
|
||||||
}
|
}
|
||||||
|
// SPIR-V takes MinLod only with implicit LOD or Grad.
|
||||||
|
if (min_lod != 0) {
|
||||||
|
lod = EmitGlsl<GLSLstd450FMax, IR::Type::F32>(state, lod, min_lod);
|
||||||
|
min_lod = 0;
|
||||||
|
}
|
||||||
operands[0] = spv::ImageOperandsLodMask;
|
operands[0] = spv::ImageOperandsLodMask;
|
||||||
operands[1] = lod;
|
operands[1] = lod;
|
||||||
operand_count = 2;
|
operand_count = 2;
|
||||||
@@ -1104,6 +1134,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
sample_operands[sample_operand_count++] = PackedOffset(
|
sample_operands[sample_operand_count++] = PackedOffset(
|
||||||
ctx, offset_value, candidate.dimension, sample_operands[0]);
|
ctx, offset_value, candidate.dimension, sample_operands[0]);
|
||||||
}
|
}
|
||||||
|
// MinLod follows the offset in operand order.
|
||||||
|
if (min_lod != 0) {
|
||||||
|
state.builder.RequireCapability(spv::CapabilityMinLod);
|
||||||
|
sample_operand_count = std::max(size_t{1}, sample_operand_count);
|
||||||
|
sample_operands[0] |= spv::ImageOperandsMinLodMask;
|
||||||
|
sample_operands[sample_operand_count++] = min_lod;
|
||||||
|
}
|
||||||
const auto coord =
|
const auto coord =
|
||||||
CoordF32(ctx, mem, *address, layout.coord,
|
CoordF32(ctx, mem, *address, layout.coord,
|
||||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||||
|
|||||||
@@ -230,10 +230,12 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& in
|
|||||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
||||||
uint32_t EmitLaneId(EmitterState& state);
|
uint32_t EmitLaneId(EmitterState& state);
|
||||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||||
|
uint32_t EmitLiveMask(EmitterState& state);
|
||||||
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitConditionRef(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitReadFirstLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitSubgroupUMinU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
|
uint32_t EmitSubgroupUMaxU32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||||
@@ -289,6 +291,7 @@ inline constexpr auto EmitBufferAtomicIAdd32 = EmitAtomic32;
|
|||||||
inline constexpr auto EmitBufferAtomicISub32 = EmitAtomic32;
|
inline constexpr auto EmitBufferAtomicISub32 = EmitAtomic32;
|
||||||
inline constexpr auto EmitBufferAtomicSMin32 = EmitAtomic32;
|
inline constexpr auto EmitBufferAtomicSMin32 = EmitAtomic32;
|
||||||
inline constexpr auto EmitBufferAtomicUMin32 = EmitAtomic32;
|
inline constexpr auto EmitBufferAtomicUMin32 = EmitAtomic32;
|
||||||
|
inline constexpr auto EmitBufferAtomicUMin64 = EmitBufferAtomic64;
|
||||||
inline constexpr auto EmitBufferAtomicSMax32 = EmitAtomic32;
|
inline constexpr auto EmitBufferAtomicSMax32 = EmitAtomic32;
|
||||||
inline constexpr auto EmitBufferAtomicUMax32 = EmitAtomic32;
|
inline constexpr auto EmitBufferAtomicUMax32 = EmitAtomic32;
|
||||||
inline constexpr auto EmitBufferAtomicAnd32 = EmitAtomic32;
|
inline constexpr auto EmitBufferAtomicAnd32 = EmitAtomic32;
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ struct EmitterState {
|
|||||||
uint32_t lane_half = 0;
|
uint32_t lane_half = 0;
|
||||||
std::array<BufferDefinition, 4> storage_buffers {};
|
std::array<BufferDefinition, 4> storage_buffers {};
|
||||||
std::vector<uint32_t> memory_byte_offsets;
|
std::vector<uint32_t> memory_byte_offsets;
|
||||||
|
std::vector<uint32_t> memory_byte_sizes;
|
||||||
uint32_t bda_pagetable_variable = 0;
|
uint32_t bda_pagetable_variable = 0;
|
||||||
uint32_t fault_buffer_variable = 0;
|
uint32_t fault_buffer_variable = 0;
|
||||||
uint32_t bda_pointer_function = 0;
|
uint32_t bda_pointer_function = 0;
|
||||||
@@ -145,6 +146,7 @@ struct EmitterState {
|
|||||||
const IR::Block* current_block = nullptr;
|
const IR::Block* current_block = nullptr;
|
||||||
uint32_t pixel_valid_mask_variable = 0;
|
uint32_t pixel_valid_mask_variable = 0;
|
||||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||||
|
uint32_t helper_invocation_variable = 0;
|
||||||
uint32_t per_vertex_variable = 0;
|
uint32_t per_vertex_variable = 0;
|
||||||
uint32_t point_size_variable = 0;
|
uint32_t point_size_variable = 0;
|
||||||
uint32_t clip_distance_variable = 0;
|
uint32_t clip_distance_variable = 0;
|
||||||
@@ -282,6 +284,7 @@ struct ImageSampleLayout {
|
|||||||
uint32_t bias = NoImageComponent;
|
uint32_t bias = NoImageComponent;
|
||||||
uint32_t coord = 0;
|
uint32_t coord = 0;
|
||||||
uint32_t lod = NoImageComponent;
|
uint32_t lod = NoImageComponent;
|
||||||
|
uint32_t clamp = NoImageComponent;
|
||||||
uint32_t grad_x = NoImageComponent;
|
uint32_t grad_x = NoImageComponent;
|
||||||
uint32_t grad_y = NoImageComponent;
|
uint32_t grad_y = NoImageComponent;
|
||||||
};
|
};
|
||||||
@@ -305,6 +308,7 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
|
|||||||
|
|
||||||
|
|
||||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
||||||
|
uint32_t MrtOutputType(EmitterState& state, uint32_t index);
|
||||||
|
|
||||||
uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||||
|
|
||||||
@@ -414,7 +418,7 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
|||||||
|
|
||||||
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
||||||
|
|
||||||
void EmitMemoryOffsets(EmitterState& state);
|
void EmitMemoryBindings(EmitterState& state);
|
||||||
|
|
||||||
uint32_t LdsDwordCount(const EmitterState& state);
|
uint32_t LdsDwordCount(const EmitterState& state);
|
||||||
void EnsureLdsStorage(EmitterState& state);
|
void EnsureLdsStorage(EmitterState& state);
|
||||||
|
|||||||
@@ -1541,16 +1541,16 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
const auto index =
|
const auto index =
|
||||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||||
const auto exec = ctx.Arg(inst, 1);
|
// Elect and count from the guest EXEC words, which V_MBCNT also ranks lanes by.
|
||||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
const auto low = ctx.Arg(inst, 1);
|
||||||
const auto low = state.builder.AllocateId();
|
const auto high = ctx.Arg(inst, 2);
|
||||||
const auto high = state.builder.AllocateId();
|
|
||||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
|
|
||||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
|
|
||||||
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||||
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
||||||
Unary(state, spv::OpBitCount, TypeU32(state), high));
|
Unary(state, spv::OpBitCount, TypeU32(state), high));
|
||||||
const auto first = ctx.FirstLane(ballot);
|
const auto mask = state.builder.AllocateId();
|
||||||
|
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), mask, low, high,
|
||||||
|
ConstantU32(state, 0), ConstantU32(state, 0));
|
||||||
|
const auto first = ctx.FirstLane(mask);
|
||||||
const auto source_lane =
|
const auto source_lane =
|
||||||
state.lane_count == 2
|
state.lane_count == 2
|
||||||
? Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 31))
|
? Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 31))
|
||||||
@@ -1564,9 +1564,7 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
const auto condition = AndCondition(
|
const auto condition = AndCondition(
|
||||||
state, is_first,
|
state, is_first,
|
||||||
AndCondition(state,
|
AndCondition(state,
|
||||||
state.lane_count == 2 ? Binary(state, spv::OpINotEqual, TypeBool(state), count,
|
Binary(state, spv::OpINotEqual, TypeBool(state), count, ConstantU32(state, 0)),
|
||||||
ConstantU32(state, 0))
|
|
||||||
: exec,
|
|
||||||
bounds));
|
bounds));
|
||||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||||
const auto value = state.builder.AllocateId();
|
const auto value = state.builder.AllocateId();
|
||||||
|
|||||||
@@ -154,9 +154,10 @@ uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resour
|
|||||||
return representative;
|
return representative;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmitMemoryOffsets(EmitterState& state) {
|
void EmitMemoryBindings(EmitterState& state) {
|
||||||
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
||||||
state.memory_byte_offsets.resize(count);
|
state.memory_byte_offsets.resize(count);
|
||||||
|
state.memory_byte_sizes.resize(count);
|
||||||
std::vector<bool> dynamic_offsets(count);
|
std::vector<bool> dynamic_offsets(count);
|
||||||
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
||||||
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
||||||
@@ -174,6 +175,8 @@ void EmitMemoryOffsets(EmitterState& state) {
|
|||||||
if (dynamic_offsets[i + lane]) continue;
|
if (dynamic_offsets[i + lane]) continue;
|
||||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
||||||
|
state.memory_byte_sizes[i + lane] =
|
||||||
|
EmitShaderDataDwordLoad(state, state.program.bindings.MemorySizeDword() + i + lane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,22 +260,28 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
|||||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||||
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
||||||
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
||||||
const auto offsets_start = state.program.bindings.memory_offset_dword +
|
const auto& bindings = state.program.bindings;
|
||||||
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
|
const auto data_start = bindings.UsesPushData() ? bindings.push_data_start_dword : 0u;
|
||||||
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||||
state, ConstantU32(state, offsets_start),
|
state, ConstantU32(state, bindings.memory_offset_dword + data_start),
|
||||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
|
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
|
||||||
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
||||||
ConstantU32(state, 3));
|
ConstantU32(state, 3));
|
||||||
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||||
state, word, shift, ConstantU32(state, 8));
|
state, word, shift, ConstantU32(state, 8));
|
||||||
|
access.length = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||||
|
state, ConstantU32(state, bindings.MemorySizeDword() + data_start), array_index));
|
||||||
} else {
|
} else {
|
||||||
access.byte_offset = state.memory_byte_offsets[static_index];
|
access.byte_offset = state.memory_byte_offsets[static_index];
|
||||||
|
access.length = state.memory_byte_sizes[static_index];
|
||||||
|
}
|
||||||
|
// storage_buffers[i] views the binding as 1 << i byte elements.
|
||||||
|
const auto shift = static_cast<uint32_t>(&buffer - state.storage_buffers.data());
|
||||||
|
if (shift != 0u) {
|
||||||
|
access.length = Binary(state, spv::OpShiftRightLogical, TypeU32(state), access.length,
|
||||||
|
ConstantU32(state, shift));
|
||||||
}
|
}
|
||||||
access.length = state.builder.AllocateId();
|
|
||||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
|
||||||
access.object_pointer, 0);
|
|
||||||
return access;
|
return access;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,6 +585,7 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
|||||||
case IR::ValueOpcode::ImageAtomicUMin32:
|
case IR::ValueOpcode::ImageAtomicUMin32:
|
||||||
case IR::ValueOpcode::ImageAtomicUMin64:
|
case IR::ValueOpcode::ImageAtomicUMin64:
|
||||||
case IR::ValueOpcode::BufferAtomicUMin32:
|
case IR::ValueOpcode::BufferAtomicUMin32:
|
||||||
|
case IR::ValueOpcode::BufferAtomicUMin64:
|
||||||
case IR::ValueOpcode::SharedAtomicUMin32: return spv::OpAtomicUMin;
|
case IR::ValueOpcode::SharedAtomicUMin32: return spv::OpAtomicUMin;
|
||||||
case IR::ValueOpcode::ImageAtomicSMax32:
|
case IR::ValueOpcode::ImageAtomicSMax32:
|
||||||
case IR::ValueOpcode::BufferAtomicSMax32:
|
case IR::ValueOpcode::BufferAtomicSMax32:
|
||||||
|
|||||||
@@ -476,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
|||||||
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
||||||
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
||||||
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
||||||
|
case IR::StageInputKind::BaseVertex: return spv::BuiltInBaseVertex;
|
||||||
|
case IR::StageInputKind::BaseInstance: return spv::BuiltInBaseInstance;
|
||||||
default: return UINT32_MAX;
|
default: return UINT32_MAX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,7 +528,9 @@ void DefineInputs(EmitterState& state) {
|
|||||||
case IR::StageInputKind::PrimitiveId:
|
case IR::StageInputKind::PrimitiveId:
|
||||||
case IR::StageInputKind::InstanceIndex:
|
case IR::StageInputKind::InstanceIndex:
|
||||||
case IR::StageInputKind::Layer:
|
case IR::StageInputKind::Layer:
|
||||||
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
|
case IR::StageInputKind::SampleId:
|
||||||
|
case IR::StageInputKind::BaseVertex:
|
||||||
|
case IR::StageInputKind::BaseInstance: type = TypeI32(state); break;
|
||||||
case IR::StageInputKind::WorkgroupId:
|
case IR::StageInputKind::WorkgroupId:
|
||||||
case IR::StageInputKind::NumWorkgroups:
|
case IR::StageInputKind::NumWorkgroups:
|
||||||
case IR::StageInputKind::LocalInvocationId:
|
case IR::StageInputKind::LocalInvocationId:
|
||||||
@@ -595,6 +599,13 @@ void DefineInputs(EmitterState& state) {
|
|||||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationFlat);
|
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationFlat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state.program.info.helper_invocation) {
|
||||||
|
const auto variable = DefineInterfaceVariable(state, TypeBool(state), spv::StorageClassInput,
|
||||||
|
"gl_HelperInvocation");
|
||||||
|
state.helper_invocation_variable = variable;
|
||||||
|
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
|
||||||
|
spv::BuiltInHelperInvocation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefineOutputs(EmitterState& state) {
|
void DefineOutputs(EmitterState& state) {
|
||||||
@@ -691,13 +702,15 @@ void DefineOutputs(EmitterState& state) {
|
|||||||
break;
|
break;
|
||||||
case IR::StageOutputKind::Parameter:
|
case IR::StageOutputKind::Parameter:
|
||||||
case IR::StageOutputKind::Mrt: {
|
case IR::StageOutputKind::Mrt: {
|
||||||
const bool uint_output =
|
auto type = TypeF32Vector(state, 4);
|
||||||
binding.kind == IR::StageOutputKind::Mrt &&
|
if (binding.kind == IR::StageOutputKind::Mrt &&
|
||||||
state.program.stage == ShaderType::Pixel &&
|
state.program.stage == ShaderType::Pixel && binding.index < 8u) {
|
||||||
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
|
// Packed UNorm stores its encoded bits.
|
||||||
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
|
const auto bit = 1u << binding.index;
|
||||||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
|
type = (state.input_info.pixel->target_packed_unorm_mask & bit) != 0u
|
||||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
? TypeU32Vector(state, 4)
|
||||||
|
: MrtOutputType(state, binding.index);
|
||||||
|
}
|
||||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||||
binding.debug_name.c_str());
|
binding.debug_name.c_str());
|
||||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
||||||
@@ -793,6 +806,10 @@ void DefineModule(EmitterState& state) {
|
|||||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||||
}
|
}
|
||||||
|
if (InputVariableForKind(state, IR::StageInputKind::BaseVertex) != 0 ||
|
||||||
|
InputVariableForKind(state, IR::StageInputKind::BaseInstance) != 0) {
|
||||||
|
state.builder.RequireCapability(spv::CapabilityDrawParameters);
|
||||||
|
}
|
||||||
if (state.program.info.image_gather_extended) {
|
if (state.program.info.image_gather_extended) {
|
||||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
|||||||
const auto op = inst.GetOpcode();
|
const auto op = inst.GetOpcode();
|
||||||
if (ctx.half != 0 &&
|
if (ctx.half != 0 &&
|
||||||
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
||||||
op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
||||||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
|
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
|
||||||
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
|
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
|
||||||
ctx.Define(inst, ctx.other_half->Result(inst));
|
ctx.Define(inst, ctx.other_half->Result(inst));
|
||||||
@@ -746,7 +746,7 @@ void EmitProgram(EmitterState& state) {
|
|||||||
state.lds_base_dwords = EmitBinaryU32(state, spv::OpIMul, index,
|
state.lds_base_dwords = EmitBinaryU32(state, spv::OpIMul, index,
|
||||||
ConstantU32(state, LdsDwordCount(state)));
|
ConstantU32(state, LdsDwordCount(state)));
|
||||||
}
|
}
|
||||||
EmitMemoryOffsets(state);
|
EmitMemoryBindings(state);
|
||||||
if (program.blocks.empty()) {
|
if (program.blocks.empty()) {
|
||||||
EmitReturn(ctx);
|
EmitReturn(ctx);
|
||||||
} else if (state.program.dispatcher_fallback) {
|
} else if (state.program.dispatcher_fallback) {
|
||||||
|
|||||||
@@ -1710,21 +1710,25 @@ Graph Structurize(const Graph& graph) {
|
|||||||
return GotoStructurizer(graph).Run();
|
return GotoStructurizer(graph).Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) {
|
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size) {
|
||||||
// Vector comparisons and carry results can write a scalar pair in wave64.
|
// A vector lane mask (comparison or carry) spans wave_size / 32 SGPRs; a lane read writes one.
|
||||||
if (inst.family == Decoder::Family::VOP1 || inst.family == Decoder::Family::VOP2 ||
|
if (inst.family == Decoder::Family::VOP1 || inst.family == Decoder::Family::VOP2 ||
|
||||||
inst.family == Decoder::Family::VOPC || inst.family == Decoder::Family::VOP3 ||
|
inst.family == Decoder::Family::VOPC || inst.family == Decoder::Family::VOP3 ||
|
||||||
inst.family == Decoder::Family::VOP3P) {
|
inst.family == Decoder::Family::VOP3P) {
|
||||||
|
const bool lane_read =
|
||||||
|
inst.opcode == Opcode::V_READLANE_B32 || inst.opcode == Opcode::V_READFIRSTLANE_B32;
|
||||||
|
const uint32_t size = lane_read ? 1u : wave_size / 32u;
|
||||||
uint32_t destination = 0;
|
uint32_t destination = 0;
|
||||||
for (const auto* operand: {&inst.dst, &inst.dst2})
|
for (const auto* operand: {&inst.dst, &inst.dst2})
|
||||||
if (ScalarOperandCode(*operand, destination) && code >= destination && code <= destination + 1u)
|
if (ScalarOperandCode(*operand, destination) && code >= destination && code < destination + size)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return InstructionWritesScalarCode(inst, code);
|
return InstructionWritesScalarCode(inst, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||||
uint32_t before, uint32_t code, std::vector<uint32_t>& crossed_calls) {
|
uint32_t before, uint32_t code, uint32_t wave_size,
|
||||||
|
std::vector<uint32_t>& crossed_calls) {
|
||||||
EXIT_IF(before >= program.instructions.size());
|
EXIT_IF(before >= program.instructions.size());
|
||||||
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
||||||
return block.inst_begin <= before && before < block.inst_end;
|
return block.inst_begin <= before && before < block.inst_end;
|
||||||
@@ -1743,11 +1747,11 @@ uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& grap
|
|||||||
for (uint32_t i = point.second; i > block->inst_begin;) {
|
for (uint32_t i = point.second; i > block->inst_begin;) {
|
||||||
const auto& inst = program.instructions[--i];
|
const auto& inst = program.instructions[--i];
|
||||||
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
||||||
EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code));
|
EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code, wave_size));
|
||||||
crossed_calls.push_back(i);
|
crossed_calls.push_back(i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (MayWriteScalarRegister(inst, code)) { index = i; break; }
|
if (MayWriteScalarRegister(inst, code, wave_size)) { index = i; break; }
|
||||||
}
|
}
|
||||||
if (index != UINT32_MAX || block->id == graph.entry_block) {
|
if (index != UINT32_MAX || block->id == graph.entry_block) {
|
||||||
EXIT_IF(definition && *definition != index);
|
EXIT_IF(definition && *definition != index);
|
||||||
|
|||||||
@@ -113,9 +113,10 @@ struct Graph {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Graph BuildGraph(const Decoder::Program& program);
|
Graph BuildGraph(const Decoder::Program& program);
|
||||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code);
|
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size);
|
||||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||||
uint32_t before, uint32_t code, std::vector<uint32_t>& crossed_calls);
|
uint32_t before, uint32_t code, uint32_t wave_size,
|
||||||
|
std::vector<uint32_t>& crossed_calls);
|
||||||
// Returns structured control flow or failure diagnostics without changing the native graph.
|
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||||
Graph Structurize(const Graph& graph);
|
Graph Structurize(const Graph& graph);
|
||||||
|
|||||||
@@ -14,12 +14,6 @@ struct MimgSampleInfo {
|
|||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MimgGatherInfo {
|
|
||||||
uint32_t encoding = 0;
|
|
||||||
Opcode decoded = Opcode::UNSUPPORTED;
|
|
||||||
uint32_t flags = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MimgAtomicInfo {
|
struct MimgAtomicInfo {
|
||||||
uint32_t encoding = 0;
|
uint32_t encoding = 0;
|
||||||
Opcode decoded = Opcode::UNSUPPORTED;
|
Opcode decoded = Opcode::UNSUPPORTED;
|
||||||
@@ -73,7 +67,7 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d
|
|||||||
if ((flags & ImageSampleFlagBias) != 0) {
|
if ((flags & ImageSampleFlagBias) != 0) {
|
||||||
components++;
|
components++;
|
||||||
}
|
}
|
||||||
if ((flags & ImageSampleFlagLod) != 0) {
|
if ((flags & (ImageSampleFlagLod | ImageSampleFlagLodClamp)) != 0) {
|
||||||
components++;
|
components++;
|
||||||
}
|
}
|
||||||
if ((flags & ImageSampleFlagDerivative) != 0) {
|
if ((flags & ImageSampleFlagDerivative) != 0) {
|
||||||
@@ -181,23 +175,72 @@ constexpr MimgSampleInfo MIMG_SAMPLE_OPCODE_LIST[] = {
|
|||||||
ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
|
constexpr MimgSampleInfo MIMG_GATHER_OPCODE_LIST[] = {
|
||||||
{0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod},
|
{0x40u, "image_gather4", 0},
|
||||||
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero},
|
{0x41u, "image_gather4_cl", ImageSampleFlagLodClamp},
|
||||||
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare},
|
{0x44u, "image_gather4_l", ImageSampleFlagLod},
|
||||||
{0x4cu, Opcode::IMAGE_GATHER4_C_L, ImageSampleFlagCompare | ImageSampleFlagLod},
|
{0x45u, "image_gather4_b", ImageSampleFlagBias},
|
||||||
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ,
|
{0x46u, "image_gather4_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp},
|
||||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero},
|
{0x47u, "image_gather4_lz", ImageSampleFlagLevelZero},
|
||||||
{0x57u, Opcode::IMAGE_GATHER4_LZ_O,
|
{0x48u, "image_gather4_c", ImageSampleFlagCompare},
|
||||||
ImageSampleFlagLevelZero | ImageSampleFlagOffset},
|
{0x49u, "image_gather4_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp},
|
||||||
{0x58u, Opcode::IMAGE_GATHER4_C_O,
|
{0x4cu, "image_gather4_c_l", ImageSampleFlagCompare | ImageSampleFlagLod},
|
||||||
ImageSampleFlagCompare | ImageSampleFlagOffset},
|
{0x4du, "image_gather4_c_b", ImageSampleFlagCompare | ImageSampleFlagBias},
|
||||||
{0x5fu, Opcode::IMAGE_GATHER4_C_LZ_O,
|
{0x4eu, "image_gather4_c_b_cl",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp},
|
||||||
|
{0x4fu, "image_gather4_c_lz", ImageSampleFlagCompare | ImageSampleFlagLevelZero},
|
||||||
|
{0x50u, "image_gather4_o", ImageSampleFlagOffset},
|
||||||
|
{0x51u, "image_gather4_cl_o", ImageSampleFlagLodClamp | ImageSampleFlagOffset},
|
||||||
|
{0x54u, "image_gather4_l_o", ImageSampleFlagLod | ImageSampleFlagOffset},
|
||||||
|
{0x55u, "image_gather4_b_o", ImageSampleFlagBias | ImageSampleFlagOffset},
|
||||||
|
{0x56u, "image_gather4_b_cl_o",
|
||||||
|
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
|
||||||
|
{0x57u, "image_gather4_lz_o", ImageSampleFlagLevelZero | ImageSampleFlagOffset},
|
||||||
|
{0x58u, "image_gather4_c_o", ImageSampleFlagCompare | ImageSampleFlagOffset},
|
||||||
|
{0x59u, "image_gather4_c_cl_o",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset},
|
||||||
|
{0x5cu, "image_gather4_c_l_o",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagLod | ImageSampleFlagOffset},
|
||||||
|
{0x5du, "image_gather4_c_b_o",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset},
|
||||||
|
{0x5eu, "image_gather4_c_b_cl_o",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||||
|
ImageSampleFlagOffset},
|
||||||
|
{0x5fu, "image_gather4_c_lz_o",
|
||||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset},
|
ImageSampleFlagCompare | ImageSampleFlagLevelZero | ImageSampleFlagOffset},
|
||||||
{0x61u, Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal},
|
{0x61u, "image_gather4h", ImageSampleFlagGatherHorizontal},
|
||||||
{0xc8u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare | ImageSampleFlagAdjust},
|
{0xc0u, "image_gather4_a", ImageSampleFlagAdjust},
|
||||||
{0xd8u, Opcode::IMAGE_GATHER4_C_O,
|
{0xc1u, "image_gather4_cl_a", ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
|
||||||
|
{0xc5u, "image_gather4_b_a", ImageSampleFlagBias | ImageSampleFlagAdjust},
|
||||||
|
{0xc6u, "image_gather4_b_cl_a",
|
||||||
|
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
|
||||||
|
{0xc8u, "image_gather4_c_a", ImageSampleFlagCompare | ImageSampleFlagAdjust},
|
||||||
|
{0xc9u, "image_gather4_c_cl_a",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagAdjust},
|
||||||
|
{0xcdu, "image_gather4_c_b_a",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagAdjust},
|
||||||
|
{0xceu, "image_gather4_c_b_cl_a",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||||
|
ImageSampleFlagAdjust},
|
||||||
|
{0xd0u, "image_gather4_o_a", ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||||
|
{0xd1u, "image_gather4_cl_o_a",
|
||||||
|
ImageSampleFlagLodClamp | ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||||
|
{0xd5u, "image_gather4_b_o_a",
|
||||||
|
ImageSampleFlagBias | ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||||
|
{0xd6u, "image_gather4_b_cl_o_a",
|
||||||
|
ImageSampleFlagBias | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
|
||||||
|
ImageSampleFlagAdjust},
|
||||||
|
{0xd8u, "image_gather4_c_o_a",
|
||||||
ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
ImageSampleFlagCompare | ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||||
|
{0xd9u, "image_gather4_c_cl_o_a",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagLodClamp | ImageSampleFlagOffset |
|
||||||
|
ImageSampleFlagAdjust},
|
||||||
|
{0xddu, "image_gather4_c_b_o_a",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagOffset |
|
||||||
|
ImageSampleFlagAdjust},
|
||||||
|
{0xdeu, "image_gather4_c_b_cl_o_a",
|
||||||
|
ImageSampleFlagCompare | ImageSampleFlagBias | ImageSampleFlagLodClamp |
|
||||||
|
ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = {
|
constexpr MimgAtomicInfo MIMG_ATOMIC_OPCODE_LIST[] = {
|
||||||
@@ -219,13 +262,13 @@ constexpr auto MIMG_SAMPLE_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_SAMPLE_OPCO
|
|||||||
constexpr auto MIMG_GATHER_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_GATHER_OPCODE_LIST);
|
constexpr auto MIMG_GATHER_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_GATHER_OPCODE_LIST);
|
||||||
constexpr auto MIMG_ATOMIC_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_ATOMIC_OPCODE_LIST);
|
constexpr auto MIMG_ATOMIC_OPS = Detail::MakeOpcodeTable<0x100>(MIMG_ATOMIC_OPCODE_LIST);
|
||||||
|
|
||||||
Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgGatherInfo* gather,
|
Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const MimgSampleInfo* gather,
|
||||||
const MimgAtomicInfo* atomic) {
|
const MimgAtomicInfo* atomic) {
|
||||||
if (sample != nullptr) {
|
if (sample != nullptr) {
|
||||||
return Opcode::IMAGE_SAMPLE;
|
return Opcode::IMAGE_SAMPLE;
|
||||||
}
|
}
|
||||||
if (gather != nullptr) {
|
if (gather != nullptr) {
|
||||||
return gather->decoded;
|
return Opcode::IMAGE_GATHER4;
|
||||||
}
|
}
|
||||||
if (atomic != nullptr) {
|
if (atomic != nullptr) {
|
||||||
return atomic->decoded;
|
return atomic->decoded;
|
||||||
@@ -244,24 +287,10 @@ Opcode DecodeMimgOpcode(uint32_t opcode, const MimgSampleInfo* sample, const Mim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t DecodeMimgSampleFlags(const MimgSampleInfo* sample, const MimgGatherInfo* gather) {
|
|
||||||
if (sample != nullptr) {
|
|
||||||
return sample->flags;
|
|
||||||
}
|
|
||||||
if (gather != nullptr) {
|
|
||||||
return gather->flags;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
|
uint32_t DecodeMimgAddressComponents(uint32_t opcode, ImageDimension dimension,
|
||||||
const MimgSampleInfo* sample, const MimgGatherInfo* gather,
|
const MimgSampleInfo* sampled, const MimgAtomicInfo* atomic) {
|
||||||
const MimgAtomicInfo* atomic) {
|
if (sampled != nullptr) {
|
||||||
if (sample != nullptr) {
|
return ImageSampleAddressComponents(sampled->flags, dimension);
|
||||||
return ImageSampleAddressComponents(sample->flags, dimension);
|
|
||||||
}
|
|
||||||
if (gather != nullptr) {
|
|
||||||
return ImageSampleAddressComponents(gather->flags, dimension);
|
|
||||||
}
|
}
|
||||||
if (atomic != nullptr) {
|
if (atomic != nullptr) {
|
||||||
return ImageCoordComponents(dimension);
|
return ImageCoordComponents(dimension);
|
||||||
@@ -290,33 +319,31 @@ uint32_t CountDmaskComponents(uint32_t dmask) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component) {
|
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, ImageDimension dimension,
|
||||||
const auto width = [flags](uint32_t index) {
|
uint32_t component) {
|
||||||
if ((flags & ImageSampleFlagA16) == 0u) return 32u;
|
if ((flags & ImageSampleFlagA16) == 0u) return {component * 32u, 32u};
|
||||||
uint32_t cursor = 0;
|
// A16 offsets, bias and PCF reference each take a whole VGPR, the bias as a half in its low
|
||||||
if ((flags & ImageSampleFlagOffset) != 0u) {
|
// 16 bits. The other components pack two per VGPR, except that each 1D derivative and the last
|
||||||
if (index == cursor++) return 32u;
|
// two 3D ones take the low half of their own VGPR.
|
||||||
}
|
const bool has_bias = (flags & ImageSampleFlagBias) != 0u;
|
||||||
if ((flags & ImageSampleFlagBias) != 0u) {
|
const uint32_t bias_index = (flags & ImageSampleFlagOffset) != 0u ? 1u : 0u;
|
||||||
if (index == cursor++) return 16u;
|
const uint32_t whole_vgprs = std::popcount(
|
||||||
}
|
flags & (ImageSampleFlagOffset | ImageSampleFlagBias | ImageSampleFlagCompare));
|
||||||
if ((flags & ImageSampleFlagCompare) != 0u && index == cursor) return 32u;
|
if (component < whole_vgprs) {
|
||||||
return 16u;
|
return {component * 32u, has_bias && component == bias_index ? 16u : 32u};
|
||||||
};
|
|
||||||
uint32_t offset = 0;
|
|
||||||
for (uint32_t index = 0; index < component; index++) {
|
|
||||||
const auto bits = width(index);
|
|
||||||
if (bits == 32u) offset = (offset + 31u) & ~31u;
|
|
||||||
offset += bits;
|
|
||||||
}
|
}
|
||||||
const auto bits = width(component);
|
const uint32_t axes =
|
||||||
if (bits == 32u) offset = (offset + 31u) & ~31u;
|
(flags & ImageSampleFlagDerivative) != 0u ? ImageGradientComponents(dimension) : 0u;
|
||||||
return {offset, bits};
|
const uint32_t unpaired = axes % 2u * 2u;
|
||||||
|
const uint32_t paired = axes * 2u - unpaired;
|
||||||
|
const uint32_t index = component - whole_vgprs;
|
||||||
|
const uint32_t half = index + std::clamp(index, paired, paired + unpaired) - paired;
|
||||||
|
return {whole_vgprs * 32u + half * 16u, 16u};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ImageAddressDwordCount(uint32_t flags, uint32_t components) {
|
uint32_t ImageAddressDwordCount(uint32_t flags, ImageDimension dimension, uint32_t components) {
|
||||||
if (components == 0u) return 0u;
|
if (components == 0u) return 0u;
|
||||||
const auto last = ImageAddressComponentLayout(flags, components - 1u);
|
const auto last = ImageAddressComponentLayout(flags, dimension, components - 1u);
|
||||||
return (last.bit_offset + last.bit_width + 31u) / 32u;
|
return (last.bit_offset + last.bit_width + 31u) / 32u;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,16 +356,17 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
const auto dimension = DecodeImageDimension((word0 >> 3u) & 0x7u);
|
const auto dimension = DecodeImageDimension((word0 >> 3u) & 0x7u);
|
||||||
const uint32_t word_count = 2u + nsa_dwords;
|
const uint32_t word_count = 2u + nsa_dwords;
|
||||||
|
|
||||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||||
const uint32_t vaddr = word1 & 0xffu;
|
const uint32_t vaddr = word1 & 0xffu;
|
||||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||||
const uint32_t ssamp = (word1 >> 21u) & 0x1fu;
|
const uint32_t ssamp = (word1 >> 21u) & 0x1fu;
|
||||||
const bool r128 = ((word0 >> 15u) & 0x1u) != 0u;
|
const bool r128 = ((word0 >> 15u) & 0x1u) != 0u;
|
||||||
const bool a16 = ((word1 >> 30u) & 0x1u) != 0u;
|
const bool a16 = ((word1 >> 30u) & 0x1u) != 0u;
|
||||||
const bool d16 = ((word1 >> 31u) & 0x1u) != 0u;
|
const bool d16 = ((word1 >> 31u) & 0x1u) != 0u;
|
||||||
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
||||||
const auto* gather = Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
|
const auto* gather = Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
|
||||||
const auto* atomic = Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode);
|
const auto* atomic = Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode);
|
||||||
|
const auto* sampled = sample != nullptr ? sample : gather;
|
||||||
|
|
||||||
inst.pc = pc;
|
inst.pc = pc;
|
||||||
inst.word_count = word_count;
|
inst.word_count = word_count;
|
||||||
@@ -351,11 +379,11 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
inst.data_dwords = d16 ? (inst.data_components + 1u) / 2u : inst.data_components;
|
inst.data_dwords = d16 ? (inst.data_components + 1u) / 2u : inst.data_components;
|
||||||
inst.glc = ((word0 >> 13u) & 1u) != 0;
|
inst.glc = ((word0 >> 13u) & 1u) != 0;
|
||||||
inst.slc = ((word0 >> 25u) & 1u) != 0;
|
inst.slc = ((word0 >> 25u) & 1u) != 0;
|
||||||
inst.image_sample_flags = DecodeMimgSampleFlags(sample, gather);
|
inst.image_sample_flags = sampled != nullptr ? sampled->flags : 0u;
|
||||||
if (a16) {
|
if (a16) {
|
||||||
inst.image_sample_flags |= ImageSampleFlagA16;
|
inst.image_sample_flags |= ImageSampleFlagA16;
|
||||||
}
|
}
|
||||||
if ((sample != nullptr || gather != nullptr) && (word0 & (1u << 12u)) != 0u) {
|
if (sampled != nullptr && (word0 & (1u << 12u)) != 0u) {
|
||||||
inst.image_sample_flags |= ImageSampleFlagUnnormalized;
|
inst.image_sample_flags |= ImageSampleFlagUnnormalized;
|
||||||
}
|
}
|
||||||
inst.image_dimension = dimension;
|
inst.image_dimension = dimension;
|
||||||
@@ -364,8 +392,7 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
for (uint32_t i = 0; i < nsa_dwords * 4u; i++) {
|
for (uint32_t i = 0; i < nsa_dwords * 4u; i++) {
|
||||||
inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu;
|
inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu;
|
||||||
}
|
}
|
||||||
inst.image_address_components =
|
inst.image_address_components = DecodeMimgAddressComponents(opcode, dimension, sampled, atomic);
|
||||||
DecodeMimgAddressComponents(opcode, dimension, sample, gather, atomic);
|
|
||||||
SetRawWords(inst, code, word_index, word_count);
|
SetRawWords(inst, code, word_index, word_count);
|
||||||
|
|
||||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||||
@@ -389,8 +416,8 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool supports_d16 = sample != nullptr || gather != nullptr || opcode == 0x00u ||
|
const bool supports_d16 = sampled != nullptr || opcode == 0x00u || opcode == 0x01u ||
|
||||||
opcode == 0x01u || opcode == 0x08u || opcode == 0x09u;
|
opcode == 0x08u || opcode == 0x09u;
|
||||||
if (d16 && !supports_d16) {
|
if (d16 && !supports_d16) {
|
||||||
SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode does not support D16 data");
|
SetUnsupported(inst, Family::MIMG, opcode, "MIMG opcode does not support D16 data");
|
||||||
}
|
}
|
||||||
@@ -407,9 +434,10 @@ void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
inst.src_count = 3;
|
inst.src_count = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* MimgSampleOpcodeName(uint32_t opcode) {
|
const char* MimgSampledOpcodeName(uint32_t opcode) {
|
||||||
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
||||||
return sample != nullptr ? sample->name : nullptr;
|
const auto* sampled = sample != nullptr ? sample : Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
|
||||||
|
return sampled != nullptr ? sampled->name : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||||
@@ -8,15 +8,16 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
|||||||
void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
void DecodeMimg(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||||
Instruction& inst);
|
Instruction& inst);
|
||||||
|
|
||||||
const char* MimgSampleOpcodeName(uint32_t opcode);
|
const char* MimgSampledOpcodeName(uint32_t opcode);
|
||||||
|
|
||||||
struct ImageAddressComponent {
|
struct ImageAddressComponent {
|
||||||
uint32_t bit_offset;
|
uint32_t bit_offset;
|
||||||
uint32_t bit_width;
|
uint32_t bit_width;
|
||||||
};
|
};
|
||||||
|
|
||||||
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component);
|
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, ImageDimension dimension,
|
||||||
uint32_t ImageAddressDwordCount(uint32_t flags, uint32_t components);
|
uint32_t component);
|
||||||
|
uint32_t ImageAddressDwordCount(uint32_t flags, ImageDimension dimension, uint32_t components);
|
||||||
|
|
||||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
|
|||||||
{0x04u, Opcode::S_LOAD_DWORDX16, 16, 32}, {0x08u, Opcode::S_BUFFER_LOAD_DWORD, 1, 32},
|
{0x04u, Opcode::S_LOAD_DWORDX16, 16, 32}, {0x08u, Opcode::S_BUFFER_LOAD_DWORD, 1, 32},
|
||||||
{0x09u, Opcode::S_BUFFER_LOAD_DWORDX2, 2, 32}, {0x0au, Opcode::S_BUFFER_LOAD_DWORDX4, 4, 32},
|
{0x09u, Opcode::S_BUFFER_LOAD_DWORDX2, 2, 32}, {0x0au, Opcode::S_BUFFER_LOAD_DWORDX4, 4, 32},
|
||||||
{0x0bu, Opcode::S_BUFFER_LOAD_DWORDX8, 8, 32}, {0x0cu, Opcode::S_BUFFER_LOAD_DWORDX16, 16, 32},
|
{0x0bu, Opcode::S_BUFFER_LOAD_DWORDX8, 8, 32}, {0x0cu, Opcode::S_BUFFER_LOAD_DWORDX16, 16, 32},
|
||||||
{0x25u, Opcode::S_MEMREALTIME, 2, 32},
|
{0x20u, Opcode::S_DCACHE_INV, 0, 0}, {0x25u, Opcode::S_MEMREALTIME, 2, 32},
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||||
@@ -69,6 +69,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
|||||||
{0x40u, Opcode::BUFFER_ATOMIC_FMAX, 1, 32},
|
{0x40u, Opcode::BUFFER_ATOMIC_FMAX, 1, 32},
|
||||||
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
|
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
|
||||||
{0x52u, Opcode::BUFFER_ATOMIC_ADD_X2, 2, 32},
|
{0x52u, Opcode::BUFFER_ATOMIC_ADD_X2, 2, 32},
|
||||||
|
{0x56u, Opcode::BUFFER_ATOMIC_UMIN_X2, 2, 32},
|
||||||
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
|
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
|
||||||
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
||||||
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
|
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
|
||||||
@@ -95,14 +96,14 @@ constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constexpr MemoryOpcodeInfo FLAT_OPCODE_LIST[] = {
|
constexpr MemoryOpcodeInfo FLAT_OPCODE_LIST[] = {
|
||||||
{0x08u, Opcode::FLAT_LOAD_UBYTE, 1, 8}, {0x09u, Opcode::FLAT_LOAD_SBYTE, 1, 8, true},
|
{0x08u, Opcode::FLAT_LOAD_UBYTE, 1, 8}, {0x09u, Opcode::FLAT_LOAD_SBYTE, 1, 8, true},
|
||||||
{0x0au, Opcode::FLAT_LOAD_USHORT, 1, 16}, {0x0bu, Opcode::FLAT_LOAD_SSHORT, 1, 16, true},
|
{0x0au, Opcode::FLAT_LOAD_USHORT, 1, 16}, {0x0bu, Opcode::FLAT_LOAD_SSHORT, 1, 16, true},
|
||||||
{0x0cu, Opcode::FLAT_LOAD_DWORD, 1, 32}, {0x0du, Opcode::FLAT_LOAD_DWORDX2, 2, 32},
|
{0x0cu, Opcode::FLAT_LOAD_DWORD, 1, 32}, {0x0du, Opcode::FLAT_LOAD_DWORDX2, 2, 32},
|
||||||
{0x0eu, Opcode::FLAT_LOAD_DWORDX4, 4, 32}, {0x0fu, Opcode::FLAT_LOAD_DWORDX3, 3, 32},
|
{0x0eu, Opcode::FLAT_LOAD_DWORDX4, 4, 32}, {0x0fu, Opcode::FLAT_LOAD_DWORDX3, 3, 32},
|
||||||
{0x18u, Opcode::FLAT_STORE_BYTE, 1, 8}, {0x1au, Opcode::FLAT_STORE_SHORT, 1, 16},
|
{0x18u, Opcode::FLAT_STORE_BYTE, 1, 8}, {0x1au, Opcode::FLAT_STORE_SHORT, 1, 16},
|
||||||
{0x1cu, Opcode::FLAT_STORE_DWORD, 1, 32}, {0x1du, Opcode::FLAT_STORE_DWORDX2, 2, 32},
|
{0x1cu, Opcode::FLAT_STORE_DWORD, 1, 32}, {0x1du, Opcode::FLAT_STORE_DWORDX2, 2, 32},
|
||||||
{0x1eu, Opcode::FLAT_STORE_DWORDX4, 4, 32}, {0x1fu, Opcode::FLAT_STORE_DWORDX3, 3, 32},
|
{0x1eu, Opcode::FLAT_STORE_DWORDX4, 4, 32}, {0x1fu, Opcode::FLAT_STORE_DWORDX3, 3, 32},
|
||||||
{0x24u, Opcode::FLAT_LOAD_SHORT_D16, 1, 16},
|
{0x24u, Opcode::FLAT_LOAD_SHORT_D16, 1, 16}, {0x25u, Opcode::FLAT_LOAD_SHORT_D16_HI, 1, 16},
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||||
@@ -285,6 +286,9 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||||
SetUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
|
SetUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
|
||||||
}
|
}
|
||||||
|
if (inst.opcode == Opcode::S_DCACHE_INV) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||||
if (inst.opcode == Opcode::S_MEMREALTIME) {
|
if (inst.opcode == Opcode::S_MEMREALTIME) {
|
||||||
@@ -419,9 +423,10 @@ void DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
|||||||
}
|
}
|
||||||
|
|
||||||
DecodeVectorGpr(IsFlatStoreOpcode(inst.opcode) ? data : vdst, inst.dst);
|
DecodeVectorGpr(IsFlatStoreOpcode(inst.opcode) ? data : vdst, inst.dst);
|
||||||
if (inst.opcode == Opcode::FLAT_LOAD_SHORT_D16) {
|
if (inst.opcode == Opcode::FLAT_LOAD_SHORT_D16 ||
|
||||||
// D16 loads reuse partial destinations to preserve the untouched high half.
|
inst.opcode == Opcode::FLAT_LOAD_SHORT_D16_HI) {
|
||||||
inst.dst.sdwa_sel = 4u;
|
// D16 loads reuse partial destinations to preserve the untouched half.
|
||||||
|
inst.dst.sdwa_sel = inst.opcode == Opcode::FLAT_LOAD_SHORT_D16_HI ? 5u : 4u;
|
||||||
}
|
}
|
||||||
DecodeVectorGpr(addr, inst.src0);
|
DecodeVectorGpr(addr, inst.src0);
|
||||||
inst.src_count = 1;
|
inst.src_count = 1;
|
||||||
|
|||||||
@@ -123,10 +123,9 @@ std::string ImageSampleFlagsToString(uint32_t flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string FormatMimg(const Instruction& inst) {
|
std::string FormatMimg(const Instruction& inst) {
|
||||||
const char* sample_name =
|
const char* sampled_name = MimgSampledOpcodeName(inst.opcode_id);
|
||||||
inst.opcode == Opcode::IMAGE_SAMPLE ? MimgSampleOpcodeName(inst.opcode_id) : nullptr;
|
|
||||||
const std::string_view name =
|
const std::string_view name =
|
||||||
sample_name != nullptr ? sample_name : magic_enum::enum_name(inst.opcode);
|
sampled_name != nullptr ? sampled_name : magic_enum::enum_name(inst.opcode);
|
||||||
std::string text =
|
std::string text =
|
||||||
fmt::format("0x{:08x}: {} {}, {}, {}, {} ; dmask=0x{:x} image_dim={}", inst.pc, name,
|
fmt::format("0x{:08x}: {} {}, {}, {}, {} ; dmask=0x{:x} image_dim={}", inst.pc, name,
|
||||||
OperandToString(inst.dst).c_str(), OperandToString(inst.src0).c_str(),
|
OperandToString(inst.dst).c_str(), OperandToString(inst.src0).c_str(),
|
||||||
@@ -138,22 +137,10 @@ std::string FormatMimg(const Instruction& inst) {
|
|||||||
if (inst.image_r128) {
|
if (inst.image_r128) {
|
||||||
text += " r128=1";
|
text += " r128=1";
|
||||||
}
|
}
|
||||||
switch (inst.opcode) {
|
if (sampled_name != nullptr) {
|
||||||
case Opcode::IMAGE_SAMPLE:
|
text += fmt::format(" sample_flags={} addr_components={}",
|
||||||
case Opcode::IMAGE_GATHER4_L:
|
ImageSampleFlagsToString(inst.image_sample_flags).c_str(),
|
||||||
case Opcode::IMAGE_GATHER4_LZ:
|
inst.image_address_components);
|
||||||
case Opcode::IMAGE_GATHER4_C:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_L:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_LZ:
|
|
||||||
case Opcode::IMAGE_GATHER4_LZ_O:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_O:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_LZ_O:
|
|
||||||
case Opcode::IMAGE_GATHER4H:
|
|
||||||
text += fmt::format(" sample_flags={} addr_components={}",
|
|
||||||
ImageSampleFlagsToString(inst.image_sample_flags).c_str(),
|
|
||||||
inst.image_address_components);
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
if (inst.image_nsa_dwords != 0) {
|
if (inst.image_nsa_dwords != 0) {
|
||||||
text += fmt::format(" nsa_dwords={} nsa_addr=", inst.image_nsa_dwords);
|
text += fmt::format(" nsa_dwords={} nsa_addr=", inst.image_nsa_dwords);
|
||||||
@@ -258,7 +245,9 @@ void DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand) {
|
|||||||
case 126u: operand.kind = OperandKind::ExecLo; return;
|
case 126u: operand.kind = OperandKind::ExecLo; return;
|
||||||
case 127u: operand.kind = OperandKind::ExecHi; return;
|
case 127u: operand.kind = OperandKind::ExecHi; return;
|
||||||
case 235u: operand.kind = OperandKind::SharedBase; return;
|
case 235u: operand.kind = OperandKind::SharedBase; return;
|
||||||
|
case 236u: operand.kind = OperandKind::SharedLimit; return;
|
||||||
case 237u: operand.kind = OperandKind::PrivateBase; return;
|
case 237u: operand.kind = OperandKind::PrivateBase; return;
|
||||||
|
case 238u: operand.kind = OperandKind::PrivateLimit; return;
|
||||||
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
||||||
case 248u:
|
case 248u:
|
||||||
operand.kind = OperandKind::FloatInlineConstant;
|
operand.kind = OperandKind::FloatInlineConstant;
|
||||||
@@ -459,7 +448,9 @@ std::string OperandToString(const Operand& operand) {
|
|||||||
case OperandKind::M0: text = "m0"; break;
|
case OperandKind::M0: text = "m0"; break;
|
||||||
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
|
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
|
||||||
case OperandKind::SharedBase: text = "shared_base"; break;
|
case OperandKind::SharedBase: text = "shared_base"; break;
|
||||||
|
case OperandKind::SharedLimit: text = "shared_limit"; break;
|
||||||
case OperandKind::PrivateBase: text = "private_base"; break;
|
case OperandKind::PrivateBase: text = "private_base"; break;
|
||||||
|
case OperandKind::PrivateLimit: text = "private_limit"; break;
|
||||||
case OperandKind::Null: text = "null"; break;
|
case OperandKind::Null: text = "null"; break;
|
||||||
default: text = "unknown"; break;
|
default: text = "unknown"; break;
|
||||||
}
|
}
|
||||||
@@ -566,6 +557,7 @@ std::string InstructionToString(const Instruction& inst) {
|
|||||||
OperandToString(inst.src0).c_str()));
|
OperandToString(inst.src0).c_str()));
|
||||||
case Opcode::S_BARRIER:
|
case Opcode::S_BARRIER:
|
||||||
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc));
|
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc));
|
||||||
|
case Opcode::S_DCACHE_INV:
|
||||||
case Opcode::BUFFER_GL0_INV:
|
case Opcode::BUFFER_GL0_INV:
|
||||||
case Opcode::BUFFER_GL1_INV:
|
case Opcode::BUFFER_GL1_INV:
|
||||||
return fmt::format("0x{:08x}: {}", inst.pc, magic_enum::enum_name(inst.opcode));
|
return fmt::format("0x{:08x}: {}", inst.pc, magic_enum::enum_name(inst.opcode));
|
||||||
@@ -612,15 +604,7 @@ std::string InstructionToString(const Instruction& inst) {
|
|||||||
case Opcode::IMAGE_LOAD_MIP:
|
case Opcode::IMAGE_LOAD_MIP:
|
||||||
case Opcode::IMAGE_GET_RESINFO:
|
case Opcode::IMAGE_GET_RESINFO:
|
||||||
case Opcode::IMAGE_GET_LOD:
|
case Opcode::IMAGE_GET_LOD:
|
||||||
case Opcode::IMAGE_GATHER4_L:
|
case Opcode::IMAGE_GATHER4: return WithUnsupportedReason(inst, FormatMimg(inst));
|
||||||
case Opcode::IMAGE_GATHER4_LZ:
|
|
||||||
case Opcode::IMAGE_GATHER4_C:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_L:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_LZ:
|
|
||||||
case Opcode::IMAGE_GATHER4_LZ_O:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_O:
|
|
||||||
case Opcode::IMAGE_GATHER4_C_LZ_O:
|
|
||||||
case Opcode::IMAGE_GATHER4H: return WithUnsupportedReason(inst, FormatMimg(inst));
|
|
||||||
case Opcode::S_LOAD_DWORD:
|
case Opcode::S_LOAD_DWORD:
|
||||||
case Opcode::S_LOAD_DWORDX2:
|
case Opcode::S_LOAD_DWORDX2:
|
||||||
case Opcode::S_LOAD_DWORDX4:
|
case Opcode::S_LOAD_DWORDX4:
|
||||||
@@ -681,6 +665,7 @@ std::string InstructionToString(const Instruction& inst) {
|
|||||||
case Opcode::BUFFER_ATOMIC_SUB:
|
case Opcode::BUFFER_ATOMIC_SUB:
|
||||||
case Opcode::BUFFER_ATOMIC_SMIN:
|
case Opcode::BUFFER_ATOMIC_SMIN:
|
||||||
case Opcode::BUFFER_ATOMIC_UMIN:
|
case Opcode::BUFFER_ATOMIC_UMIN:
|
||||||
|
case Opcode::BUFFER_ATOMIC_UMIN_X2:
|
||||||
case Opcode::BUFFER_ATOMIC_SMAX:
|
case Opcode::BUFFER_ATOMIC_SMAX:
|
||||||
case Opcode::BUFFER_ATOMIC_UMAX:
|
case Opcode::BUFFER_ATOMIC_UMAX:
|
||||||
case Opcode::BUFFER_ATOMIC_AND:
|
case Opcode::BUFFER_ATOMIC_AND:
|
||||||
@@ -697,6 +682,7 @@ std::string InstructionToString(const Instruction& inst) {
|
|||||||
case Opcode::FLAT_LOAD_USHORT:
|
case Opcode::FLAT_LOAD_USHORT:
|
||||||
case Opcode::FLAT_LOAD_SSHORT:
|
case Opcode::FLAT_LOAD_SSHORT:
|
||||||
case Opcode::FLAT_LOAD_SHORT_D16:
|
case Opcode::FLAT_LOAD_SHORT_D16:
|
||||||
|
case Opcode::FLAT_LOAD_SHORT_D16_HI:
|
||||||
case Opcode::FLAT_LOAD_DWORD:
|
case Opcode::FLAT_LOAD_DWORD:
|
||||||
case Opcode::FLAT_LOAD_DWORDX2:
|
case Opcode::FLAT_LOAD_DWORDX2:
|
||||||
case Opcode::FLAT_LOAD_DWORDX3:
|
case Opcode::FLAT_LOAD_DWORDX3:
|
||||||
|
|||||||
@@ -544,6 +544,7 @@ enum class Opcode {
|
|||||||
S_BUFFER_LOAD_DWORDX8,
|
S_BUFFER_LOAD_DWORDX8,
|
||||||
S_BUFFER_LOAD_DWORDX16,
|
S_BUFFER_LOAD_DWORDX16,
|
||||||
S_MEMREALTIME,
|
S_MEMREALTIME,
|
||||||
|
S_DCACHE_INV,
|
||||||
BUFFER_LOAD_FORMAT_X,
|
BUFFER_LOAD_FORMAT_X,
|
||||||
BUFFER_LOAD_FORMAT_XY,
|
BUFFER_LOAD_FORMAT_XY,
|
||||||
BUFFER_LOAD_FORMAT_XYZ,
|
BUFFER_LOAD_FORMAT_XYZ,
|
||||||
@@ -596,6 +597,7 @@ enum class Opcode {
|
|||||||
BUFFER_ATOMIC_SUB,
|
BUFFER_ATOMIC_SUB,
|
||||||
BUFFER_ATOMIC_SMIN,
|
BUFFER_ATOMIC_SMIN,
|
||||||
BUFFER_ATOMIC_UMIN,
|
BUFFER_ATOMIC_UMIN,
|
||||||
|
BUFFER_ATOMIC_UMIN_X2,
|
||||||
BUFFER_ATOMIC_SMAX,
|
BUFFER_ATOMIC_SMAX,
|
||||||
BUFFER_ATOMIC_UMAX,
|
BUFFER_ATOMIC_UMAX,
|
||||||
BUFFER_ATOMIC_AND,
|
BUFFER_ATOMIC_AND,
|
||||||
@@ -612,6 +614,7 @@ enum class Opcode {
|
|||||||
FLAT_LOAD_USHORT,
|
FLAT_LOAD_USHORT,
|
||||||
FLAT_LOAD_SSHORT,
|
FLAT_LOAD_SSHORT,
|
||||||
FLAT_LOAD_SHORT_D16,
|
FLAT_LOAD_SHORT_D16,
|
||||||
|
FLAT_LOAD_SHORT_D16_HI,
|
||||||
FLAT_LOAD_DWORD,
|
FLAT_LOAD_DWORD,
|
||||||
FLAT_LOAD_DWORDX2,
|
FLAT_LOAD_DWORDX2,
|
||||||
FLAT_LOAD_DWORDX3,
|
FLAT_LOAD_DWORDX3,
|
||||||
@@ -707,15 +710,7 @@ enum class Opcode {
|
|||||||
IMAGE_ATOMIC_FMIN,
|
IMAGE_ATOMIC_FMIN,
|
||||||
IMAGE_ATOMIC_FMAX,
|
IMAGE_ATOMIC_FMAX,
|
||||||
IMAGE_SAMPLE,
|
IMAGE_SAMPLE,
|
||||||
IMAGE_GATHER4_L,
|
IMAGE_GATHER4,
|
||||||
IMAGE_GATHER4_LZ,
|
|
||||||
IMAGE_GATHER4_C,
|
|
||||||
IMAGE_GATHER4_C_L,
|
|
||||||
IMAGE_GATHER4_C_LZ,
|
|
||||||
IMAGE_GATHER4_LZ_O,
|
|
||||||
IMAGE_GATHER4_C_O,
|
|
||||||
IMAGE_GATHER4_C_LZ_O,
|
|
||||||
IMAGE_GATHER4H,
|
|
||||||
V_INTERP_P1_F32,
|
V_INTERP_P1_F32,
|
||||||
V_INTERP_P2_F32,
|
V_INTERP_P2_F32,
|
||||||
V_INTERP_MOV_F32,
|
V_INTERP_MOV_F32,
|
||||||
@@ -766,7 +761,9 @@ enum class OperandKind {
|
|||||||
M0,
|
M0,
|
||||||
PopsExitingWaveId,
|
PopsExitingWaveId,
|
||||||
SharedBase,
|
SharedBase,
|
||||||
|
SharedLimit,
|
||||||
PrivateBase,
|
PrivateBase,
|
||||||
|
PrivateLimit,
|
||||||
Null,
|
Null,
|
||||||
Vgpr,
|
Vgpr,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,30 +5,20 @@
|
|||||||
|
|
||||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||||
|
|
||||||
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar,
|
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar) {
|
||||||
bool cmpx) {
|
|
||||||
if (scalar) {
|
if (scalar) {
|
||||||
ir.SetScc(value);
|
ir.SetScc(value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto masked = ir.LogicalAnd(ir.GetExec(), value);
|
WriteVectorMask(inst.dst, value);
|
||||||
if (cmpx) {
|
|
||||||
const auto mask = BallotMask(masked);
|
|
||||||
ir.SetExec(masked);
|
|
||||||
ir.SetExecLo(mask[0]);
|
|
||||||
ir.SetExecHi(mask[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
WriteMask(inst.dst, masked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar,
|
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar) {
|
||||||
bool cmpx) {
|
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar);
|
||||||
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar, cmpx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
IR::Type type, bool scalar, bool cmpx, bool swap_operands) {
|
IR::Type type, bool scalar, bool swap_operands) {
|
||||||
// The equality compares share their IR opcode with the unsigned ones.
|
// The equality compares share their IR opcode with the unsigned ones.
|
||||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||||
@@ -49,18 +39,18 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
|||||||
if (swap_operands) {
|
if (swap_operands) {
|
||||||
std::swap(lhs, rhs);
|
std::swap(lhs, rhs);
|
||||||
}
|
}
|
||||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), scalar, cmpx);
|
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), scalar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
void Translator::EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
bool signed_value, bool cmpx) {
|
bool signed_value) {
|
||||||
const auto lhs = ReadU16AsU32(inst.src0, signed_value);
|
const auto lhs = ReadU16AsU32(inst.src0, signed_value);
|
||||||
const auto rhs = ReadU16AsU32(inst.src1, signed_value);
|
const auto rhs = ReadU16AsU32(inst.src1, signed_value);
|
||||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false, cmpx);
|
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs})), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
bool half, bool cmpx) {
|
bool half) {
|
||||||
const auto type = IR::ArgTypeOf(opcode, 0);
|
const auto type = IR::ArgTypeOf(opcode, 0);
|
||||||
const auto lhs =
|
const auto lhs =
|
||||||
half ? IR::Value(ReadF16AsF32(inst.src0)) : ReadOperand(inst.src0, type);
|
half ? IR::Value(ReadF16AsF32(inst.src0)) : ReadOperand(inst.src0, type);
|
||||||
@@ -68,10 +58,10 @@ void Translator::EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpc
|
|||||||
half ? IR::Value(ReadF16AsF32(inst.src1)) : ReadOperand(inst.src1, type);
|
half ? IR::Value(ReadF16AsF32(inst.src1)) : ReadOperand(inst.src1, type);
|
||||||
const IR::FPCompareFlags flags{
|
const IR::FPCompareFlags flags{
|
||||||
.flush_input_denorms = !half && type == IR::Type::F32 && flush_f32_inputs};
|
.flush_input_denorms = !half && type == IR::Type::F32 && flush_f32_inputs};
|
||||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs}, flags)), false, cmpx);
|
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {lhs, rhs}, flags)), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered,
|
||||||
bool is64) {
|
bool is64) {
|
||||||
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
||||||
const auto is_nan_op = is64 ? IR::ValueOpcode::FPIsNan64 : IR::ValueOpcode::FPIsNan32;
|
const auto is_nan_op = is64 ? IR::ValueOpcode::FPIsNan64 : IR::ValueOpcode::FPIsNan32;
|
||||||
@@ -79,16 +69,16 @@ void Translator::EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool
|
|||||||
const auto rhs = ReadOperand(inst.src1, type);
|
const auto rhs = ReadOperand(inst.src1, type);
|
||||||
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
const auto unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
||||||
IR::U1(ir.Emit(is_nan_op, {rhs})));
|
IR::U1(ir.Emit(is_nan_op, {rhs})));
|
||||||
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false, cmpx);
|
EmitCompareResult(inst, ordered ? ir.LogicalNot(unordered) : unordered, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half) {
|
void Translator::EmitFloatClassCompare(const Decoder::Instruction& inst, bool half) {
|
||||||
const auto value = half ? IR::Value(Read16LaneBits(inst.src0, false))
|
const auto value = half ? IR::Value(Read16LaneBits(inst.src0, false))
|
||||||
: ReadOperand(inst.src0, IR::Type::F32);
|
: ReadOperand(inst.src0, IR::Type::F32);
|
||||||
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
||||||
: ReadOperand(inst.src1, IR::Type::U32);
|
: ReadOperand(inst.src1, IR::Type::U32);
|
||||||
const auto opcode = half ? IR::ValueOpcode::FPCmpClass16 : IR::ValueOpcode::FPCmpClass32;
|
const auto opcode = half ? IR::ValueOpcode::FPCmpClass16 : IR::ValueOpcode::FPCmpClass32;
|
||||||
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {value, mask})), false, cmpx);
|
EmitCompareResult(inst, IR::U1(ir.Emit(opcode, {value, mask})), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||||
@@ -51,33 +51,33 @@ void Translator::S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin)
|
|||||||
|
|
||||||
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
|
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
|
||||||
bool negate_exec, bool negate_source, bool write_64) {
|
bool negate_exec, bool negate_source, bool write_64) {
|
||||||
if (!write_64) {
|
const auto combine = [&](IR::U32 old, IR::U32 src) {
|
||||||
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
|
|
||||||
const auto old = ir.GetExecLo();
|
|
||||||
const auto src = ReadU32(inst.src0);
|
|
||||||
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
|
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
|
||||||
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
|
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
|
||||||
IR::U32 result;
|
return operation == IR::ValueOpcode::LogicalAnd ? ir.BitwiseAnd(lhs, rhs)
|
||||||
switch (operation) {
|
: ir.BitwiseOr(lhs, rhs);
|
||||||
case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break;
|
};
|
||||||
case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break;
|
if (!write_64) {
|
||||||
default: EXIT("unsupported SAVEEXEC operation");
|
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
|
||||||
}
|
const auto old = ir.GetExecLo();
|
||||||
|
const auto result = combine(old, ReadU32(inst.src0));
|
||||||
WriteRawU32(inst.dst, old);
|
WriteRawU32(inst.dst, old);
|
||||||
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
|
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
|
||||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto old = ir.GetExec();
|
// EXEC words combine bitwise; the per-lane EXEC combines the per-lane masks, so helper lanes
|
||||||
const auto src = ReadMask(inst.src0);
|
// stay enabled.
|
||||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
const auto old = ir.GetExec();
|
||||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
const auto src = ReadMask(inst.src0);
|
||||||
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
|
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||||
WriteMask(inst.dst, old, true);
|
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||||
const auto mask = BallotMask(result);
|
const auto src_mask = ReadU32Pair(inst.src0);
|
||||||
ir.SetExec(result);
|
const std::array old_mask {ir.GetExecLo(), ir.GetExecHi()};
|
||||||
ir.SetExecLo(mask[0]);
|
const std::array mask {combine(old_mask[0], src_mask[0]), combine(old_mask[1], src_mask[1])};
|
||||||
ir.SetExecHi(mask[1]);
|
WriteMask(inst.dst, old, old_mask, true);
|
||||||
|
WriteMask(ConditionOperand(Decoder::OperandKind::ExecLo), IR::U1(ir.Emit(operation, {lhs, rhs})),
|
||||||
|
mask);
|
||||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
|
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ void Translator::ADD_U32(const Decoder::Instruction& inst, bool vector, bool use
|
|||||||
}
|
}
|
||||||
WriteOperand(DestinationOperand(inst), result);
|
WriteOperand(DestinationOperand(inst), result);
|
||||||
if (vector) {
|
if (vector) {
|
||||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), carry));
|
WriteVectorMask(inst.dst2, carry);
|
||||||
} else {
|
} else {
|
||||||
ir.SetScc(carry);
|
ir.SetScc(carry);
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ void Translator::SUB_U32(const Decoder::Instruction& inst, bool vector, bool rev
|
|||||||
const auto borrow = ir.UGreaterThan(rhs, lhs);
|
const auto borrow = ir.UGreaterThan(rhs, lhs);
|
||||||
WriteOperand(DestinationOperand(inst), result);
|
WriteOperand(DestinationOperand(inst), result);
|
||||||
if (vector) {
|
if (vector) {
|
||||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
WriteVectorMask(inst.dst2, borrow);
|
||||||
} else {
|
} else {
|
||||||
ir.SetScc(borrow);
|
ir.SetScc(borrow);
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ void Translator::SUBB_U32(const Decoder::Instruction& inst, bool vector, bool re
|
|||||||
const auto borrow = ir.LogicalOr(borrow0, borrow1);
|
const auto borrow = ir.LogicalOr(borrow0, borrow1);
|
||||||
WriteOperand(DestinationOperand(inst), result);
|
WriteOperand(DestinationOperand(inst), result);
|
||||||
if (vector) {
|
if (vector) {
|
||||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
WriteVectorMask(inst.dst2, borrow);
|
||||||
} else {
|
} else {
|
||||||
ir.SetScc(borrow);
|
ir.SetScc(borrow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ void Translator::V_PACK_B32_F16(const Decoder::Instruction& inst) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IR::U32 Translator::PackU16Lanes(IR::U32 low, IR::U32 high) {
|
IR::U32 Translator::PackU16Lanes(IR::U32 low, IR::U32 high) {
|
||||||
|
// A zero high lane zero-extends; descriptor tracking bounds a mask, not a bitfield insert.
|
||||||
|
if (high == IR::Value(0u)) return ir.BitwiseAnd(low, IR::U32(IR::Value(0xffffu)));
|
||||||
return IR::U32(
|
return IR::U32(
|
||||||
ir.Emit(IR::ValueOpcode::BitFieldInsert, {low, high, IR::Value(16u), IR::Value(16u)}));
|
ir.Emit(IR::ValueOpcode::BitFieldInsert, {low, high, IR::Value(16u), IR::Value(16u)}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,19 +107,7 @@ void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
|||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
const auto lhs = ReadU32Pair(inst.src0);
|
||||||
(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Descriptor tracking reasons about each scalar word independently.
|
|
||||||
const auto lhs = ReadU32Pair(inst.src0);
|
|
||||||
auto mask_valid = ReadMaskValid(inst.src0);
|
|
||||||
if (inst.src_count > 1u) {
|
|
||||||
mask_valid = ir.LogicalAnd(mask_valid, ReadMaskValid(inst.src1));
|
|
||||||
}
|
|
||||||
std::array<IR::U32, 2> result;
|
std::array<IR::U32, 2> result;
|
||||||
if (unary) {
|
if (unary) {
|
||||||
result = {ir.BitwiseNot(lhs[0]), ir.BitwiseNot(lhs[1])};
|
result = {ir.BitwiseNot(lhs[0]), ir.BitwiseNot(lhs[1])};
|
||||||
@@ -132,11 +120,21 @@ void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
|||||||
result[component] = negate_result ? ir.BitwiseNot(IR::U32(value)) : IR::U32(value);
|
result[component] = negate_result ? ir.BitwiseNot(IR::U32(value)) : IR::U32(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WriteU32Pair(inst.dst, result);
|
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
||||||
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||||
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
WriteMask(inst.dst, invocation_result, result, true);
|
||||||
ir.SetThreadBitScalarReg(dst, invocation_result);
|
} else {
|
||||||
ir.SetScalarMaskTag(dst, mask_valid);
|
// Descriptor tracking reasons about each scalar word independently.
|
||||||
|
auto mask_valid = ReadMaskValid(inst.src0);
|
||||||
|
if (inst.src_count > 1u) {
|
||||||
|
mask_valid = ir.LogicalAnd(mask_valid, ReadMaskValid(inst.src1));
|
||||||
|
}
|
||||||
|
WriteU32Pair(inst.dst, result);
|
||||||
|
if (inst.dst.kind == Decoder::OperandKind::Sgpr) {
|
||||||
|
const auto dst = static_cast<IR::ScalarReg>(inst.dst.reg);
|
||||||
|
ir.SetThreadBitScalarReg(dst, invocation_result);
|
||||||
|
ir.SetScalarMaskTag(dst, mask_valid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ir.SetScc(ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u))));
|
ir.SetScc(ir.INotEqual(ir.BitwiseOr(result[0], result[1]), IR::U32(IR::Value(0u))));
|
||||||
}
|
}
|
||||||
@@ -292,7 +290,8 @@ void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
|||||||
if (inst.dst2.kind != Decoder::OperandKind::Null &&
|
if (inst.dst2.kind != Decoder::OperandKind::Null &&
|
||||||
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
||||||
// The 32x32 product fits in 64 bits, so only the addition can carry.
|
// The 32x32 product fits in 64 bits, so only the addition can carry.
|
||||||
WriteMask(inst.dst2, IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product})));
|
const auto carry = IR::U1(ir.Emit(IR::ValueOpcode::ULessThan64, {result, product}));
|
||||||
|
WriteVectorMask(inst.dst2, carry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,7 +471,7 @@ void Translator::S_BITCMP_B32(const Decoder::Instruction& inst, bool expected) {
|
|||||||
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
const auto offset = ir.BitwiseAnd(ReadU32(inst.src1), IR::U32(IR::Value(31u)));
|
||||||
const auto bit =
|
const auto bit =
|
||||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {value, offset, IR::Value(1u)}));
|
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))));
|
ir.SetScc(ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||||
@@ -483,7 +482,7 @@ void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
|||||||
ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]);
|
ir.Select(ir.ULessThan(offset, IR::U32(IR::Value(32u))), value[0], value[1]);
|
||||||
const auto bit =
|
const auto bit =
|
||||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
|
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))));
|
ir.SetScc(ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::V_ALIGN_B32(const Decoder::Instruction& inst, bool byte_offset) {
|
void Translator::V_ALIGN_B32(const Decoder::Instruction& inst, bool byte_offset) {
|
||||||
|
|||||||
@@ -322,8 +322,8 @@ IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
|
|||||||
const Decoder::Operand& base) {
|
const Decoder::Operand& base) {
|
||||||
std::array<IR::Value, 13> components {};
|
std::array<IR::Value, 13> components {};
|
||||||
components.fill(IR::Value(0u));
|
components.fill(IR::Value(0u));
|
||||||
const auto count =
|
const auto count = Decoder::ImageAddressDwordCount(
|
||||||
Decoder::ImageAddressDwordCount(inst.image_sample_flags, inst.image_address_components);
|
inst.image_sample_flags, inst.image_dimension, inst.image_address_components);
|
||||||
EXIT_IF(count > components.size());
|
EXIT_IF(count > components.size());
|
||||||
const auto nsa_components =
|
const auto nsa_components =
|
||||||
std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
||||||
@@ -863,9 +863,10 @@ void Translator::DS_WRITE2(const Decoder::Instruction& inst) {
|
|||||||
|
|
||||||
void 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);
|
const auto memory = MemoryInfoFromDecoded(inst);
|
||||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
const auto exec = ExecMask();
|
||||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
WriteOperand(inst.dst,
|
||||||
AddMemoryInfo(memory, inst.pc)));
|
ir.Emit(opcode, {ReadU32(MemorySourceAt(inst, 0)), exec[0], exec[1], ir.GetExec()},
|
||||||
|
AddMemoryInfo(memory, inst.pc)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
|
void Translator::DS_ADDTID(const Decoder::Instruction& inst, bool write) {
|
||||||
@@ -901,10 +902,7 @@ void Translator::DS_ORDERED_COUNT(const Decoder::Instruction& inst) {
|
|||||||
const auto packer = ir.BitwiseAnd(m0, IR::U32(IR::Value(0xfu)));
|
const auto packer = ir.BitwiseAnd(m0, IR::U32(IR::Value(0xfu)));
|
||||||
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
|
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
|
||||||
}
|
}
|
||||||
const auto exec = ir.GetExec();
|
const auto [low, high] = ExecMask();
|
||||||
const auto ballot = ir.Emit(IR::ValueOpcode::Ballot, {exec});
|
|
||||||
const auto low = ir.CompositeExtract(ballot, 0u);
|
|
||||||
const auto high = ir.CompositeExtract(ballot, 1u);
|
|
||||||
const auto active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
|
const auto active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
|
||||||
const auto first = ir.Select(
|
const auto first = ir.Select(
|
||||||
ir.INotEqual(low, IR::U32(IR::Value(0u))),
|
ir.INotEqual(low, IR::U32(IR::Value(0u))),
|
||||||
@@ -943,6 +941,8 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
|
|||||||
|
|
||||||
void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||||
switch (inst.opcode) {
|
switch (inst.opcode) {
|
||||||
|
// Translated scalar loads have no cache of their own to invalidate.
|
||||||
|
case Decoder::Opcode::S_DCACHE_INV: return;
|
||||||
case Decoder::Opcode::BUFFER_GL0_INV:
|
case Decoder::Opcode::BUFFER_GL0_INV:
|
||||||
case Decoder::Opcode::BUFFER_GL1_INV:
|
case Decoder::Opcode::BUFFER_GL1_INV:
|
||||||
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
|
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
|
||||||
@@ -1031,6 +1031,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
|||||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMin32);
|
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMin32);
|
||||||
case Decoder::Opcode::BUFFER_ATOMIC_UMIN:
|
case Decoder::Opcode::BUFFER_ATOMIC_UMIN:
|
||||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicUMin32);
|
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicUMin32);
|
||||||
|
case Decoder::Opcode::BUFFER_ATOMIC_UMIN_X2:
|
||||||
|
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicUMin64);
|
||||||
case Decoder::Opcode::BUFFER_ATOMIC_SMAX:
|
case Decoder::Opcode::BUFFER_ATOMIC_SMAX:
|
||||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMax32);
|
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMax32);
|
||||||
case Decoder::Opcode::BUFFER_ATOMIC_UMAX:
|
case Decoder::Opcode::BUFFER_ATOMIC_UMAX:
|
||||||
@@ -1148,6 +1150,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
|||||||
case Decoder::Opcode::FLAT_LOAD_USHORT:
|
case Decoder::Opcode::FLAT_LOAD_USHORT:
|
||||||
case Decoder::Opcode::FLAT_LOAD_SSHORT:
|
case Decoder::Opcode::FLAT_LOAD_SSHORT:
|
||||||
case Decoder::Opcode::FLAT_LOAD_SHORT_D16:
|
case Decoder::Opcode::FLAT_LOAD_SHORT_D16:
|
||||||
|
case Decoder::Opcode::FLAT_LOAD_SHORT_D16_HI:
|
||||||
case Decoder::Opcode::FLAT_LOAD_DWORD:
|
case Decoder::Opcode::FLAT_LOAD_DWORD:
|
||||||
case Decoder::Opcode::FLAT_LOAD_DWORDX2:
|
case Decoder::Opcode::FLAT_LOAD_DWORDX2:
|
||||||
case Decoder::Opcode::FLAT_LOAD_DWORDX3:
|
case Decoder::Opcode::FLAT_LOAD_DWORDX3:
|
||||||
@@ -1167,15 +1170,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
|||||||
case Decoder::Opcode::IMAGE_STORE:
|
case Decoder::Opcode::IMAGE_STORE:
|
||||||
case Decoder::Opcode::IMAGE_STORE_MIP: return IMAGE_STORE(inst);
|
case Decoder::Opcode::IMAGE_STORE_MIP: return IMAGE_STORE(inst);
|
||||||
case Decoder::Opcode::IMAGE_SAMPLE: return IMAGE_SAMPLE(inst);
|
case Decoder::Opcode::IMAGE_SAMPLE: return IMAGE_SAMPLE(inst);
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_L:
|
case Decoder::Opcode::IMAGE_GATHER4: return IMAGE_GATHER(inst);
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_LZ:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_C:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_C_L:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_C_LZ:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_LZ_O:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_C_O:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4_C_LZ_O:
|
|
||||||
case Decoder::Opcode::IMAGE_GATHER4H: return IMAGE_GATHER(inst);
|
|
||||||
|
|
||||||
case Decoder::Opcode::DS_MIN_F32:
|
case Decoder::Opcode::DS_MIN_F32:
|
||||||
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicFMin32, false);
|
return DS_ATOMIC(inst, IR::ValueOpcode::SharedAtomicFMin32, false);
|
||||||
|
|||||||
@@ -73,43 +73,41 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
|||||||
|
|
||||||
case O::S_CMP_EQ_U32:
|
case O::S_CMP_EQ_U32:
|
||||||
case O::S_CMP_EQ_I32:
|
case O::S_CMP_EQ_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LG_U32:
|
case O::S_CMP_LG_U32:
|
||||||
case O::S_CMP_LG_I32:
|
case O::S_CMP_LG_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_GT_U32:
|
case O::S_CMP_GT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_GE_U32:
|
case O::S_CMP_GE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true);
|
||||||
false);
|
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LT_U32:
|
case O::S_CMP_LT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LE_U32:
|
case O::S_CMP_LE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_GT_I32:
|
case O::S_CMP_GT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_GE_I32:
|
case O::S_CMP_GE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true);
|
||||||
false);
|
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LT_I32:
|
case O::S_CMP_LT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LE_I32:
|
case O::S_CMP_LE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_EQ_U64:
|
case O::S_CMP_EQ_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, true);
|
||||||
return;
|
return;
|
||||||
case O::S_CMP_LG_U64:
|
case O::S_CMP_LG_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, true, false);
|
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, true);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case O::S_AND_B64:
|
case O::S_AND_B64:
|
||||||
|
|||||||
@@ -129,6 +129,20 @@ std::array<IR::U32, 2> Translator::BallotMask(IR::U1 value) {
|
|||||||
program.wave_size == 64u ? ir.CompositeExtract(mask, 1) : IR::U32(IR::Value(0u))};
|
program.wave_size == 64u ? ir.CompositeExtract(mask, 1) : IR::U32(IR::Value(0u))};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<IR::U32, 2> Translator::ExecMask() {
|
||||||
|
return {ir.GetExecLo(), program.wave_size == 64u ? ir.GetExecHi() : IR::U32(IR::Value(0u))};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pixel EXEC words exclude the helper invocations that the per-lane EXEC keeps enabled.
|
||||||
|
std::array<IR::U32, 2> Translator::VectorMask(IR::U1 value) {
|
||||||
|
auto mask = BallotMask(value);
|
||||||
|
if (program.stage == ShaderType::Pixel) {
|
||||||
|
const auto exec = ExecMask();
|
||||||
|
mask = {ir.BitwiseAnd(mask[0], exec[0]), ir.BitwiseAnd(mask[1], exec[1])};
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
||||||
switch (operand.kind) {
|
switch (operand.kind) {
|
||||||
case Decoder::OperandKind::LiteralConstant:
|
case Decoder::OperandKind::LiteralConstant:
|
||||||
@@ -138,6 +152,8 @@ IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
|||||||
case Decoder::OperandKind::SharedBase:
|
case Decoder::OperandKind::SharedBase:
|
||||||
case Decoder::OperandKind::PrivateBase:
|
case Decoder::OperandKind::PrivateBase:
|
||||||
case Decoder::OperandKind::PopsExitingWaveId: return IR::U32(IR::Value(0u));
|
case Decoder::OperandKind::PopsExitingWaveId: return IR::U32(IR::Value(0u));
|
||||||
|
case Decoder::OperandKind::SharedLimit:
|
||||||
|
case Decoder::OperandKind::PrivateLimit: return IR::U32(IR::Value(0xffffffffu));
|
||||||
case Decoder::OperandKind::Sgpr:
|
case Decoder::OperandKind::Sgpr:
|
||||||
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
|
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
|
||||||
case Decoder::OperandKind::Vgpr:
|
case Decoder::OperandKind::Vgpr:
|
||||||
@@ -388,21 +404,9 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
|||||||
switch (operand.kind) {
|
switch (operand.kind) {
|
||||||
case Decoder::OperandKind::Scc: ir.SetScc(IR::U1(value)); return;
|
case Decoder::OperandKind::Scc: ir.SetScc(IR::U1(value)); return;
|
||||||
case Decoder::OperandKind::ExecLo:
|
case Decoder::OperandKind::ExecLo:
|
||||||
case Decoder::OperandKind::ExecHi: {
|
case Decoder::OperandKind::ExecHi:
|
||||||
const auto mask = BallotMask(IR::U1(value));
|
|
||||||
ir.SetExec(IR::U1(value));
|
|
||||||
ir.SetExecLo(mask[0]);
|
|
||||||
ir.SetExecHi(mask[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case Decoder::OperandKind::VccLo:
|
case Decoder::OperandKind::VccLo:
|
||||||
case Decoder::OperandKind::VccHi: {
|
case Decoder::OperandKind::VccHi: EXIT("lane masks are written through WriteMask");
|
||||||
const auto mask = BallotMask(IR::U1(value));
|
|
||||||
ir.SetVcc(IR::U1(value));
|
|
||||||
ir.SetVccLo(mask[0]);
|
|
||||||
ir.SetVccHi(mask[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
||||||
IR::U32(IR::Value(0u))));
|
IR::U32(IR::Value(0u))));
|
||||||
@@ -471,17 +475,17 @@ IR::U32 Translator::ReadU32(const Decoder::Operand& operand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand) {
|
std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand) {
|
||||||
if (operand.kind == Decoder::OperandKind::PrivateBase ||
|
// Each aperture limit is its base plus 0xffffffff.
|
||||||
operand.kind == Decoder::OperandKind::SharedBase) {
|
switch (operand.kind) {
|
||||||
return {IR::U32(IR::Value(0u)), IR::U32(IR::Value(
|
case Decoder::OperandKind::SharedBase:
|
||||||
operand.kind == Decoder::OperandKind::PrivateBase ? Decoder::PrivateApertureHigh
|
case Decoder::OperandKind::SharedLimit:
|
||||||
: Decoder::SharedApertureHigh))};
|
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::SharedApertureHigh))};
|
||||||
}
|
case Decoder::OperandKind::PrivateBase:
|
||||||
if (operand.kind == Decoder::OperandKind::ExecLo) {
|
case Decoder::OperandKind::PrivateLimit:
|
||||||
return {ir.GetExecLo(), ir.GetExecHi()};
|
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::PrivateApertureHigh))};
|
||||||
}
|
case Decoder::OperandKind::ExecLo: return {ir.GetExecLo(), ir.GetExecHi()};
|
||||||
if (operand.kind == Decoder::OperandKind::VccLo) {
|
case Decoder::OperandKind::VccLo: return {ir.GetVccLo(), ir.GetVccHi()};
|
||||||
return {ir.GetVccLo(), ir.GetVccHi()};
|
default: break;
|
||||||
}
|
}
|
||||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||||
const auto bits =
|
const auto bits =
|
||||||
@@ -639,7 +643,7 @@ IR::U1 Translator::ReadMask(const Decoder::Operand& operand) {
|
|||||||
}
|
}
|
||||||
case Decoder::OperandKind::ExecLo:
|
case Decoder::OperandKind::ExecLo:
|
||||||
case Decoder::OperandKind::ExecHi: return ir.GetExec();
|
case Decoder::OperandKind::ExecHi: return ir.GetExec();
|
||||||
case Decoder::OperandKind::VccLo:
|
case Decoder::OperandKind::VccLo: return ir.GetVcc();
|
||||||
case Decoder::OperandKind::VccHi:
|
case Decoder::OperandKind::VccHi:
|
||||||
return program.wave_size == 32u
|
return program.wave_size == 32u
|
||||||
? ThreadBit({ReadRawU32(operand), IR::U32(IR::Value(0u))})
|
? ThreadBit({ReadRawU32(operand), IR::U32(IR::Value(0u))})
|
||||||
@@ -677,9 +681,8 @@ IR::U1 Translator::ReadMaskValid(const Decoder::Operand& operand) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||||
bool write_64) {
|
const std::array<IR::U32, 2>& mask, bool write_64) {
|
||||||
const auto mask = BallotMask(value);
|
|
||||||
switch (operand.kind) {
|
switch (operand.kind) {
|
||||||
case Decoder::OperandKind::Sgpr: {
|
case Decoder::OperandKind::Sgpr: {
|
||||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||||
@@ -700,43 +703,43 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
|
|||||||
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||||
}
|
}
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
case Decoder::OperandKind::ExecLo:
|
case Decoder::OperandKind::ExecLo:
|
||||||
case Decoder::OperandKind::ExecHi: {
|
case Decoder::OperandKind::ExecHi: {
|
||||||
ir.SetExec(value);
|
ir.SetExec(value);
|
||||||
ir.SetExecLo(mask[0]);
|
ir.SetExecLo(mask[0]);
|
||||||
ir.SetExecHi(mask[1]);
|
ir.SetExecHi(mask[1]);
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
case Decoder::OperandKind::VccLo:
|
case Decoder::OperandKind::VccLo:
|
||||||
case Decoder::OperandKind::VccHi: {
|
case Decoder::OperandKind::VccHi: {
|
||||||
if (!write_64 && program.wave_size == 32u) {
|
if (operand.kind == Decoder::OperandKind::VccHi && !write_64 &&
|
||||||
|
program.wave_size == 32u) {
|
||||||
WriteRawU32(operand, mask[0]);
|
WriteRawU32(operand, mask[0]);
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
ir.SetVcc(value);
|
ir.SetVcc(value);
|
||||||
ir.SetVccLo(mask[0]);
|
ir.SetVccLo(mask[0]);
|
||||||
ir.SetVccHi(mask[1]);
|
if (write_64 || program.wave_size == 64u) {
|
||||||
return mask;
|
ir.SetVccHi(mask[1]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
case Decoder::OperandKind::Scc: ir.SetScc(value); return mask;
|
case Decoder::OperandKind::Scc: ir.SetScc(value); return;
|
||||||
default:
|
default:
|
||||||
WriteRawU32(operand, mask[0]);
|
WriteRawU32(operand, mask[0]);
|
||||||
WriteRawU32(OffsetOperand(operand, 1), mask[1]);
|
WriteRawU32(OffsetOperand(operand, 1), mask[1]);
|
||||||
return mask;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 value) {
|
void Translator::WriteVectorMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||||
if (operand.kind == Decoder::OperandKind::Scc) {
|
|
||||||
WriteOperand(operand, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// VALU instructions only write lanes enabled by the incoming EXEC mask. This is
|
// VALU instructions only write lanes enabled by the incoming EXEC mask. This is
|
||||||
// especially important for V_CMPX: replacing EXEC with the ungated comparison would
|
// especially important for V_CMPX: replacing EXEC with the ungated comparison would
|
||||||
// reactivate lanes disabled by an enclosing divergent region.
|
// reactivate lanes disabled by an enclosing divergent region.
|
||||||
WriteMask(operand, ir.LogicalAnd(ir.GetExec(), value));
|
const auto active = ir.LogicalAnd(ir.GetExec(), value);
|
||||||
|
WriteMask(operand, active, VectorMask(active));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
void Translator::AddBranchCondition(const CFG::Graph& graph, const CFG::BasicBlock& source,
|
||||||
@@ -1153,7 +1156,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry_ir.SetExec(initial_exec);
|
entry_ir.SetExec(initial_exec);
|
||||||
const auto initial_mask = entry_ir.Emit(IR::ValueOpcode::Ballot, {initial_exec});
|
// Pixel EXEC words start with the live (non-helper) lanes; helpers keep the per-lane EXEC.
|
||||||
|
const auto initial_mask = options.stage == ShaderType::Pixel
|
||||||
|
? entry_ir.Emit(IR::ValueOpcode::LiveMask)
|
||||||
|
: entry_ir.Emit(IR::ValueOpcode::Ballot, {initial_exec});
|
||||||
const auto initial_lo = entry_ir.CompositeExtract(initial_mask, 0);
|
const auto initial_lo = entry_ir.CompositeExtract(initial_mask, 0);
|
||||||
const auto initial_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1)
|
const auto initial_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1)
|
||||||
: IR::U32(IR::Value(0u));
|
: IR::U32(IR::Value(0u));
|
||||||
@@ -1238,12 +1244,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||||
primitive_count, u32(8)),
|
primitive_count, u32(8)),
|
||||||
vertex_count)));
|
vertex_count)));
|
||||||
|
// Instance IDs exclude the start instance, which shaders add from user data.
|
||||||
if (mesh.fast_launch) {
|
if (mesh.fast_launch) {
|
||||||
// Fast launch broadcasts the subgroup's base vertex and instance to every lane.
|
// Fast launch broadcasts the subgroup's base vertex and instance to every lane.
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5), entry_ir.IAdd(draw(1), group));
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5), entry_ir.IAdd(draw(1), group));
|
||||||
entry_ir.SetVectorReg(
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(6),
|
||||||
static_cast<IR::VectorReg>(6),
|
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
|
||||||
} else {
|
} else {
|
||||||
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
|
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
|
||||||
// center in every subgroup; strip winding follows the global primitive.
|
// center in every subgroup; strip winding follows the global primitive.
|
||||||
@@ -1271,14 +1277,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
entry_ir.ShiftLeftLogical(second, u32(18))));
|
entry_ir.ShiftLeftLogical(second, u32(18))));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
||||||
entry_ir.ShiftLeftLogical(third, u32(2)));
|
entry_ir.ShiftLeftLogical(third, u32(2)));
|
||||||
const auto index_bytes = draw(3);
|
const auto index_bytes = draw(2);
|
||||||
const auto indexed = entry_ir.INotEqual(index_bytes, u32(0));
|
const auto indexed = entry_ir.INotEqual(index_bytes, u32(0));
|
||||||
const auto index_low = draw(4);
|
const auto index_low = draw(3);
|
||||||
const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)),
|
const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)),
|
||||||
entry_ir.IMul(input_vertex, index_bytes));
|
entry_ir.IMul(input_vertex, index_bytes));
|
||||||
const auto index_resource = entry_ir.Emit(
|
const auto index_resource = entry_ir.Emit(
|
||||||
IR::ValueOpcode::GetAddressResource,
|
IR::ValueOpcode::GetAddressResource,
|
||||||
{entry_ir.BitwiseAnd(index_low, u32(~3u)), draw(5)});
|
{entry_ir.BitwiseAnd(index_low, u32(~3u)), draw(4)});
|
||||||
const auto memory_index = static_cast<uint32_t>(result.memory_info.size());
|
const auto memory_index = static_cast<uint32_t>(result.memory_info.size());
|
||||||
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
||||||
const auto packed_index = entry_ir.Emit(
|
const auto packed_index = entry_ir.Emit(
|
||||||
@@ -1292,9 +1298,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
entry_ir.IMul(index_bytes, u32(8))}));
|
entry_ir.IMul(index_bytes, u32(8))}));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||||
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
||||||
entry_ir.SetVectorReg(
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||||
static_cast<IR::VectorReg>(8),
|
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
|
||||||
}
|
}
|
||||||
} else if (options.stage == ShaderType::Local) {
|
} else if (options.stage == ShaderType::Local) {
|
||||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
|
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
|
||||||
@@ -1384,10 +1389,27 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
|||||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
||||||
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
|
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
|
||||||
|
// Vulkan adds an indirect record's bases to the indices; the hardware passes them
|
||||||
|
// only in the user SGPRs the packet patches.
|
||||||
|
const auto& offsets = options.input_info.vertex->indirect_offsets;
|
||||||
|
const auto index = [&](uint8_t slot, IR::StageInputKind kind,
|
||||||
|
IR::StageInputKind base_kind) {
|
||||||
|
if (offsets == ShaderIndirectOffsets {}) {
|
||||||
|
return builtin(kind);
|
||||||
|
}
|
||||||
|
const auto base = builtin(base_kind);
|
||||||
|
if (slot < options.user_data_count) {
|
||||||
|
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(options.user_data_base + slot),
|
||||||
|
base);
|
||||||
|
}
|
||||||
|
return entry_ir.ISub(builtin(kind), base);
|
||||||
|
};
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||||
builtin(IR::StageInputKind::VertexIndex));
|
index(offsets.vertex, IR::StageInputKind::VertexIndex,
|
||||||
|
IR::StageInputKind::BaseVertex));
|
||||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||||
builtin(IR::StageInputKind::InstanceIndex));
|
index(offsets.instance, IR::StageInputKind::InstanceIndex,
|
||||||
|
IR::StageInputKind::BaseInstance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ private:
|
|||||||
Decoder::Operand ScalarDestinationOperand(const Decoder::Operand& operand, uint32_t offset);
|
Decoder::Operand ScalarDestinationOperand(const Decoder::Operand& operand, uint32_t offset);
|
||||||
Decoder::Operand PlainOperand(const Decoder::Operand& operand);
|
Decoder::Operand PlainOperand(const Decoder::Operand& operand);
|
||||||
std::array<IR::U32, 2> BallotMask(IR::U1 value);
|
std::array<IR::U32, 2> BallotMask(IR::U1 value);
|
||||||
|
std::array<IR::U32, 2> ExecMask();
|
||||||
|
std::array<IR::U32, 2> VectorMask(IR::U1 value);
|
||||||
IR::U32 ReadRawU32(const Decoder::Operand& operand);
|
IR::U32 ReadRawU32(const Decoder::Operand& operand);
|
||||||
IR::U32 ReadScalarCode(uint32_t code);
|
IR::U32 ReadScalarCode(uint32_t code);
|
||||||
IR::U32 ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value);
|
IR::U32 ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value);
|
||||||
@@ -52,9 +54,9 @@ private:
|
|||||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||||
IR::U1 ReadMask(const Decoder::Operand& operand);
|
IR::U1 ReadMask(const Decoder::Operand& operand);
|
||||||
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
||||||
std::array<IR::U32, 2> WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
void WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||||
bool write_64 = false);
|
const std::array<IR::U32, 2>& mask, bool write_64 = false);
|
||||||
void WriteCompareResult(const Decoder::Operand& operand, IR::U1 value);
|
void WriteVectorMask(const Decoder::Operand& operand, IR::U1 value);
|
||||||
|
|
||||||
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
||||||
IR::ExportFlags AddExportInfo(const Decoder::Instruction& inst);
|
IR::ExportFlags AddExportInfo(const Decoder::Instruction& inst);
|
||||||
@@ -115,17 +117,15 @@ private:
|
|||||||
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||||
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
||||||
IR::U32 Saturate16(IR::U32 value, bool signed_value);
|
IR::U32 Saturate16(IR::U32 value, bool signed_value);
|
||||||
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar, bool cmpx);
|
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar);
|
||||||
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar, bool cmpx);
|
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar);
|
||||||
void EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
void EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type,
|
||||||
bool scalar, bool cmpx, bool swap_operands = false);
|
bool scalar, bool swap_operands = false);
|
||||||
void EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
void EmitInteger16Compare(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||||
bool signed_value, bool cmpx);
|
bool signed_value);
|
||||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half);
|
||||||
bool cmpx);
|
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool is64 = false);
|
||||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool half = false);
|
||||||
bool is64 = false);
|
|
||||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half = false);
|
|
||||||
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
void V_CVT_F32_UBYTE(const Decoder::Instruction& inst, uint32_t byte_index);
|
||||||
void V_CVT_F32_U32(const Decoder::Instruction& inst);
|
void V_CVT_F32_U32(const Decoder::Instruction& inst);
|
||||||
void V_CVT_F32_I32(const Decoder::Instruction& inst);
|
void V_CVT_F32_I32(const Decoder::Instruction& inst);
|
||||||
|
|||||||
@@ -25,444 +25,310 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
|||||||
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
||||||
|
|
||||||
case O::V_CMP_F_I32:
|
case O::V_CMP_F_I32:
|
||||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return;
|
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false); return;
|
||||||
case O::V_CMP_T_I32:
|
case O::V_CMP_T_I32:
|
||||||
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false, false); return;
|
case O::V_CMP_T_U32: EmitCompareConstant(inst, true, false); return;
|
||||||
case O::V_CMP_EQ_U32:
|
case O::V_CMP_EQ_U32:
|
||||||
case O::V_CMP_EQ_I32:
|
case O::V_CMP_EQ_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_U32:
|
case O::V_CMPX_EQ_U32:
|
||||||
case O::V_CMPX_EQ_I32:
|
case O::V_CMPX_EQ_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NE_U32:
|
case O::V_CMP_NE_U32:
|
||||||
case O::V_CMP_NE_I32:
|
case O::V_CMP_NE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NE_U32:
|
case O::V_CMPX_NE_U32:
|
||||||
case O::V_CMPX_NE_I32:
|
case O::V_CMPX_NE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_U32:
|
case O::V_CMP_GT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_U32:
|
case O::V_CMPX_GT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_U32:
|
case O::V_CMP_GE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_U32:
|
case O::V_CMPX_GE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_U32:
|
case O::V_CMP_LT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_U32:
|
case O::V_CMPX_LT_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_U32:
|
case O::V_CMP_LE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_U32:
|
case O::V_CMPX_LE_U32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_I32:
|
case O::V_CMP_GT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_I32:
|
case O::V_CMPX_GT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_I32:
|
case O::V_CMP_GE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_I32:
|
case O::V_CMPX_GE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_I32:
|
case O::V_CMP_LT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_I32:
|
case O::V_CMPX_LT_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_I32:
|
case O::V_CMP_LE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false,
|
|
||||||
false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_I32:
|
case O::V_CMPX_LE_I32:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_F_I64:
|
case O::V_CMP_F_I64:
|
||||||
case O::V_CMP_F_U64: EmitCompareConstant(inst, false, false, false); return;
|
case O::V_CMP_F_U64:
|
||||||
case O::V_CMP_T_I64:
|
|
||||||
case O::V_CMP_T_U64: EmitCompareConstant(inst, true, false, false); return;
|
|
||||||
case O::V_CMPX_F_I64:
|
case O::V_CMPX_F_I64:
|
||||||
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false, true); return;
|
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false); return;
|
||||||
|
case O::V_CMP_T_I64:
|
||||||
|
case O::V_CMP_T_U64:
|
||||||
case O::V_CMPX_T_I64:
|
case O::V_CMPX_T_I64:
|
||||||
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false, true); return;
|
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false); return;
|
||||||
case O::V_CMP_EQ_I64:
|
case O::V_CMP_EQ_I64:
|
||||||
case O::V_CMP_EQ_U64:
|
case O::V_CMP_EQ_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_I64:
|
case O::V_CMPX_EQ_I64:
|
||||||
case O::V_CMPX_EQ_U64:
|
case O::V_CMPX_EQ_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_U64:
|
case O::V_CMP_LT_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_U64:
|
case O::V_CMPX_LT_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_U64:
|
case O::V_CMP_GT_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_U64:
|
case O::V_CMPX_GT_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NE_I64:
|
case O::V_CMP_NE_I64:
|
||||||
case O::V_CMP_NE_U64:
|
case O::V_CMP_NE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NE_I64:
|
case O::V_CMPX_NE_I64:
|
||||||
case O::V_CMPX_NE_U64:
|
case O::V_CMPX_NE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case O::V_CMP_LT_I64:
|
case O::V_CMP_LT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_I64:
|
case O::V_CMPX_LT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_I64:
|
case O::V_CMP_LE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_I64:
|
case O::V_CMPX_LE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
// The IR has no signed 64-bit greater-than: a > b is b < a, and a >= b is b <= a.
|
// The IR has no signed 64-bit greater-than: a > b is b < a, and a >= b is b <= a.
|
||||||
case O::V_CMP_GT_I64:
|
case O::V_CMP_GT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false,
|
|
||||||
true);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_I64:
|
case O::V_CMPX_GT_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_I64:
|
case O::V_CMP_GE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false,
|
|
||||||
true);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_I64:
|
case O::V_CMPX_GE_I64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true,
|
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
|
||||||
true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_U64:
|
case O::V_CMP_LE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_U64:
|
case O::V_CMPX_LE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_U64:
|
case O::V_CMP_GE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_U64:
|
case O::V_CMPX_GE_U64:
|
||||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, true);
|
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case O::V_CMP_EQ_U16:
|
case O::V_CMP_EQ_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
|
case O::V_CMPX_EQ_U16:
|
||||||
|
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_EQ_I16:
|
case O::V_CMP_EQ_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_I16:
|
case O::V_CMPX_EQ_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NE_U16:
|
case O::V_CMP_NE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NE_U16:
|
case O::V_CMPX_NE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NE_I16:
|
case O::V_CMP_NE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NE_I16:
|
case O::V_CMPX_NE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_U16:
|
case O::V_CMP_GT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_U16:
|
case O::V_CMPX_GT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_U16:
|
case O::V_CMP_GE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
|
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_U16:
|
case O::V_CMP_LT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_U16:
|
case O::V_CMPX_LT_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false);
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_U16:
|
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_U16:
|
case O::V_CMP_LE_U16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
|
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_I16:
|
case O::V_CMP_GT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_I16:
|
case O::V_CMPX_GT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_I16:
|
case O::V_CMP_GE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_I16:
|
case O::V_CMPX_GE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_I16:
|
case O::V_CMP_LT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_I16:
|
case O::V_CMPX_LT_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_I16:
|
case O::V_CMP_LE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_I16:
|
case O::V_CMPX_LE_I16:
|
||||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, true);
|
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return;
|
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false); return;
|
||||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return;
|
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false); return;
|
||||||
case O::V_CMP_EQ_F32:
|
case O::V_CMP_EQ_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, false);
|
case O::V_CMPX_EQ_F32:
|
||||||
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_EQ_F64:
|
case O::V_CMP_EQ_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual64, false, false);
|
case O::V_CMPX_EQ_F64:
|
||||||
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_F64:
|
case O::V_CMP_LE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_F64:
|
case O::V_CMPX_LE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false);
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual64, false, true);
|
|
||||||
return;
|
|
||||||
case O::V_CMP_F_F64: EmitCompareConstant(inst, false, false, false); return;
|
|
||||||
case O::V_CMP_TRU_F64: EmitCompareConstant(inst, true, false, false); return;
|
|
||||||
case O::V_CMPX_F_F64: EmitCompareConstant(inst, false, false, true); return;
|
|
||||||
case O::V_CMPX_TRU_F64: EmitCompareConstant(inst, true, false, true); return;
|
|
||||||
case O::V_CMP_LT_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan64, false, true);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual64, false, true);
|
|
||||||
return;
|
|
||||||
case O::V_CMP_GT_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan64, false, true);
|
|
||||||
return;
|
|
||||||
case O::V_CMP_LG_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LG_F64:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual64, false, true);
|
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_F64:
|
case O::V_CMP_GE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual64, false, false);
|
case O::V_CMPX_GE_F64:
|
||||||
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual64, false);
|
||||||
|
return;
|
||||||
|
case O::V_CMP_F_F64:
|
||||||
|
case O::V_CMPX_F_F64: EmitCompareConstant(inst, false, false); return;
|
||||||
|
case O::V_CMP_TRU_F64:
|
||||||
|
case O::V_CMPX_TRU_F64: EmitCompareConstant(inst, true, false); return;
|
||||||
|
case O::V_CMP_LT_F64:
|
||||||
|
case O::V_CMPX_LT_F64:
|
||||||
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan64, false);
|
||||||
|
return;
|
||||||
|
case O::V_CMP_GT_F64:
|
||||||
|
case O::V_CMPX_GT_F64:
|
||||||
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan64, false);
|
||||||
|
return;
|
||||||
|
case O::V_CMP_LG_F64:
|
||||||
|
case O::V_CMPX_LG_F64:
|
||||||
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLG_F64:
|
case O::V_CMP_NLG_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLG_F64:
|
case O::V_CMPX_NLG_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NEQ_F64:
|
case O::V_CMP_NEQ_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NEQ_F64:
|
case O::V_CMPX_NEQ_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLE_F64:
|
case O::V_CMP_NLE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLE_F64:
|
case O::V_CMPX_NLE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLT_F64:
|
case O::V_CMP_NLT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLT_F64:
|
case O::V_CMPX_NLT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGE_F64:
|
case O::V_CMP_NGE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGE_F64:
|
case O::V_CMPX_NGE_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGT_F64:
|
case O::V_CMP_NGT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGT_F64:
|
case O::V_CMPX_NGT_F64:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false);
|
||||||
return;
|
|
||||||
case O::V_CMP_O_F64: EmitFloatOrderedCompare(inst, true, false, true); return;
|
|
||||||
case O::V_CMP_U_F64: EmitFloatOrderedCompare(inst, false, false, true); return;
|
|
||||||
case O::V_CMPX_O_F64: EmitFloatOrderedCompare(inst, true, true, true); return;
|
|
||||||
case O::V_CMPX_U_F64: EmitFloatOrderedCompare(inst, false, true, true); return;
|
|
||||||
case O::V_CMPX_EQ_F32:
|
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, false, true);
|
|
||||||
return;
|
return;
|
||||||
|
case O::V_CMP_O_F64:
|
||||||
|
case O::V_CMPX_O_F64: EmitFloatOrderedCompare(inst, true, true); return;
|
||||||
|
case O::V_CMP_U_F64:
|
||||||
|
case O::V_CMPX_U_F64: EmitFloatOrderedCompare(inst, false, true); return;
|
||||||
case O::V_CMP_LG_F32:
|
case O::V_CMP_LG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LG_F32:
|
case O::V_CMPX_LG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_F32:
|
case O::V_CMP_GT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_F32:
|
case O::V_CMPX_GT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_F32:
|
case O::V_CMP_GE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_F32:
|
case O::V_CMPX_GE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_F32:
|
case O::V_CMP_LT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_F32:
|
case O::V_CMPX_LT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_F32:
|
case O::V_CMP_LE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_F32:
|
case O::V_CMPX_LE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLG_F32:
|
case O::V_CMP_NLG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLG_F32:
|
case O::V_CMPX_NLG_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NEQ_F32:
|
case O::V_CMP_NEQ_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NEQ_F32:
|
case O::V_CMPX_NEQ_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLE_F32:
|
case O::V_CMP_NLE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLE_F32:
|
case O::V_CMPX_NLE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLT_F32:
|
case O::V_CMP_NLT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLT_F32:
|
case O::V_CMPX_NLT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGE_F32:
|
case O::V_CMP_NGE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGE_F32:
|
case O::V_CMPX_NGE_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGT_F32:
|
case O::V_CMP_NGT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGT_F32:
|
case O::V_CMPX_NGT_F32:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_EQ_F16:
|
case O::V_CMP_EQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_EQ_F16:
|
case O::V_CMPX_EQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LG_F16:
|
case O::V_CMP_LG_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true, false);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GT_F16:
|
case O::V_CMP_GT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GT_F16:
|
case O::V_CMPX_GT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_GE_F16:
|
case O::V_CMP_GE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_GE_F16:
|
case O::V_CMPX_GE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LT_F16:
|
case O::V_CMP_LT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LT_F16:
|
case O::V_CMPX_LT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_LE_F16:
|
case O::V_CMP_LE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_LE_F16:
|
case O::V_CMPX_LE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGE_F16:
|
case O::V_CMP_NGE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true, false);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NGT_F16:
|
case O::V_CMP_NGT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NGT_F16:
|
case O::V_CMPX_NGT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMPX_NLE_F16:
|
case O::V_CMPX_NLE_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NEQ_F16:
|
case O::V_CMP_NEQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NEQ_F16:
|
case O::V_CMPX_NEQ_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_NLT_F16:
|
case O::V_CMP_NLT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
|
||||||
return;
|
|
||||||
case O::V_CMPX_NLT_F16:
|
case O::V_CMPX_NLT_F16:
|
||||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true);
|
||||||
return;
|
return;
|
||||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
|
case O::V_CMP_O_F32:
|
||||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
|
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true); return;
|
||||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
|
case O::V_CMP_U_F32:
|
||||||
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false, true); return;
|
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false); return;
|
||||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
|
case O::V_CMP_CLASS_F32:
|
||||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
|
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst); return;
|
||||||
case O::V_CMP_CLASS_F16: EmitFloatClassCompare(inst, false, true); return;
|
case O::V_CMP_CLASS_F16:
|
||||||
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return;
|
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true); return;
|
||||||
|
|
||||||
case O::V_CVT_F32_UBYTE0: V_CVT_F32_UBYTE(inst, 0); return;
|
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_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return;
|
||||||
|
|||||||
@@ -118,15 +118,14 @@ ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Program::~Program() {
|
Program::~Program() {
|
||||||
// Planning expressions can refer to block values but outlive block storage in the base class.
|
// Planning expressions can refer to block values but outlive block storage in the base class,
|
||||||
|
// and values cross block boundaries: detach everything before any storage is destroyed.
|
||||||
for (auto& inst: value_storage) {
|
for (auto& inst: value_storage) {
|
||||||
inst.Invalidate();
|
inst.DropArgs();
|
||||||
}
|
}
|
||||||
// Values may cross block boundaries. Detach all arguments before any block starts destroying
|
|
||||||
// its instruction storage so reverse-use links always point to live definitions.
|
|
||||||
for (auto* block: blocks) {
|
for (auto* block: blocks) {
|
||||||
for (auto& inst: *block) {
|
for (auto& inst: *block) {
|
||||||
inst.Invalidate();
|
inst.DropArgs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,6 +168,8 @@ bool EquivalentSelectorDomain(const ResourcePlan& program,
|
|||||||
const DescriptorSource::IndirectDescriptor& right) {
|
const DescriptorSource::IndirectDescriptor& right) {
|
||||||
return left.selector == right.selector && left.signed_count == right.signed_count &&
|
return left.selector == right.selector && left.signed_count == right.signed_count &&
|
||||||
left.workgroup_axis == right.workgroup_axis &&
|
left.workgroup_axis == right.workgroup_axis &&
|
||||||
|
left.workgroup_multiplier == right.workgroup_multiplier &&
|
||||||
|
left.workgroup_shift == right.workgroup_shift &&
|
||||||
EquivalentValue(program, left.key_count, right.key_count) &&
|
EquivalentValue(program, left.key_count, right.key_count) &&
|
||||||
EquivalentValue(program, left.selector_first, right.selector_first) &&
|
EquivalentValue(program, left.selector_first, right.selector_first) &&
|
||||||
EquivalentValue(program, left.selector_mask, right.selector_mask);
|
EquivalentValue(program, left.selector_mask, right.selector_mask);
|
||||||
|
|||||||
@@ -155,14 +155,17 @@ KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
|
|||||||
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
||||||
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
||||||
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
||||||
A, B, C, D, E, F, G, H, I, J)
|
A, B, C, D, E, F, G, H, I, J, K)
|
||||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
|
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
|
||||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j)
|
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c)
|
||||||
|
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k,
|
||||||
|
l)
|
||||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
|
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
|
||||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
|
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p,
|
||||||
|
q, r)
|
||||||
KYTY_IO_FIELDS(DescriptorSource, a, b, c)
|
KYTY_IO_FIELDS(DescriptorSource, a, b, c)
|
||||||
KYTY_IO_FIELDS(SrtRead, a, b)
|
KYTY_IO_FIELDS(SrtRead, a, b)
|
||||||
KYTY_IO_FIELDS(ResourceBlock, a, b, c, d)
|
KYTY_IO_FIELDS(ResourceBlock, a, b, c, d, e)
|
||||||
KYTY_IO_FIELDS(UniformFill, a, b, c, d, e, f)
|
KYTY_IO_FIELDS(UniformFill, a, b, c, d, e, f)
|
||||||
KYTY_IO_FIELDS(UniformFillPlan, a, b, c, d)
|
KYTY_IO_FIELDS(UniformFillPlan, a, b, c, d)
|
||||||
KYTY_IO_FIELDS(ResourceSpecialization::Buffer, a, b, c, d, e, f, g, h)
|
KYTY_IO_FIELDS(ResourceSpecialization::Buffer, a, b, c, d, e, f, g, h)
|
||||||
|
|||||||
@@ -251,6 +251,8 @@ enum class StageInputKind {
|
|||||||
LocalInvocationId,
|
LocalInvocationId,
|
||||||
LocalInvocationIndex,
|
LocalInvocationIndex,
|
||||||
GlobalInvocationId,
|
GlobalInvocationId,
|
||||||
|
BaseVertex,
|
||||||
|
BaseInstance,
|
||||||
Parameter,
|
Parameter,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -365,7 +367,7 @@ static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u);
|
|||||||
|
|
||||||
struct PushData {
|
struct PushData {
|
||||||
static constexpr uint32_t DwordCount = 32;
|
static constexpr uint32_t DwordCount = 32;
|
||||||
static constexpr uint32_t MeshDrawDwordCount = 6;
|
static constexpr uint32_t MeshDrawDwordCount = 5;
|
||||||
static constexpr uint32_t NoStart = UINT32_MAX;
|
static constexpr uint32_t NoStart = UINT32_MAX;
|
||||||
std::array<uint32_t, DwordCount> dwords {};
|
std::array<uint32_t, DwordCount> dwords {};
|
||||||
|
|
||||||
@@ -495,10 +497,16 @@ struct Bindings {
|
|||||||
uint32_t dispatch_thread_dword = PushData::NoStart;
|
uint32_t dispatch_thread_dword = PushData::NoStart;
|
||||||
uint32_t memory_offset_dword = 0;
|
uint32_t memory_offset_dword = 0;
|
||||||
|
|
||||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
// Packed 8-bit binding offsets, then each binding's bound range in bytes: OpArrayLength
|
||||||
|
// reads 0 for ranges of 2 GiB or more on NVIDIA.
|
||||||
|
[[nodiscard]] uint32_t MemorySizeDword() const {
|
||||||
return memory_offset_dword +
|
return memory_offset_dword +
|
||||||
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
|
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||||
|
return MemorySizeDword() +
|
||||||
|
descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)];
|
||||||
|
}
|
||||||
[[nodiscard]] bool UsesPushData() const {
|
[[nodiscard]] bool UsesPushData() const {
|
||||||
return push_data_start_dword != PushData::NoStart;
|
return push_data_start_dword != PushData::NoStart;
|
||||||
}
|
}
|
||||||
@@ -541,6 +549,7 @@ struct ShaderInfo {
|
|||||||
bool subgroup_shuffle = false;
|
bool subgroup_shuffle = false;
|
||||||
bool subgroup_arithmetic = false;
|
bool subgroup_arithmetic = false;
|
||||||
bool subgroup_local_invocation_id = false;
|
bool subgroup_local_invocation_id = false;
|
||||||
|
bool helper_invocation = false;
|
||||||
bool compute_derivatives = false;
|
bool compute_derivatives = false;
|
||||||
bool image_gather_extended = false;
|
bool image_gather_extended = false;
|
||||||
bool function_lds = false;
|
bool function_lds = false;
|
||||||
@@ -565,6 +574,13 @@ struct DescriptorSource {
|
|||||||
|
|
||||||
bool operator==(const CountedRows& other) const = default;
|
bool operator==(const CountedRows& other) const = default;
|
||||||
};
|
};
|
||||||
|
struct MaskRanks {
|
||||||
|
uint32_t source = UINT32_MAX;
|
||||||
|
uint32_t stride = 0; // Byte spacing of the mask words its load can address.
|
||||||
|
uint32_t offset = 0;
|
||||||
|
|
||||||
|
bool operator==(const MaskRanks& other) const = default;
|
||||||
|
};
|
||||||
uint32_t source = UINT32_MAX;
|
uint32_t source = UINT32_MAX;
|
||||||
uint32_t stride = 0;
|
uint32_t stride = 0;
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
@@ -572,10 +588,12 @@ struct DescriptorSource {
|
|||||||
uint32_t bit_offset = 0;
|
uint32_t bit_offset = 0;
|
||||||
uint32_t bit_mask = UINT32_MAX;
|
uint32_t bit_mask = UINT32_MAX;
|
||||||
bool global = false;
|
bool global = false;
|
||||||
// Four DWORD bitmap words per record; set-bit ranks subtract from the last key.
|
// Four or eight DWORD bitmap words per record; set ranks subtract from the last key.
|
||||||
bool bitset = false;
|
bool bitset = false;
|
||||||
std::optional<CountedRows> rows;
|
std::optional<CountedRows> rows;
|
||||||
std::vector<std::pair<uint32_t, uint32_t>> byte_ranges;
|
std::vector<std::pair<uint32_t, uint32_t>> byte_ranges;
|
||||||
|
std::optional<uint32_t> excluded; // A guard keeps this key from the read.
|
||||||
|
std::optional<MaskRanks> ranks; // Records are the bits some mask word sets.
|
||||||
|
|
||||||
bool operator==(const SelectorRead& other) const = default;
|
bool operator==(const SelectorRead& other) const = default;
|
||||||
};
|
};
|
||||||
@@ -599,8 +617,12 @@ struct DescriptorSource {
|
|||||||
bool indexed = false;
|
bool indexed = false;
|
||||||
bool signed_count = false;
|
bool signed_count = false;
|
||||||
uint32_t workgroup_axis = UINT32_MAX;
|
uint32_t workgroup_axis = UINT32_MAX;
|
||||||
|
// Nonzero shift: workgroup keys are (selector_first + ID) * multiplier >> shift.
|
||||||
|
uint32_t workgroup_multiplier = 0;
|
||||||
|
uint32_t workgroup_shift = 0;
|
||||||
Value key_count;
|
Value key_count;
|
||||||
// First selector index, or first descriptor key for a direct or bitmap range.
|
// First selector index, workgroup key base, or first descriptor key for a direct or bitmap
|
||||||
|
// range.
|
||||||
Value selector_first;
|
Value selector_first;
|
||||||
Value selector_mask;
|
Value selector_mask;
|
||||||
std::vector<uint32_t> sources;
|
std::vector<uint32_t> sources;
|
||||||
@@ -627,6 +649,7 @@ struct ResourceBlock {
|
|||||||
Value condition;
|
Value condition;
|
||||||
std::vector<uint32_t> successors;
|
std::vector<uint32_t> successors;
|
||||||
std::vector<uint32_t> sources;
|
std::vector<uint32_t> sources;
|
||||||
|
std::vector<uint32_t> images;
|
||||||
std::vector<uint32_t> srt_reads;
|
std::vector<uint32_t> srt_reads;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,22 +25,6 @@ Value Value::F32(float value) {
|
|||||||
return Value(Type::F32, std::bit_cast<uint32_t>(value));
|
return Value(Type::F32, std::bit_cast<uint32_t>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Value::IsEmpty() const {
|
|
||||||
return type == Type::Void;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::IsImmediate() const {
|
|
||||||
return type != Type::Opaque;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::IsIdentity() const {
|
|
||||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::IsPhi() const {
|
|
||||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type Value::GetType() const {
|
Type Value::GetType() const {
|
||||||
if (IsPhi()) {
|
if (IsPhi()) {
|
||||||
return inst->Flags<Type>();
|
return inst->Flags<Type>();
|
||||||
@@ -56,19 +40,6 @@ Inst* Value::Instruction() const {
|
|||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
|
|
||||||
Inst* Value::TryInstruction() const {
|
|
||||||
return type == Type::Opaque ? inst : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Inst* Value::ResolveInstruction() const {
|
|
||||||
EXIT_IF(type != Type::Opaque);
|
|
||||||
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Value::Resolve() const {
|
|
||||||
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScalarReg Value::ScalarRegister() const {
|
ScalarReg Value::ScalarRegister() const {
|
||||||
EXIT_IF(type != Type::ScalarReg);
|
EXIT_IF(type != Type::ScalarReg);
|
||||||
return scalar_reg;
|
return scalar_reg;
|
||||||
@@ -94,11 +65,6 @@ uint16_t Value::U16() const {
|
|||||||
return imm_u16;
|
return imm_u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Value::U32() const {
|
|
||||||
EXIT_IF(type != Type::U32);
|
|
||||||
return imm_u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Value::U64() const {
|
uint64_t Value::U64() const {
|
||||||
EXIT_IF(type != Type::U64);
|
EXIT_IF(type != Type::U64);
|
||||||
return imm_u64;
|
return imm_u64;
|
||||||
@@ -152,10 +118,6 @@ Inst::~Inst() {
|
|||||||
ClearArgs();
|
ClearArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueOpcode Inst::GetOpcode() const {
|
|
||||||
return opcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type Inst::GetType() const {
|
Type Inst::GetType() const {
|
||||||
if (opcode == ValueOpcode::Phi) {
|
if (opcode == ValueOpcode::Phi) {
|
||||||
return static_cast<Type>(flags);
|
return static_cast<Type>(flags);
|
||||||
@@ -178,51 +140,25 @@ size_t Inst::UseCount() const {
|
|||||||
return uses.size();
|
return uses.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Inst::NumArgs() const {
|
|
||||||
return num_args == PhiArity ? phi_args.size() : num_args;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Inst::NumPhiBlocks() const {
|
size_t Inst::NumPhiBlocks() const {
|
||||||
return num_args == PhiArity ? phi_args.size() : 0;
|
return num_args == PhiArity ? phi_args.size() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value Inst::Arg(size_t index) const {
|
|
||||||
EXIT_IF(index >= NumArgs());
|
|
||||||
if (num_args <= InlineArity) {
|
|
||||||
return fixed_args[index];
|
|
||||||
}
|
|
||||||
return num_args == PhiArity ? phi_args[index].second : large_args[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
Block* Inst::PhiBlock(size_t index) const {
|
Block* Inst::PhiBlock(size_t index) const {
|
||||||
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_args.size());
|
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_args.size());
|
||||||
return phi_args[index].first;
|
return phi_args[index].first;
|
||||||
}
|
}
|
||||||
|
|
||||||
Block* Inst::Parent() const {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<Use>& Inst::Uses() const {
|
|
||||||
return uses;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Inst::SetParent(Block* block) {
|
void Inst::SetParent(Block* block) {
|
||||||
parent = block;
|
parent = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inst::SetArg(size_t index, Value value) {
|
void Inst::SetArg(size_t index, Value value) {
|
||||||
const auto old = Arg(index);
|
auto& arg = ArgSlot(*this, index);
|
||||||
if (auto* old_inst = old.TryInstruction(); old_inst != nullptr) {
|
if (auto* old_inst = arg.TryInstruction(); old_inst != nullptr) {
|
||||||
RemoveUse(old_inst, index);
|
RemoveUse(old_inst, index);
|
||||||
}
|
}
|
||||||
if (num_args <= InlineArity) {
|
arg = value;
|
||||||
fixed_args[index] = value;
|
|
||||||
} else if (num_args == PhiArity) {
|
|
||||||
phi_args[index].second = value;
|
|
||||||
} else {
|
|
||||||
large_args[index] = value;
|
|
||||||
}
|
|
||||||
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
|
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
|
||||||
AddUse(new_inst, index);
|
AddUse(new_inst, index);
|
||||||
}
|
}
|
||||||
@@ -238,11 +174,15 @@ void Inst::AddPhiOperand(Block* predecessor, Value value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
|
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
|
||||||
const auto old_uses = uses;
|
// Detach the arguments first (a phi can use itself), then move every use over in order.
|
||||||
for (const auto& use: old_uses) {
|
|
||||||
use.user->SetArg(use.operand, replacement);
|
|
||||||
}
|
|
||||||
Invalidate();
|
Invalidate();
|
||||||
|
const auto old_uses = std::exchange(uses, {});
|
||||||
|
for (const auto& use: old_uses) {
|
||||||
|
ArgSlot(*use.user, use.operand) = replacement;
|
||||||
|
}
|
||||||
|
if (auto* target = replacement.TryInstruction(); target != nullptr) {
|
||||||
|
target->uses.insert(target->uses.end(), old_uses.begin(), old_uses.end());
|
||||||
|
}
|
||||||
if (preserve) {
|
if (preserve) {
|
||||||
opcode = ValueOpcode::Identity;
|
opcode = ValueOpcode::Identity;
|
||||||
num_args = 1;
|
num_args = 1;
|
||||||
@@ -256,9 +196,6 @@ void Inst::Invalidate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Inst::AddUse(Inst* used, size_t operand) {
|
void Inst::AddUse(Inst* used, size_t operand) {
|
||||||
const auto found = std::ranges::find_if(
|
|
||||||
used->uses, [&](const Use& use) { return use.user == this && use.operand == operand; });
|
|
||||||
EXIT_IF(found != used->uses.end());
|
|
||||||
used->uses.push_back({this, operand});
|
used->uses.push_back({this, operand});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,6 +212,10 @@ void Inst::ClearArgs() {
|
|||||||
RemoveUse(value_inst, index);
|
RemoveUse(value_inst, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DropArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inst::DropArgs() {
|
||||||
if (num_args == PhiArity) {
|
if (num_args == PhiArity) {
|
||||||
std::destroy_at(&phi_args);
|
std::destroy_at(&phi_args);
|
||||||
std::construct_at(&fixed_args);
|
std::construct_at(&fixed_args);
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ public:
|
|||||||
static Value F16(uint16_t bits);
|
static Value F16(uint16_t bits);
|
||||||
static Value F32(float value);
|
static Value F32(float value);
|
||||||
|
|
||||||
[[nodiscard]] bool IsEmpty() const;
|
[[nodiscard]] bool IsEmpty() const { return type == Type::Void; }
|
||||||
[[nodiscard]] bool IsImmediate() const;
|
[[nodiscard]] bool IsImmediate() const { return type != Type::Opaque; }
|
||||||
[[nodiscard]] bool IsIdentity() const;
|
[[nodiscard]] bool IsIdentity() const;
|
||||||
[[nodiscard]] bool IsPhi() const;
|
[[nodiscard]] bool IsPhi() const;
|
||||||
[[nodiscard]] Type GetType() const;
|
[[nodiscard]] Type GetType() const;
|
||||||
|
|
||||||
[[nodiscard]] Inst* Instruction() const;
|
[[nodiscard]] Inst* Instruction() const;
|
||||||
[[nodiscard]] Inst* TryInstruction() const;
|
[[nodiscard]] Inst* TryInstruction() const { return type == Type::Opaque ? inst : nullptr; }
|
||||||
[[nodiscard]] Inst* ResolveInstruction() const;
|
[[nodiscard]] Inst* ResolveInstruction() const;
|
||||||
[[nodiscard]] Value Resolve() const;
|
[[nodiscard]] Value Resolve() const;
|
||||||
[[nodiscard]] ScalarReg ScalarRegister() const;
|
[[nodiscard]] ScalarReg ScalarRegister() const;
|
||||||
@@ -112,7 +112,7 @@ public:
|
|||||||
Inst(Inst&&) = delete;
|
Inst(Inst&&) = delete;
|
||||||
Inst& operator=(Inst&&) = delete;
|
Inst& operator=(Inst&&) = delete;
|
||||||
|
|
||||||
[[nodiscard]] ValueOpcode GetOpcode() const;
|
[[nodiscard]] ValueOpcode GetOpcode() const { return opcode; }
|
||||||
[[nodiscard]] Type GetType() const;
|
[[nodiscard]] Type GetType() const;
|
||||||
[[nodiscard]] bool MayHaveSideEffects() const;
|
[[nodiscard]] bool MayHaveSideEffects() const;
|
||||||
[[nodiscard]] bool HasUses() const;
|
[[nodiscard]] bool HasUses() const;
|
||||||
@@ -121,8 +121,8 @@ public:
|
|||||||
[[nodiscard]] size_t NumPhiBlocks() const;
|
[[nodiscard]] size_t NumPhiBlocks() const;
|
||||||
[[nodiscard]] Value Arg(size_t index) const;
|
[[nodiscard]] Value Arg(size_t index) const;
|
||||||
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||||
[[nodiscard]] Block* Parent() const;
|
[[nodiscard]] Block* Parent() const { return parent; }
|
||||||
[[nodiscard]] const std::vector<Use>& Uses() const;
|
[[nodiscard]] const std::vector<Use>& Uses() const { return uses; }
|
||||||
// SRT walker output index, assigned by the resource plan that owns this instruction.
|
// SRT walker output index, assigned by the resource plan that owns this instruction.
|
||||||
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
|
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
|
||||||
void SetWalkOutput(uint32_t index) const { walk_output = index; }
|
void SetWalkOutput(uint32_t index) const { walk_output = index; }
|
||||||
@@ -138,6 +138,8 @@ public:
|
|||||||
void AddPhiOperand(Block* predecessor, Value value);
|
void AddPhiOperand(Block* predecessor, Value value);
|
||||||
void ReplaceUsesWith(Value replacement, bool preserve = true);
|
void ReplaceUsesWith(Value replacement, bool preserve = true);
|
||||||
void Invalidate();
|
void Invalidate();
|
||||||
|
// Drops the arguments without updating their use lists, for teardown that destroys both sides.
|
||||||
|
void DropArgs();
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
|
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
|
||||||
@@ -157,6 +159,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
friend void EliminateDeadCode(const std::vector<Block*>& blocks);
|
friend void EliminateDeadCode(const std::vector<Block*>& blocks);
|
||||||
|
|
||||||
|
template <typename Self>
|
||||||
|
static auto& ArgSlot(Self& self, size_t index);
|
||||||
void AddUse(Inst* used, size_t operand);
|
void AddUse(Inst* used, size_t operand);
|
||||||
void RemoveUse(Inst* used, size_t operand);
|
void RemoveUse(Inst* used, size_t operand);
|
||||||
void ClearArgs();
|
void ClearArgs();
|
||||||
@@ -181,4 +185,44 @@ private:
|
|||||||
|
|
||||||
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
||||||
|
|
||||||
|
// Called per operand by the IR passes and the resource walker; inline, as the build has no LTO.
|
||||||
|
inline bool Value::IsIdentity() const {
|
||||||
|
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Value::IsPhi() const {
|
||||||
|
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Inst* Value::ResolveInstruction() const {
|
||||||
|
EXIT_IF(type != Type::Opaque);
|
||||||
|
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Value Value::Resolve() const {
|
||||||
|
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t Value::U32() const {
|
||||||
|
EXIT_IF(type != Type::U32);
|
||||||
|
return imm_u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t Inst::NumArgs() const {
|
||||||
|
return num_args == PhiArity ? phi_args.size() : num_args;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Self>
|
||||||
|
auto& Inst::ArgSlot(Self& self, size_t index) {
|
||||||
|
EXIT_IF(index >= self.NumArgs());
|
||||||
|
if (self.num_args <= InlineArity) {
|
||||||
|
return self.fixed_args[index];
|
||||||
|
}
|
||||||
|
return self.num_args == PhiArity ? self.phi_args[index].second : self.large_args[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Value Inst::Arg(size_t index) const {
|
||||||
|
return ArgSlot(*this, index);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||||
@@ -123,6 +123,7 @@ BufferAccess BufferAccessOf(ValueOpcode opcode) {
|
|||||||
case ValueOpcode::BufferAtomicISub32:
|
case ValueOpcode::BufferAtomicISub32:
|
||||||
case ValueOpcode::BufferAtomicSMin32:
|
case ValueOpcode::BufferAtomicSMin32:
|
||||||
case ValueOpcode::BufferAtomicUMin32:
|
case ValueOpcode::BufferAtomicUMin32:
|
||||||
|
case ValueOpcode::BufferAtomicUMin64:
|
||||||
case ValueOpcode::BufferAtomicSMax32:
|
case ValueOpcode::BufferAtomicSMax32:
|
||||||
case ValueOpcode::BufferAtomicUMax32:
|
case ValueOpcode::BufferAtomicUMax32:
|
||||||
case ValueOpcode::BufferAtomicAnd32:
|
case ValueOpcode::BufferAtomicAnd32:
|
||||||
@@ -140,6 +141,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
|
|||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case ValueOpcode::BufferAtomicSwap64:
|
case ValueOpcode::BufferAtomicSwap64:
|
||||||
case ValueOpcode::BufferAtomicIAdd64:
|
case ValueOpcode::BufferAtomicIAdd64:
|
||||||
|
case ValueOpcode::BufferAtomicUMin64:
|
||||||
case ValueOpcode::BufferAtomicAnd64:
|
case ValueOpcode::BufferAtomicAnd64:
|
||||||
case ValueOpcode::BufferAtomicOr64:
|
case ValueOpcode::BufferAtomicOr64:
|
||||||
case ValueOpcode::LoadBufferU32x2:
|
case ValueOpcode::LoadBufferU32x2:
|
||||||
|
|||||||
@@ -219,9 +219,11 @@ VALUE_OPCODE(FPFract64, F64, F64)
|
|||||||
|
|
||||||
VALUE_OPCODE(LaneId, U32)
|
VALUE_OPCODE(LaneId, U32)
|
||||||
VALUE_OPCODE(Ballot, U32x4, U1)
|
VALUE_OPCODE(Ballot, U32x4, U1)
|
||||||
|
VALUE_OPCODE(LiveMask, U32x4)
|
||||||
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
||||||
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
||||||
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
||||||
|
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
|
||||||
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
||||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
||||||
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
|
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
|
||||||
@@ -267,6 +269,7 @@ VALUE_OPCODE(BufferAtomicIAdd64, U64, BufferResource, U32, U32, U32, U64, U1)
|
|||||||
VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1)
|
VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(BufferAtomicUMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
VALUE_OPCODE(BufferAtomicUMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||||
|
VALUE_OPCODE(BufferAtomicUMin64, U64, BufferResource, U32, U32, U32, U64, U1)
|
||||||
VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1)
|
VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||||
@@ -309,8 +312,8 @@ VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
|
|||||||
VALUE_OPCODE(SharedAtomicOr64, Void, U32, U64, U1)
|
VALUE_OPCODE(SharedAtomicOr64, Void, U32, U64, U1)
|
||||||
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(SharedAtomicMaskedOr32, Void, U32, U32, U32, U1)
|
VALUE_OPCODE(SharedAtomicMaskedOr32, Void, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(DataAppend, U32, U32, U1)
|
VALUE_OPCODE(DataAppend, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(DataConsume, U32, U32, U1)
|
VALUE_OPCODE(DataConsume, U32, U32, U32, U32, U1)
|
||||||
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
||||||
|
|
||||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||||
|
|||||||
@@ -167,6 +167,16 @@ bool ReplaceBinaryIdentity(Inst& inst, Type type, uint64_t identity) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether one operand is the other's negation.
|
||||||
|
bool IsComplementPair(const Inst& inst, ValueOpcode negation) {
|
||||||
|
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||||
|
const auto* negate = Arg(inst, arg).TryInstruction();
|
||||||
|
if (negate != nullptr && negate->GetOpcode() == negation &&
|
||||||
|
Arg(*negate, 0) == Arg(inst, arg ^ 1u)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool FoldSelect(Inst& inst) {
|
bool FoldSelect(Inst& inst) {
|
||||||
const auto condition = Arg(inst, 0);
|
const auto condition = Arg(inst, 0);
|
||||||
const auto true_value = Arg(inst, 1);
|
const auto true_value = Arg(inst, 1);
|
||||||
@@ -626,8 +636,9 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
|||||||
case ValueOpcode::BitwiseXor32:
|
case ValueOpcode::BitwiseXor32:
|
||||||
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
|
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
|
||||||
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ b;
|
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ b;
|
||||||
})) {
|
}) && !ReplaceBinaryIdentity(inst, Type::U32, 0u) &&
|
||||||
ReplaceBinaryIdentity(inst, Type::U32, 0u);
|
IsComplementPair(inst, ValueOpcode::BitwiseNot32)) {
|
||||||
|
Replace(inst, Value(UINT32_MAX));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case ValueOpcode::BitwiseNot32: {
|
case ValueOpcode::BitwiseNot32: {
|
||||||
@@ -802,6 +813,8 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
|||||||
Replace(inst, lhs.U1() ? lhs : rhs);
|
Replace(inst, lhs.U1() ? lhs : rhs);
|
||||||
} else if (IsImmediate(rhs, Type::U1)) {
|
} else if (IsImmediate(rhs, Type::U1)) {
|
||||||
Replace(inst, rhs.U1() ? rhs : lhs);
|
Replace(inst, rhs.U1() ? rhs : lhs);
|
||||||
|
} else if (IsComplementPair(inst, ValueOpcode::LogicalNot)) {
|
||||||
|
Replace(inst, Value(true));
|
||||||
} else {
|
} else {
|
||||||
const auto* left = lhs.TryInstruction();
|
const auto* left = lhs.TryInstruction();
|
||||||
const auto* right = rhs.TryInstruction();
|
const auto* right = rhs.TryInstruction();
|
||||||
@@ -892,8 +905,10 @@ private:
|
|||||||
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
|
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
|
||||||
!Immediate(Arg(inst, 1), 0u)) return {};
|
!Immediate(Arg(inst, 1), 0u)) return {};
|
||||||
const auto* source = Arg(inst, 0).TryInstruction();
|
const auto* source = Arg(inst, 0).TryInstruction();
|
||||||
return source != nullptr && source->GetOpcode() == ValueOpcode::Ballot
|
if (source == nullptr) return {};
|
||||||
? Arg(*source, 0) : Value {};
|
// Helper lanes keep the per-lane EXEC, so the live mask projects to true.
|
||||||
|
if (source->GetOpcode() == ValueOpcode::LiveMask) return Value(true);
|
||||||
|
return source->GetOpcode() == ValueOpcode::Ballot ? Arg(*source, 0) : Value {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CanProject(Value value) {
|
bool CanProject(Value value) {
|
||||||
|
|||||||
@@ -99,89 +99,66 @@ Value GetRealValue(PhiMap& phi_map, Value source, uint32_t lane, uint32_t wave_s
|
|||||||
return Value(&phi);
|
return Value(&phi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction.
|
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction. Wave64 first joins
|
||||||
|
// the two rows of each half; a wave32 half is one row.
|
||||||
// Fragment subgroups may have no physical invocation at either endpoint.
|
// Fragment subgroups may have no physical invocation at either endpoint.
|
||||||
class UMinReductionMatcher {
|
class WaveReductionMatcher {
|
||||||
public:
|
public:
|
||||||
explicit UMinReductionMatcher(Block* block): block(block) {}
|
WaveReductionMatcher(const Block* block, ValueOpcode operation)
|
||||||
|
: block(block), operation(operation) {}
|
||||||
|
|
||||||
Value Match(const Inst& minimum) {
|
Value Match(const Inst& reduction, uint32_t wave_size) const {
|
||||||
const auto* low = Get(minimum.Arg(0), ValueOpcode::ReadLane);
|
const auto* low = Get(reduction.Arg(0), ValueOpcode::ReadLane);
|
||||||
const auto* high = Get(minimum.Arg(1), ValueOpcode::ReadLane);
|
const auto* high = Get(reduction.Arg(1), ValueOpcode::ReadLane);
|
||||||
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto lane = [](Value value, uint32_t expected) {
|
const auto lane = [&](Value value, uint32_t expected) {
|
||||||
value = value.Resolve();
|
value = value.Resolve();
|
||||||
return value.IsImmediate() && value.GetType() == Type::U32 &&
|
return value.IsImmediate() && value.GetType() == Type::U32 &&
|
||||||
value.U32() % 64u == expected;
|
value.U32() % wave_size == expected;
|
||||||
};
|
};
|
||||||
if (!((lane(low->Arg(1), 31) && lane(high->Arg(1), 63)) ||
|
const auto half = wave_size / 2u;
|
||||||
(lane(low->Arg(1), 63) && lane(high->Arg(1), 31)))) {
|
if (!((lane(low->Arg(1), half - 1u) && lane(high->Arg(1), wave_size - 1u)) ||
|
||||||
|
(lane(low->Arg(1), wave_size - 1u) && lane(high->Arg(1), half - 1u)))) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto* joined = Get(Unwrap(low->Arg(0)), ValueOpcode::UMin32);
|
if (wave_size == 32u) return MatchScan(low->Arg(0));
|
||||||
|
const auto* joined = Get(low->Arg(0), operation);
|
||||||
if (joined == nullptr) return {};
|
if (joined == nullptr) return {};
|
||||||
for (size_t index = 0; index < 2; index++) {
|
for (size_t index = 0; index < 2; index++) {
|
||||||
auto attempt = *this;
|
const auto* cross = Get(joined->Arg(index), ValueOpcode::Permlane16U32);
|
||||||
const auto* cross = attempt.Get(attempt.Unwrap(joined->Arg(index)), ValueOpcode::Permlane16U32);
|
|
||||||
if (cross == nullptr) continue;
|
if (cross == nullptr) continue;
|
||||||
const auto flags = cross->Flags<PermlaneFlags>();
|
const auto flags = cross->Flags<PermlaneFlags>();
|
||||||
auto prefix = attempt.Unwrap(joined->Arg(1 - index));
|
const auto prefix = joined->Arg(1 - index).Resolve();
|
||||||
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
||||||
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
||||||
cross->Arg(2).Resolve() != Value(UINT32_MAX) ||
|
cross->Arg(2).Resolve() != Value(UINT32_MAX) || !FullExec(cross->Arg(3)) ||
|
||||||
!attempt.FullExec(cross->Arg(3)) || attempt.Unwrap(cross->Arg(0)) != prefix) {
|
cross->Arg(0).Resolve() != prefix) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
if (const auto seed = MatchScan(prefix); !seed.IsEmpty()) return seed;
|
||||||
prefix = attempt.MatchRow(prefix, shift);
|
|
||||||
}
|
|
||||||
prefix = attempt.Unwrap(prefix);
|
|
||||||
if (prefix.IsEmpty() || attempt.saved_exec.IsEmpty()) return prefix;
|
|
||||||
const auto* seed = attempt.Get(prefix, ValueOpcode::SelectU32);
|
|
||||||
if (seed != nullptr && seed->Arg(2).Resolve() == Value(UINT32_MAX)) {
|
|
||||||
const auto predicate = seed->Arg(0).Resolve();
|
|
||||||
const auto* subset = attempt.Get(predicate, ValueOpcode::LogicalAnd);
|
|
||||||
if (predicate == attempt.saved_exec ||
|
|
||||||
(subset != nullptr && (subset->Arg(0).Resolve() == attempt.saved_exec ||
|
|
||||||
subset->Arg(1).Resolve() == attempt.saved_exec))) {
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// The row scan's input.
|
||||||
|
Value MatchScan(Value prefix) const {
|
||||||
|
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
||||||
|
prefix = MatchRow(prefix, shift);
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
Inst* Get(Value value, ValueOpcode opcode) const {
|
Inst* Get(Value value, ValueOpcode opcode) const {
|
||||||
auto* inst = value.Resolve().TryInstruction();
|
auto* inst = value.Resolve().TryInstruction();
|
||||||
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
||||||
? inst : nullptr;
|
? inst : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FullExec(Value predicate) {
|
// Constant propagation folds the x | ~x EXEC of s_orn2_saveexec to true.
|
||||||
predicate = predicate.Resolve();
|
static bool FullExec(Value predicate) { return predicate.Resolve() == Value(true); }
|
||||||
if (predicate == Value(true)) return true;
|
|
||||||
const auto* either = Get(predicate, ValueOpcode::LogicalOr);
|
|
||||||
if (either == nullptr) return false;
|
|
||||||
for (size_t index = 0; index < 2; index++) {
|
|
||||||
const auto* negate = Get(either->Arg(index), ValueOpcode::LogicalNot);
|
|
||||||
if (negate == nullptr || negate->Arg(0).Resolve() != either->Arg(1 - index).Resolve()) continue;
|
|
||||||
const auto original = negate->Arg(0).Resolve();
|
|
||||||
if (saved_exec.IsEmpty()) saved_exec = original;
|
|
||||||
return saved_exec == original;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Unwrap(Value value) {
|
|
||||||
while (const auto* select = Get(value, ValueOpcode::SelectU32)) {
|
|
||||||
if (!FullExec(select->Arg(0))) break;
|
|
||||||
value = select->Arg(1);
|
|
||||||
}
|
|
||||||
return value.Resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
||||||
const auto flags = inst.Flags<DppMoveFlags>();
|
const auto flags = inst.Flags<DppMoveFlags>();
|
||||||
@@ -189,22 +166,24 @@ private:
|
|||||||
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value MatchRow(Value value, uint32_t shift) {
|
Value MatchRow(Value value, uint32_t shift) const {
|
||||||
const auto* update = Get(Unwrap(value), ValueOpcode::DppUpdateU32);
|
const auto* update = Get(value, ValueOpcode::DppUpdateU32);
|
||||||
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
||||||
const auto* minimum = Get(update->Arg(0), ValueOpcode::UMin32);
|
const auto* combined = Get(update->Arg(0), operation);
|
||||||
if (minimum == nullptr) return {};
|
if (combined == nullptr) return {};
|
||||||
for (size_t index = 0; index < 2; index++) {
|
for (size_t index = 0; index < 2; index++) {
|
||||||
const auto* move = Get(minimum->Arg(index), ValueOpcode::DppMoveU32);
|
const auto* move = Get(combined->Arg(index), ValueOpcode::DppMoveU32);
|
||||||
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
|
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
|
||||||
const auto previous = Unwrap(minimum->Arg(1 - index));
|
const auto previous = combined->Arg(1 - index).Resolve();
|
||||||
if (Unwrap(move->Arg(0)) == previous && Unwrap(update->Arg(1)) == previous) return previous;
|
if (move->Arg(0).Resolve() == previous && update->Arg(1).Resolve() == previous) {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Block* block;
|
const Block* block;
|
||||||
Value saved_exec;
|
ValueOpcode operation;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -217,18 +196,22 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
|||||||
|
|
||||||
for (auto* block: program.blocks) {
|
for (auto* block: program.blocks) {
|
||||||
for (auto& inst: *block) {
|
for (auto& inst: *block) {
|
||||||
if (wave_size == 64u && inst.GetOpcode() == ValueOpcode::UMin32) {
|
const auto opcode = inst.GetOpcode();
|
||||||
const auto seed = UMinReductionMatcher(block).Match(inst);
|
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
|
||||||
|
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
|
||||||
if (!seed.IsEmpty()) {
|
if (!seed.IsEmpty()) {
|
||||||
const auto position = std::find_if(block->begin(), block->end(),
|
const auto position = std::find_if(block->begin(), block->end(),
|
||||||
[&](const Inst& candidate) { return &candidate == &inst; });
|
[&](const Inst& candidate) { return &candidate == &inst; });
|
||||||
auto& reduction = *block->PrependNewInst(position, ValueOpcode::SubgroupUMinU32, {seed});
|
const auto reduce = opcode == ValueOpcode::UMin32
|
||||||
|
? ValueOpcode::SubgroupUMinU32
|
||||||
|
: ValueOpcode::SubgroupUMaxU32;
|
||||||
|
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
|
||||||
inst.ReplaceUsesWith(Value(&reduction));
|
inst.ReplaceUsesWith(Value(&reduction));
|
||||||
stats.rewritten_reductions++;
|
stats.rewritten_reductions++;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (inst.GetOpcode() != ValueOpcode::ReadLane) {
|
if (opcode != ValueOpcode::ReadLane) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto selector = inst.Arg(1).Resolve();
|
const auto selector = inst.Arg(1).Resolve();
|
||||||
|
|||||||
@@ -331,6 +331,18 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
|||||||
keys.resize(count);
|
keys.resize(count);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
// Reads the key of each record whose rank is set.
|
||||||
|
const auto read_ranks = [&](uint64_t base, uint64_t size, uint64_t first, uint64_t step,
|
||||||
|
uint32_t ranks) {
|
||||||
|
keys.reserve(std::popcount(ranks));
|
||||||
|
for (; ranks != 0u; ranks &= ranks - 1u) {
|
||||||
|
const auto offset = first + std::countr_zero(ranks) * step;
|
||||||
|
uint32_t key = 0;
|
||||||
|
if (offset > UINT32_MAX || !read_selector(base, size, offset, {&key, 1})) return false;
|
||||||
|
keys.push_back(key);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
if (selector->rows) {
|
if (selector->rows) {
|
||||||
const auto& rows = *selector->rows;
|
const auto& rows = *selector->rows;
|
||||||
DescriptorValue count_value;
|
DescriptorValue count_value;
|
||||||
@@ -377,19 +389,21 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
|||||||
}
|
}
|
||||||
} else if (selector->bitset) {
|
} else if (selector->bitset) {
|
||||||
ShaderBufferResource material;
|
ShaderBufferResource material;
|
||||||
std::array<uint32_t, 4> masks {};
|
std::array<uint32_t, 8> storage {};
|
||||||
|
const auto record_words = std::min<size_t>(selector->stride / 4u, storage.size());
|
||||||
|
const auto masks = std::span(storage).first(record_words);
|
||||||
uint32_t first = 0, count = 0;
|
uint32_t first = 0, count = 0;
|
||||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||||
!WalkedValue(program, indirect.selector_first, first) ||
|
!WalkedValue(program, indirect.selector_first, first) ||
|
||||||
!EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u ||
|
!EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u ||
|
||||||
selector->stride != sizeof(masks) || selector->offset != 0u ||
|
selector->stride != masks.size_bytes() || selector->offset != 0u ||
|
||||||
material.Type() != 0u || material.Stride() != sizeof(masks) ||
|
material.Type() != 0u || material.Stride() != selector->stride ||
|
||||||
material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u ||
|
material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u ||
|
||||||
(material.Base48() & 3u) != 0u) return false;
|
(material.Base48() & 3u) != 0u) return false;
|
||||||
if (material.Format() != Prospero::BufferFormat::kInvalid) {
|
if (material.Format() != Prospero::BufferFormat::kInvalid) {
|
||||||
const auto words = material.GetSize() / sizeof(uint32_t);
|
const auto words = material.GetSize() / sizeof(uint32_t);
|
||||||
// Bitmap storage can grow with resolution while its candidate domain stays
|
// Bitmap storage can grow with resolution while its candidate domain stays at 32 keys
|
||||||
// at 128 keys. Bound scratch memory, preserving each coherent read's capture.
|
// per record word. Bound scratch memory, preserving each coherent read's capture.
|
||||||
for (uint64_t first_word = 0; first_word < words; first_word += keys.size()) {
|
for (uint64_t first_word = 0; first_word < words; first_word += keys.size()) {
|
||||||
keys.resize(std::min<uint64_t>(words - first_word, MaxIndirectDescriptorProbes));
|
keys.resize(std::min<uint64_t>(words - first_word, MaxIndirectDescriptorProbes));
|
||||||
if (!ReadScalarTable(material.Base48(), material.GetSize(), first_word * 4u,
|
if (!ReadScalarTable(material.Base48(), material.GetSize(), first_word * 4u,
|
||||||
@@ -445,12 +459,35 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
|||||||
uint64_t {first} + count > material.NumRecords())) return false;
|
uint64_t {first} + count > material.NumRecords())) return false;
|
||||||
const auto last = count != 0u ? uint64_t {first} + count - 1u : 0u;
|
const auto last = count != 0u ? uint64_t {first} + count - 1u : 0u;
|
||||||
if (last > UINT32_MAX) return false;
|
if (last > UINT32_MAX) return false;
|
||||||
|
// Out-of-bounds scalar reads return zero.
|
||||||
const auto end = count != 0u ? last * step + selector->offset + 4u : 0u;
|
const auto end = count != 0u ? last * step + selector->offset + 4u : 0u;
|
||||||
if (end > uint64_t {UINT32_MAX} + 1u ||
|
if (end > uint64_t {UINT32_MAX} + 1u) return false;
|
||||||
(indirect.workgroup_axis == UINT32_MAX && end > size))
|
const auto start = uint64_t {first} * step + selector->offset;
|
||||||
return false;
|
if (selector->ranks) {
|
||||||
if (!read_keys(base, size, uint64_t {first} * step + selector->offset, step, count))
|
// A scan selects the ranks some word of its mask's address class sets.
|
||||||
|
const auto& mask = *selector->ranks;
|
||||||
|
DescriptorValue mask_value;
|
||||||
|
ShaderBufferResource masks;
|
||||||
|
if (count > 32u || !WalkedDescriptor(program, mask.source, mask_value) ||
|
||||||
|
!DecodeBufferDescriptor(mask_value, masks)) return false;
|
||||||
|
// Each chunk holds whole strides of the power-of-two class, or one of its words.
|
||||||
|
const auto words = mask.stride / 4u;
|
||||||
|
const auto chunk =
|
||||||
|
words <= MaxIndirectDescriptorProbes ? MaxIndirectDescriptorProbes : 1u;
|
||||||
|
uint32_t ranks = 0;
|
||||||
|
for (auto at = uint64_t {mask.offset}; at + 4u <= masks.GetSize();
|
||||||
|
at += uint64_t {std::max(chunk, words)} * 4u) {
|
||||||
|
keys.resize(std::min<uint64_t>((masks.GetSize() - at) / 4u, chunk));
|
||||||
|
if (!ReadScalarTable(masks.Base48(), masks.GetSize(), at, runtime, keys))
|
||||||
|
return false;
|
||||||
|
for (size_t word = 0; word < keys.size(); word += words) ranks |= keys[word];
|
||||||
|
}
|
||||||
|
keys.clear();
|
||||||
|
if (count < 32u) ranks &= (1u << count) - 1u;
|
||||||
|
if (!read_ranks(base, size, start, step, ranks)) return false;
|
||||||
|
} else if (!read_keys(base, size, start, step, count)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
} else if (!indirect.selector_mask.IsEmpty()) {
|
} else if (!indirect.selector_mask.IsEmpty()) {
|
||||||
uint32_t mask = 0;
|
uint32_t mask = 0;
|
||||||
uint32_t count = 0;
|
uint32_t count = 0;
|
||||||
@@ -462,18 +499,8 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
|||||||
if (count < 32u) mask &= (1u << count) - 1u;
|
if (count < 32u) mask &= (1u << count) - 1u;
|
||||||
const auto material_base =
|
const auto material_base =
|
||||||
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
|
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
|
||||||
keys.reserve(std::popcount(mask));
|
if (!read_ranks(material_base, UINT64_MAX, selector->offset, selector->stride, mask))
|
||||||
while (mask != 0u) {
|
return false;
|
||||||
const auto index = std::countr_zero(mask);
|
|
||||||
const auto offset = static_cast<uint64_t>(selector->offset) +
|
|
||||||
static_cast<uint64_t>(index) * selector->stride;
|
|
||||||
if (offset > UINT32_MAX) return false;
|
|
||||||
uint32_t key = 0;
|
|
||||||
if (!read_selector(material_base, UINT64_MAX, static_cast<uint32_t>(offset),
|
|
||||||
{&key, 1})) return false;
|
|
||||||
keys.push_back(key);
|
|
||||||
mask &= mask - 1u;
|
|
||||||
}
|
|
||||||
} else if (selector->stride == 0u) {
|
} else if (selector->stride == 0u) {
|
||||||
ShaderBufferResource material;
|
ShaderBufferResource material;
|
||||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||||
@@ -503,6 +530,7 @@ bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::In
|
|||||||
if (selector->bit_offset != 0u || selector->bit_mask != UINT32_MAX || selector->maximum != UINT32_MAX)
|
if (selector->bit_offset != 0u || selector->bit_mask != UINT32_MAX || selector->maximum != UINT32_MAX)
|
||||||
for (auto& key: keys)
|
for (auto& key: keys)
|
||||||
key = std::min((key >> selector->bit_offset) & selector->bit_mask, selector->maximum);
|
key = std::min((key >> selector->bit_offset) & selector->bit_mask, selector->maximum);
|
||||||
|
if (selector->excluded) std::erase(keys, *selector->excluded);
|
||||||
std::ranges::sort(keys);
|
std::ranges::sort(keys);
|
||||||
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||||
}
|
}
|
||||||
@@ -756,6 +784,12 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
|||||||
std::iota(keys.begin(), keys.end(), first_key);
|
std::iota(keys.begin(), keys.end(), first_key);
|
||||||
if (!keys.empty() && keys.back() < keys.front())
|
if (!keys.empty() && keys.back() < keys.front())
|
||||||
std::rotate(keys.begin(), keys.begin() + (UINT32_MAX - first_key + 1ull), keys.end());
|
std::rotate(keys.begin(), keys.begin() + (UINT32_MAX - first_key + 1ull), keys.end());
|
||||||
|
if (indirect.workgroup_shift != 0u) {
|
||||||
|
for (auto& key: keys)
|
||||||
|
key = static_cast<uint32_t>(uint64_t {key} * indirect.workgroup_multiplier >>
|
||||||
|
indirect.workgroup_shift);
|
||||||
|
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!indirect.selector_mask.IsEmpty() && table_value.dword_count != 2u) return false;
|
if (!indirect.selector_mask.IsEmpty() && table_value.dword_count != 2u) return false;
|
||||||
if (!selector_keys->resolved) {
|
if (!selector_keys->resolved) {
|
||||||
@@ -1041,6 +1075,26 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
|||||||
return dimension == Decoder::ImageDimension::Dim1D ||
|
return dimension == Decoder::ImageDimension::Dim1D ||
|
||||||
dimension == Decoder::ImageDimension::Dim2D;
|
dimension == Decoder::ImageDimension::Dim2D;
|
||||||
};
|
};
|
||||||
|
// The descriptor's type selects the address layout. A 2D or 3D candidate reads the leading
|
||||||
|
// coordinates of an equal or larger dimension, but an array with more than one slice reads
|
||||||
|
// the next as its slice. Derivative, LOD, clamp and mip operands shift with the dimension.
|
||||||
|
const auto leading = [&](Decoder::ImageDimension dimension, const DescriptorValue& value) {
|
||||||
|
const bool sliced =
|
||||||
|
static_cast<Prospero::ImageType>((value.dwords[3] >> 28u) & 0xfu) ==
|
||||||
|
Prospero::ImageType::kColor2DArray &&
|
||||||
|
(value.dwords[4] & 0x1fffu) != ((value.dwords[4] >> 16u) & 0x1fffu);
|
||||||
|
return !sliced &&
|
||||||
|
(dimension == Decoder::ImageDimension::Dim2D || dimension == base.dimension);
|
||||||
|
};
|
||||||
|
const bool leading_coordinates = (base.dimension == Decoder::ImageDimension::Dim2D ||
|
||||||
|
base.dimension == Decoder::ImageDimension::Dim3D) &&
|
||||||
|
leading(image_class.dimension, snapshot.images[exemplar]) &&
|
||||||
|
std::ranges::none_of(program.memory_info, [&](const MemoryInfo& memory) {
|
||||||
|
return memory.kind == ResourceKind::Image && memory.resource == root_index &&
|
||||||
|
(memory.image_has_mip || (memory.image_sample_flags &
|
||||||
|
(Decoder::ImageSampleFlagLod | Decoder::ImageSampleFlagDerivative |
|
||||||
|
Decoder::ImageSampleFlagLodClamp)) != 0u);
|
||||||
|
});
|
||||||
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
|
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
|
||||||
auto& image = specialization.images[candidate];
|
auto& image = specialization.images[candidate];
|
||||||
if (image.indirect_root != root_index) {
|
if (image.indirect_root != root_index) {
|
||||||
@@ -1060,8 +1114,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
|||||||
base.dimension == Decoder::ImageDimension::Dim2D &&
|
base.dimension == Decoder::ImageDimension::Dim2D &&
|
||||||
!image.cube && !image_class.cube && is_1d_or_2d(image.dimension) &&
|
!image.cube && !image_class.cube && is_1d_or_2d(image.dimension) &&
|
||||||
is_1d_or_2d(image_class.dimension);
|
is_1d_or_2d(image_class.dimension);
|
||||||
|
const bool prefix_coordinates =
|
||||||
|
leading_coordinates && leading(image.dimension, snapshot.images[candidate]);
|
||||||
if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension)) &&
|
if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension)) &&
|
||||||
!unfiltered_coordinates) ||
|
!unfiltered_coordinates && !prefix_coordinates) ||
|
||||||
image.conversion_format != image_class.conversion_format ||
|
image.conversion_format != image_class.conversion_format ||
|
||||||
(!storage_swizzle && image.shader_swizzle != image_class.shader_swizzle)) {
|
(!storage_swizzle && image.shader_swizzle != image_class.shader_swizzle)) {
|
||||||
return SpecializationFail(
|
return SpecializationFail(
|
||||||
@@ -1164,14 +1220,16 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
|
|||||||
block.sources.push_back(program.info.buffers.at(memory.resource).source);
|
block.sources.push_back(program.info.buffers.at(memory.resource).source);
|
||||||
} else {
|
} else {
|
||||||
block.sources.push_back(program.info.images.at(memory.resource).source);
|
block.sources.push_back(program.info.images.at(memory.resource).source);
|
||||||
|
block.images.push_back(memory.resource);
|
||||||
if (image.needs_sampler) {
|
if (image.needs_sampler) {
|
||||||
block.sources.push_back(program.info.samplers.at(memory.sampler).source);
|
block.sources.push_back(program.info.samplers.at(memory.sampler).source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::ranges::sort(block.sources);
|
for (auto* list: {&block.sources, &block.images}) {
|
||||||
block.sources.erase(std::unique(block.sources.begin(), block.sources.end()),
|
std::ranges::sort(*list);
|
||||||
block.sources.end());
|
list->erase(std::unique(list->begin(), list->end()), list->end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (auto& block: blocks) block.condition = predicate(block.condition);
|
for (auto& block: blocks) block.condition = predicate(block.condition);
|
||||||
if (std::ranges::none_of(
|
if (std::ranges::none_of(
|
||||||
@@ -1645,7 +1703,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
|||||||
}
|
}
|
||||||
if (source->indirect_descriptor.has_value()) {
|
if (source->indirect_descriptor.has_value()) {
|
||||||
snapshot.images[i] = {.dword_count = 8u};
|
snapshot.images[i] = {.dword_count = 8u};
|
||||||
if (!WalkedActive(program, image.source)) {
|
if (!WalkedActive(program, image.source) || !WalkedImageActive(program, i)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto& indirect = *source->indirect_descriptor;
|
const auto& indirect = *source->indirect_descriptor;
|
||||||
@@ -1665,7 +1723,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
|||||||
if (!evaluate(image.source, snapshot.images[i])) {
|
if (!evaluate(image.source, snapshot.images[i])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ValidImageDescriptor(snapshot.images[i], image.r128)) {
|
// An image listed only by unreached blocks reads zero, not its source's descriptor.
|
||||||
|
if (!WalkedImageActive(program, i) ||
|
||||||
|
!ValidImageDescriptor(snapshot.images[i], image.r128)) {
|
||||||
snapshot.images[i].dwords.fill(0);
|
snapshot.images[i].dwords.fill(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1711,8 +1771,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) {
|
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) {
|
||||||
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, and
|
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, entries of
|
||||||
// entries that need shader-side conversion, read as null instead of failing the draw.
|
// another dimension and entries that need shader-side conversion read as null instead of
|
||||||
|
// failing the draw.
|
||||||
const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu);
|
const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu);
|
||||||
const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu);
|
const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu);
|
||||||
const auto numeric_class =
|
const auto numeric_class =
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@@ -1014,7 +1016,9 @@ private:
|
|||||||
(!shared_image_heap && (a.table_offset != b.table_offset ||
|
(!shared_image_heap && (a.table_offset != b.table_offset ||
|
||||||
a.table_immediate != b.table_immediate ||
|
a.table_immediate != b.table_immediate ||
|
||||||
a.table_stride != b.table_stride)) || a.indexed != b.indexed ||
|
a.table_stride != b.table_stride)) || a.indexed != b.indexed ||
|
||||||
a.workgroup_axis != b.workgroup_axis || a.sources != b.sources ||
|
a.workgroup_axis != b.workgroup_axis ||
|
||||||
|
a.workgroup_multiplier != b.workgroup_multiplier ||
|
||||||
|
a.workgroup_shift != b.workgroup_shift || a.sources != b.sources ||
|
||||||
a.signed_count != b.signed_count ||
|
a.signed_count != b.signed_count ||
|
||||||
!EquivalentValue(m_program, a.key_count, b.key_count) ||
|
!EquivalentValue(m_program, a.key_count, b.key_count) ||
|
||||||
a.selector_first.IsEmpty() != b.selector_first.IsEmpty() ||
|
a.selector_first.IsEmpty() != b.selector_first.IsEmpty() ||
|
||||||
@@ -1136,31 +1140,63 @@ private:
|
|||||||
LaneQuantifier lanes = LaneQuantifier::All;
|
LaneQuantifier lanes = LaneQuantifier::All;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The immediate that edge proves value differs from on every lane, if any.
|
||||||
|
std::optional<uint32_t> ExcludedBy(const EdgePredicate& edge, Value value) const {
|
||||||
|
if (edge.lanes != LaneQuantifier::All) return {};
|
||||||
|
const auto* test = edge.condition.TryInstruction();
|
||||||
|
if (test != nullptr && test->NumArgs() == 2u &&
|
||||||
|
((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) ||
|
||||||
|
(test->GetOpcode() == ValueOpcode::IEqual32 && !edge.positive))) {
|
||||||
|
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||||
|
uint32_t immediate;
|
||||||
|
if (ImmediateU32(test->Arg(arg), immediate) &&
|
||||||
|
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return immediate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool NonzeroOnEntry(Value value, const Block* block) const {
|
bool NonzeroOnEntry(Value value, const Block* block) const {
|
||||||
if (block == nullptr) return false;
|
if (block == nullptr) return false;
|
||||||
bool witnessed = false;
|
bool witnessed = false;
|
||||||
const bool guarded = GuardedOnEntry(block, [](const Block* current) {
|
const bool guarded = GuardedOnEntry(block, [](const Block* current) {
|
||||||
return current->ImmPredecessors().size() != 1u;
|
return current->ImmPredecessors().size() != 1u;
|
||||||
}, [&](const EdgePredicate& edge) {
|
}, [&](const EdgePredicate& edge) {
|
||||||
if (edge.lanes != LaneQuantifier::All) return false;
|
return ExcludedBy(edge, value) == 0u && (witnessed = true);
|
||||||
const auto* test = edge.condition.TryInstruction();
|
|
||||||
if (test != nullptr && test->NumArgs() == 2u &&
|
|
||||||
((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) ||
|
|
||||||
(test->GetOpcode() == ValueOpcode::IEqual32 && !edge.positive))) {
|
|
||||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
|
||||||
uint32_t immediate;
|
|
||||||
if (ImmediateU32(test->Arg(arg), immediate) && immediate == 0u &&
|
|
||||||
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return witnessed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
return guarded && witnessed;
|
return guarded && witnessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t WorkgroupAxis(Value key) const {
|
// The key that every path from its definition to block rules out, if any.
|
||||||
|
std::optional<uint32_t> ExcludedKeyOnEntry(Value key, const Block* block) const {
|
||||||
|
const auto* definition = key.Resolve().TryInstruction();
|
||||||
|
if (definition == nullptr) return {};
|
||||||
|
std::optional<uint32_t> excluded;
|
||||||
|
const auto defined = [&](const Block* current) { return current == definition->Parent(); };
|
||||||
|
const bool guarded = GuardedOnEntry(block, defined, [&](const EdgePredicate& edge) {
|
||||||
|
const auto immediate = ExcludedBy(edge, key);
|
||||||
|
if (!immediate || (excluded && excluded != immediate)) return false;
|
||||||
|
excluded = immediate;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return guarded ? excluded : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The workgroup ID axis of key; with first, also of a runtime first + workgroup ID sum.
|
||||||
|
uint32_t WorkgroupAxis(Value key, Value* first = nullptr) const {
|
||||||
const auto* builtin = key.Resolve().TryInstruction();
|
const auto* builtin = key.Resolve().TryInstruction();
|
||||||
uint32_t axis = UINT32_MAX;
|
uint32_t axis = UINT32_MAX;
|
||||||
|
if (first != nullptr && builtin != nullptr && builtin->GetOpcode() == ValueOpcode::IAdd32) {
|
||||||
|
for (uint32_t arm = 0; arm < 2u; ++arm) {
|
||||||
|
axis = WorkgroupAxis(builtin->Arg(arm));
|
||||||
|
const auto base = builtin->Arg(arm ^ 1u);
|
||||||
|
if (axis == UINT32_MAX ||
|
||||||
|
!ValidateRuntimeValue(m_program, base, RuntimeValueType::Integer)) continue;
|
||||||
|
*first = base;
|
||||||
|
return axis;
|
||||||
|
}
|
||||||
|
return UINT32_MAX;
|
||||||
|
}
|
||||||
if (m_program.stage != ShaderType::Compute || builtin == nullptr ||
|
if (m_program.stage != ShaderType::Compute || builtin == nullptr ||
|
||||||
builtin->GetOpcode() != ValueOpcode::GetBuiltin || builtin->NumArgs() != 2u ||
|
builtin->GetOpcode() != ValueOpcode::GetBuiltin || builtin->NumArgs() != 2u ||
|
||||||
builtin->Arg(0) != Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)) ||
|
builtin->Arg(0) != Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)) ||
|
||||||
@@ -1168,6 +1204,27 @@ private:
|
|||||||
return axis;
|
return axis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Matches key = (first + workgroup ID) * multiplier >> shift with shift >= 32; first and the
|
||||||
|
// division are optional.
|
||||||
|
bool MatchWorkgroupKey(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
|
||||||
|
uint32_t bits = 0, multiplier = 0;
|
||||||
|
const auto* shift = key.Resolve().TryInstruction();
|
||||||
|
if (shift != nullptr && shift->GetOpcode() == ValueOpcode::ShiftRightLogical32) {
|
||||||
|
const auto* high = shift->Arg(0).Resolve().TryInstruction();
|
||||||
|
if (high != nullptr && high->GetOpcode() == ValueOpcode::UMulHi &&
|
||||||
|
ImmediateU32(shift->Arg(1), bits) && bits < 32u &&
|
||||||
|
ImmediateU32(high->Arg(1), multiplier)) {
|
||||||
|
key = high->Arg(0);
|
||||||
|
bits += 32u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indirect.workgroup_axis = WorkgroupAxis(key, &indirect.selector_first);
|
||||||
|
if (indirect.workgroup_axis == UINT32_MAX) return false;
|
||||||
|
indirect.workgroup_multiplier = multiplier;
|
||||||
|
indirect.workgroup_shift = bits;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool MatchTableOffset(Value value, Value& key, uint32_t& offset, uint32_t& stride) const {
|
bool MatchTableOffset(Value value, Value& key, uint32_t& offset, uint32_t& stride) const {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -1672,6 +1729,8 @@ private:
|
|||||||
uint32_t initial_arm;
|
uint32_t initial_arm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool ZeroBased(uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; }
|
||||||
|
|
||||||
std::optional<Induction> MatchInduction(Value key, const auto& accepts_step) const {
|
std::optional<Induction> MatchInduction(Value key, const auto& accepts_step) const {
|
||||||
key = key.Resolve();
|
key = key.Resolve();
|
||||||
const auto* phi = key.TryInstruction();
|
const auto* phi = key.TryInstruction();
|
||||||
@@ -1733,8 +1792,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t PostTestLoopMaximum(Value key) const {
|
uint32_t PostTestLoopMaximum(Value key) const {
|
||||||
const auto induction = MatchInduction(key,
|
const auto induction = MatchInduction(key, ZeroBased);
|
||||||
[](uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; });
|
|
||||||
if (!induction) return UINT32_MAX;
|
if (!induction) return UINT32_MAX;
|
||||||
const auto [phi, step, initial_arm] = *induction;
|
const auto [phi, step, initial_arm] = *induction;
|
||||||
const auto* incoming = phi->PhiBlock(initial_arm ^ 1u);
|
const auto* incoming = phi->PhiBlock(initial_arm ^ 1u);
|
||||||
@@ -1767,8 +1825,7 @@ private:
|
|||||||
|
|
||||||
const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound,
|
const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound,
|
||||||
LaneQuantifier lanes = LaneQuantifier::Any) const {
|
LaneQuantifier lanes = LaneQuantifier::Any) const {
|
||||||
return BoundedInduction(key, use,
|
return BoundedInduction(key, use, ZeroBased,
|
||||||
[](uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; },
|
|
||||||
[&](const Inst& compare, uint32_t initial) {
|
[&](const Inst& compare, uint32_t initial) {
|
||||||
return (compare.GetOpcode() == ValueOpcode::SLessThan32 ||
|
return (compare.GetOpcode() == ValueOpcode::SLessThan32 ||
|
||||||
compare.GetOpcode() == ValueOpcode::ULessThan32) &&
|
compare.GetOpcode() == ValueOpcode::ULessThan32) &&
|
||||||
@@ -1793,6 +1850,29 @@ private:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A zero-based key that only per-lane data bounds takes the count all uniform zero-based
|
||||||
|
// loops in the program share, assuming each lane's count stays within it.
|
||||||
|
bool ProgramLoopCount(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
|
||||||
|
if (!MatchInduction(key, ZeroBased) || BoundedLoop(key, nullptr) != nullptr) return false;
|
||||||
|
const Inst* count = nullptr;
|
||||||
|
for (auto* block: m_program.blocks) {
|
||||||
|
for (auto& phi: *block) {
|
||||||
|
if (phi.GetOpcode() != ValueOpcode::Phi) break;
|
||||||
|
const auto* compare = BoundedLoop(Value(&phi), nullptr);
|
||||||
|
if (compare == nullptr) continue;
|
||||||
|
if (count != nullptr && !EquivalentValue(m_program, compare->Arg(1), count->Arg(1)))
|
||||||
|
return false;
|
||||||
|
count = compare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count == nullptr) return false;
|
||||||
|
std::printf("Warning: shader 0x%016llx bounds a per-lane table loop by its uniform loops' "
|
||||||
|
"count\n", static_cast<unsigned long long>(m_program.shader_hash));
|
||||||
|
indirect.key_count = count->Arg(1);
|
||||||
|
indirect.signed_count = count->GetOpcode() == ValueOpcode::SLessThan32;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Value ActiveValue(Value value, Value guard) const {
|
Value ActiveValue(Value value, Value guard) const {
|
||||||
value = value.Resolve();
|
value = value.Resolve();
|
||||||
while (const auto* select = value.TryInstruction()) {
|
while (const auto* select = value.TryInstruction()) {
|
||||||
@@ -1829,6 +1909,10 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Dominates(const Block* initial, const Block* use, Value witness = {}) const {
|
bool Dominates(const Block* initial, const Block* use, Value witness = {}) const {
|
||||||
|
// The proven local-zero invocation fails every all-lane bypass condition that excludes it,
|
||||||
|
// such as an all-inactive or an all-nonzero bypass.
|
||||||
|
const auto* negated = SimplifyGuard(witness).TryInstruction();
|
||||||
|
const bool nonzero = negated != nullptr && negated->GetOpcode() == ValueOpcode::LogicalNot;
|
||||||
std::vector<const Block*> pending {use};
|
std::vector<const Block*> pending {use};
|
||||||
for (size_t index = 0; index < pending.size(); ++index) {
|
for (size_t index = 0; index < pending.size(); ++index) {
|
||||||
const auto* current = pending[index];
|
const auto* current = pending[index];
|
||||||
@@ -1837,9 +1921,9 @@ private:
|
|||||||
for (const auto* previous: current->ImmPredecessors()) {
|
for (const auto* previous: current->ImmPredecessors()) {
|
||||||
if (!witness.IsEmpty()) {
|
if (!witness.IsEmpty()) {
|
||||||
const auto edge = ConditionalEdge(previous, current);
|
const auto edge = ConditionalEdge(previous, current);
|
||||||
// The proven local-zero invocation makes an all-inactive bypass impossible.
|
if (edge && edge->lanes == LaneQuantifier::All &&
|
||||||
if (edge && edge->lanes == LaneQuantifier::All && !edge->positive &&
|
(edge->positive ? nonzero && Implies(edge->condition, negated->Arg(0))
|
||||||
Implies(witness, edge->condition)) continue;
|
: Implies(witness, edge->condition))) continue;
|
||||||
}
|
}
|
||||||
if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous);
|
if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous);
|
||||||
}
|
}
|
||||||
@@ -1948,7 +2032,10 @@ private:
|
|||||||
|
|
||||||
bool LocalZeroWitness(Value guard) const {
|
bool LocalZeroWitness(Value guard) const {
|
||||||
const auto* equal = SimplifyGuard(guard).TryInstruction();
|
const auto* equal = SimplifyGuard(guard).TryInstruction();
|
||||||
if (equal == nullptr || equal->GetOpcode() != ValueOpcode::IEqual32) return false;
|
const bool negated = equal != nullptr && equal->GetOpcode() == ValueOpcode::LogicalNot;
|
||||||
|
if (negated) equal = SimplifyGuard(equal->Arg(0)).TryInstruction();
|
||||||
|
const auto expected = negated ? ValueOpcode::INotEqual32 : ValueOpcode::IEqual32;
|
||||||
|
if (equal == nullptr || equal->GetOpcode() != expected) return false;
|
||||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||||
if (equal->Arg(arg).Resolve() != Value(0u)) continue;
|
if (equal->Arg(arg).Resolve() != Value(0u)) continue;
|
||||||
Value induction;
|
Value induction;
|
||||||
@@ -1982,6 +2069,10 @@ private:
|
|||||||
EquivalentValue(m_program, test->Arg(0), value)) return test->Arg(1);
|
EquivalentValue(m_program, test->Arg(0), value)) return test->Arg(1);
|
||||||
if ((positive ? op == ValueOpcode::UGreaterThan32 : op == ValueOpcode::ULessThanEqual32) &&
|
if ((positive ? op == ValueOpcode::UGreaterThan32 : op == ValueOpcode::ULessThanEqual32) &&
|
||||||
EquivalentValue(m_program, test->Arg(1), value)) return test->Arg(0);
|
EquivalentValue(m_program, test->Arg(1), value)) return test->Arg(0);
|
||||||
|
uint32_t limit = 0;
|
||||||
|
if ((positive ? op == ValueOpcode::ULessThanEqual32 : op == ValueOpcode::UGreaterThan32) &&
|
||||||
|
ImmediateU32(test->Arg(1), limit) && limit != UINT32_MAX &&
|
||||||
|
EquivalentValue(m_program, test->Arg(0), value)) return Value(limit + 1u);
|
||||||
const auto* scan = value.Resolve().TryInstruction();
|
const auto* scan = value.Resolve().TryInstruction();
|
||||||
if (finite_bitscan && scan != nullptr && scan->GetOpcode() == ValueOpcode::FindILsb32 &&
|
if (finite_bitscan && scan != nullptr && scan->GetOpcode() == ValueOpcode::FindILsb32 &&
|
||||||
(positive ? op == ValueOpcode::INotEqual32 : op == ValueOpcode::IEqual32)) {
|
(positive ? op == ValueOpcode::INotEqual32 : op == ValueOpcode::IEqual32)) {
|
||||||
@@ -2056,7 +2147,7 @@ private:
|
|||||||
const auto* read = ActiveValue(value, active).TryInstruction();
|
const auto* read = ActiveValue(value, active).TryInstruction();
|
||||||
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
||||||
m_program.stage != ShaderType::Compute ||
|
m_program.stage != ShaderType::Compute ||
|
||||||
read->Arg(1).Resolve() != Value(true)) return {};
|
!Implies(active, read->Arg(1))) return {};
|
||||||
uint64_t group_size = 1u;
|
uint64_t group_size = 1u;
|
||||||
for (const auto dimension: m_input.compute->threads_num) {
|
for (const auto dimension: m_input.compute->threads_num) {
|
||||||
if (dimension == 0u || dimension > m_input.compute->wave_size) return {};
|
if (dimension == 0u || dimension > m_input.compute->wave_size) return {};
|
||||||
@@ -2087,10 +2178,16 @@ private:
|
|||||||
const auto* limit = BoundedLoop(read_offset.index, read->Parent(),
|
const auto* limit = BoundedLoop(read_offset.index, read->Parent(),
|
||||||
[](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; });
|
[](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; });
|
||||||
if (limit == nullptr) return {};
|
if (limit == nullptr) return {};
|
||||||
|
auto counter_active = active;
|
||||||
const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction();
|
const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction();
|
||||||
|
// Every lane reads the same counter word, so a selected lane carries it.
|
||||||
|
if (counter_read != nullptr && counter_read->GetOpcode() == ValueOpcode::ReadFirstLane) {
|
||||||
|
counter_active = counter_read->Arg(1);
|
||||||
|
counter_read = ActiveValue(counter_read->Arg(0), counter_active).TryInstruction();
|
||||||
|
}
|
||||||
uint32_t consumed_counter = 0;
|
uint32_t consumed_counter = 0;
|
||||||
if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
||||||
counter_read->Arg(1).Resolve() != Value(true) ||
|
!Implies(counter_active, counter_read->Arg(1)) ||
|
||||||
!constant_address(*counter_read, consumed_counter)) return {};
|
!constant_address(*counter_read, consumed_counter)) return {};
|
||||||
|
|
||||||
struct Initializer { const Inst* inst; uint32_t address; uint32_t words; };
|
struct Initializer { const Inst* inst; uint32_t address; uint32_t words; };
|
||||||
@@ -2304,25 +2401,23 @@ private:
|
|||||||
((offset | stride | memory->offset) & 3u) == 0u &&
|
((offset | stride | memory->offset) & 3u) == 0u &&
|
||||||
uint64_t {offset} + memory->offset <= UINT32_MAX;
|
uint64_t {offset} + memory->offset <= UINT32_MAX;
|
||||||
if (affine) {
|
if (affine) {
|
||||||
workgroup_axis = WorkgroupAxis(index);
|
workgroup_axis = WorkgroupAxis(index, &first);
|
||||||
if (workgroup_axis == UINT32_MAX) {
|
|
||||||
const auto* sum = index.Resolve().TryInstruction();
|
|
||||||
if (sum != nullptr && sum->GetOpcode() == ValueOpcode::IAdd32) {
|
|
||||||
for (uint32_t arm = 0; arm < 2u; ++arm) {
|
|
||||||
const auto axis = WorkgroupAxis(sum->Arg(arm));
|
|
||||||
const auto base = sum->Arg(arm ^ 1u);
|
|
||||||
if (axis == UINT32_MAX ||
|
|
||||||
!ValidateRuntimeValue(m_program, base, RuntimeValueType::Integer)) continue;
|
|
||||||
workgroup_axis = axis;
|
|
||||||
first = base;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto* bound = BoundedLoop(index, read->Parent());
|
const auto* bound = BoundedLoop(index, read->Parent());
|
||||||
if (bound != nullptr && bound->GetOpcode() == ValueOpcode::ULessThan32) {
|
if (bound != nullptr && bound->GetOpcode() == ValueOpcode::ULessThan32) {
|
||||||
count = bound->Arg(1);
|
count = bound->Arg(1);
|
||||||
|
} else if (workgroup_axis == UINT32_MAX && !require_bound) {
|
||||||
|
// Other bounded indices, such as bit scans, read the records up to their maximum;
|
||||||
|
// a bit scan of a nonzero mask load reads only the ranks its words set.
|
||||||
|
const auto maximum = SelectorMaximum(index);
|
||||||
|
if (maximum == 31u) selector.ranks = BitScanRanks(index);
|
||||||
|
if (maximum < MaxIndirectDescriptorProbes) count = Value(maximum + 1u);
|
||||||
}
|
}
|
||||||
|
// A record selected from a shared append list is below the list's bound.
|
||||||
|
const auto* lane = index.Resolve().TryInstruction();
|
||||||
|
if (count.IsEmpty() && lane != nullptr &&
|
||||||
|
lane->GetOpcode() == ValueOpcode::ReadFirstLane &&
|
||||||
|
HasActiveLane(lane->Arg(1), lane->Parent()))
|
||||||
|
count = SharedLoadBound(lane->Arg(0), lane->Arg(1));
|
||||||
}
|
}
|
||||||
// Raw pointers have no V# bounds for enumerating an unbounded selector domain.
|
// Raw pointers have no V# bounds for enumerating an unbounded selector domain.
|
||||||
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() &&
|
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() &&
|
||||||
@@ -2498,7 +2593,8 @@ private:
|
|||||||
// Each equation is an inductive subset obligation. Phis require every incoming
|
// Each equation is an inductive subset obligation. Phis require every incoming
|
||||||
// value; AND needs either operand. Grounding rejects cycles without an initial
|
// value; AND needs either operand. Grounding rejects cycles without an initial
|
||||||
// value, then the greatest fixed point checks every initial and carried edge.
|
// value, then the greatest fixed point checks every initial and carried edge.
|
||||||
bool BitsetMaskSubset(Value mask, Value active, Value word, const Inst& read) const {
|
bool BitsetMaskSubset(Value mask, Value active, Value word,
|
||||||
|
std::span<const Inst* const> reads) const {
|
||||||
struct State {
|
struct State {
|
||||||
Value value;
|
Value value;
|
||||||
Value guard;
|
Value guard;
|
||||||
@@ -2510,7 +2606,10 @@ private:
|
|||||||
bool all = true;
|
bool all = true;
|
||||||
std::vector<size_t> terms;
|
std::vector<size_t> terms;
|
||||||
};
|
};
|
||||||
const auto load_guard = read.Arg(4).Resolve();
|
// A scalar load reads one word without EXEC; four-word loads take their guard.
|
||||||
|
const bool scalar = reads[0]->GetOpcode() == ValueOpcode::ReadConstBuffer;
|
||||||
|
const auto load_guard = scalar ? Value(true) : reads[0]->Arg(4).Resolve();
|
||||||
|
const auto words = static_cast<uint32_t>(reads.size()) * (scalar ? 1u : 4u);
|
||||||
std::vector<State> states(2);
|
std::vector<State> states(2);
|
||||||
std::vector<Equation> equations(2);
|
std::vector<Equation> equations(2);
|
||||||
const auto add = [&](State state) {
|
const auto add = [&](State state) {
|
||||||
@@ -2521,9 +2620,9 @@ private:
|
|||||||
equations.emplace_back();
|
equations.emplace_back();
|
||||||
return states.size() - 1u;
|
return states.size() - 1u;
|
||||||
};
|
};
|
||||||
std::array<size_t, 5> roots {add({.value = active})};
|
std::vector<size_t> roots {add({.value = active})};
|
||||||
for (uint32_t component = 0; component < 4u; ++component)
|
for (uint32_t component = 0; component < words; ++component)
|
||||||
roots[component + 1u] = add({mask, active, component, component});
|
roots.push_back(add({mask, active, component, component}));
|
||||||
for (size_t index = 2; index < states.size(); ++index) {
|
for (size_t index = 2; index < states.size(); ++index) {
|
||||||
const auto state = states[index];
|
const auto state = states[index];
|
||||||
const auto* inst = state.value.TryInstruction();
|
const auto* inst = state.value.TryInstruction();
|
||||||
@@ -2573,11 +2672,14 @@ private:
|
|||||||
switch (inst->GetOpcode()) {
|
switch (inst->GetOpcode()) {
|
||||||
case ValueOpcode::CompositeExtractU32x4: {
|
case ValueOpcode::CompositeExtractU32x4: {
|
||||||
uint32_t component;
|
uint32_t component;
|
||||||
if (inst->Arg(0).Resolve().TryInstruction() == &read &&
|
if (inst->Arg(0).Resolve().TryInstruction() == reads[state.component / 4u] &&
|
||||||
ImmediateU32(inst->Arg(1), component) && component == state.component)
|
ImmediateU32(inst->Arg(1), component) && component == state.component % 4u)
|
||||||
equation.terms = {1u};
|
equation.terms = {1u};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ValueOpcode::ReadConstBuffer:
|
||||||
|
if (scalar && inst == reads[state.component]) equation.terms = {1u};
|
||||||
|
break;
|
||||||
case ValueOpcode::Phi:
|
case ValueOpcode::Phi:
|
||||||
// The predecessor may be an earlier outer iteration. Establish
|
// The predecessor may be an earlier outer iteration. Establish
|
||||||
// this register's invariant without the current word or EXEC.
|
// this register's invariant without the current word or EXEC.
|
||||||
@@ -2598,17 +2700,17 @@ private:
|
|||||||
const auto* masked = key.TryInstruction();
|
const auto* masked = key.TryInstruction();
|
||||||
uint32_t bound = 0;
|
uint32_t bound = 0;
|
||||||
if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 &&
|
if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 &&
|
||||||
DescendingLoop(word, masked->Parent(), bound) && bound == 3u) {
|
DescendingLoop(word, masked->Parent(), bound) && bound == words - 1u) {
|
||||||
for (uint32_t side = 0; side < 2u; ++side) {
|
for (uint32_t side = 0; side < 2u; ++side) {
|
||||||
uint32_t bits;
|
uint32_t bits;
|
||||||
if (ImmediateU32(masked->Arg(side), bits) && (bits & 3u) == 3u)
|
if (ImmediateU32(masked->Arg(side), bits) && (bits & bound) == bound)
|
||||||
key = masked->Arg(side ^ 1u).Resolve();
|
key = masked->Arg(side ^ 1u).Resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EquivalentValue(m_program, key, word);
|
return EquivalentValue(m_program, key, word);
|
||||||
});
|
});
|
||||||
uint32_t literal;
|
uint32_t literal;
|
||||||
if (ImmediateU32(equality, literal) && literal < 4u) selected_word = literal;
|
if (ImmediateU32(equality, literal) && literal < words) selected_word = literal;
|
||||||
equation.terms.push_back(term(inst->Arg(1), inst->Arg(0), selected_word));
|
equation.terms.push_back(term(inst->Arg(1), inst->Arg(0), selected_word));
|
||||||
}
|
}
|
||||||
if (!selected || !*selected)
|
if (!selected || !*selected)
|
||||||
@@ -2646,7 +2748,53 @@ private:
|
|||||||
return std::ranges::all_of(roots, [&](size_t root) { return valid[root]; });
|
return std::ranges::all_of(roots, [&](size_t root) { return valid[root]; });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds,
|
// The mask words whose set bits are every record a bit scan of index selects: the scanned
|
||||||
|
// value keeps only bits of one scalar load, which reads a word its address can reach.
|
||||||
|
std::optional<DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks>
|
||||||
|
BitScanRanks(Value index) {
|
||||||
|
const auto* scan = index.Resolve().TryInstruction();
|
||||||
|
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindILsb32) return {};
|
||||||
|
const auto* masked = scan->Arg(0).Resolve().TryInstruction();
|
||||||
|
if (masked == nullptr) return {};
|
||||||
|
std::vector<const Inst*> values {masked};
|
||||||
|
const Inst* load = nullptr;
|
||||||
|
for (size_t i = 0; i < values.size() && load == nullptr; ++i) {
|
||||||
|
const auto* value = values[i];
|
||||||
|
if (value->GetOpcode() == ValueOpcode::ReadConstBuffer) load = value;
|
||||||
|
if (value->GetOpcode() != ValueOpcode::Phi &&
|
||||||
|
value->GetOpcode() != ValueOpcode::BitwiseAnd32) continue;
|
||||||
|
for (size_t arg = 0; arg < value->NumArgs(); ++arg) {
|
||||||
|
const auto* next = value->Arg(arg).Resolve().TryInstruction();
|
||||||
|
if (next != nullptr && std::ranges::find(values, next) == values.end())
|
||||||
|
values.push_back(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t memory_index = 0;
|
||||||
|
const auto* memory = load != nullptr ? ScalarReadMemory(*load, memory_index) : nullptr;
|
||||||
|
if (memory == nullptr || memory->offset > INT32_MAX ||
|
||||||
|
!MemoryIndexBelongsTo(memory_index, *load) ||
|
||||||
|
!BitsetMaskSubset(scan->Arg(0), Value(true), Value(0u), std::array {load})) return {};
|
||||||
|
auto address = load->Arg(1);
|
||||||
|
auto active = Value(true);
|
||||||
|
const auto* lane = address.Resolve().TryInstruction();
|
||||||
|
if (lane != nullptr && lane->GetOpcode() == ValueOpcode::ReadFirstLane) {
|
||||||
|
if (!HasActiveLane(lane->Arg(1), lane->Parent())) return {};
|
||||||
|
active = lane->Arg(1);
|
||||||
|
address = lane->Arg(0);
|
||||||
|
}
|
||||||
|
// A left shift keeps the address a multiple of its scale; other addresses read any word.
|
||||||
|
const auto* shift = ActiveValue(address, active).TryInstruction();
|
||||||
|
uint32_t bits = 0;
|
||||||
|
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
|
||||||
|
!ImmediateU32(shift->Arg(1), bits) || bits >= 32u) bits = 0;
|
||||||
|
DescriptorSource source;
|
||||||
|
if (!MakeRuntimeTableSource(*load, source)) return {};
|
||||||
|
return DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks {
|
||||||
|
.source = InternSource(source), .stride = std::max(1u << bits, 4u),
|
||||||
|
.offset = memory->offset & ~3u};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds, uint32_t words,
|
||||||
DescriptorSource::IndirectDescriptor& indirect) {
|
DescriptorSource::IndirectDescriptor& indirect) {
|
||||||
const auto selected_value = [&](Value value) {
|
const auto selected_value = [&](Value value) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -2670,7 +2818,7 @@ private:
|
|||||||
}
|
}
|
||||||
const auto* word_inst = word.TryInstruction();
|
const auto* word_inst = word.TryInstruction();
|
||||||
if (word_inst == nullptr || std::ranges::none_of(bounds, [&](const auto& bound) {
|
if (word_inst == nullptr || std::ranges::none_of(bounds, [&](const auto& bound) {
|
||||||
return bound.first == word_inst && bound.second == 3u;
|
return bound.first == word_inst && bound.second == words - 1u;
|
||||||
})) return false;
|
})) return false;
|
||||||
// FFBH's leading-zero count is inverted again to recover the set-bit rank.
|
// FFBH's leading-zero count is inverted again to recover the set-bit rank.
|
||||||
for (uint32_t inverse = 0; inverse < 2u; ++inverse) {
|
for (uint32_t inverse = 0; inverse < 2u; ++inverse) {
|
||||||
@@ -2682,21 +2830,34 @@ private:
|
|||||||
const auto* scan = bit.TryInstruction();
|
const auto* scan = bit.TryInstruction();
|
||||||
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindUMsb32 ||
|
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindUMsb32 ||
|
||||||
!ImpliesNonzero(active, scan->Arg(0))) return false;
|
!ImpliesNonzero(active, scan->Arg(0))) return false;
|
||||||
|
const auto bitmap_load = [&](const Inst& read, uint32_t offset) {
|
||||||
|
const auto index = read.Flags<MemoryFlags>().index;
|
||||||
|
if (read.GetOpcode() != ValueOpcode::LoadBufferU32x4 || InCycle(read.Parent()) ||
|
||||||
|
index >= m_program.memory_info.size()) return false;
|
||||||
|
const auto& memory = m_program.memory_info[index];
|
||||||
|
return memory.kind == ResourceKind::Buffer && !memory.typed && !memory.formatted &&
|
||||||
|
memory.idxen && !memory.offen && memory.offset == offset &&
|
||||||
|
memory.data_bits == 32u && memory.data_dwords == 4u &&
|
||||||
|
read.Arg(2).Resolve() == Value(0u) && read.Arg(3).Resolve() == Value(0u);
|
||||||
|
};
|
||||||
for (const auto* block: m_program.blocks) {
|
for (const auto* block: m_program.blocks) {
|
||||||
for (const auto& read: *block) {
|
for (const auto& read: *block) {
|
||||||
if (read.GetOpcode() != ValueOpcode::LoadBufferU32x4 || InCycle(block)) continue;
|
if (!bitmap_load(read, 0u)) continue;
|
||||||
const auto index = read.Flags<MemoryFlags>().index;
|
// An eight-word record continues in a second four-word load of the same buffer and EXEC.
|
||||||
if (index >= m_program.memory_info.size()) continue;
|
std::array<const Inst*, 2> reads {&read};
|
||||||
const auto& memory = m_program.memory_info[index];
|
if (words == 8u)
|
||||||
if (memory.kind != ResourceKind::Buffer || memory.typed || memory.formatted ||
|
for (const auto* other: m_program.blocks)
|
||||||
!memory.idxen || memory.offen || memory.offset != 0u || memory.data_bits != 32u ||
|
for (const auto& load: *other)
|
||||||
memory.data_dwords != 4u || read.Arg(2).Resolve() != Value(0u) ||
|
if (reads[1] == nullptr && bitmap_load(load, 16u) &&
|
||||||
read.Arg(3).Resolve() != Value(0u) ||
|
EquivalentValue(m_program, load.Arg(0), read.Arg(0)) &&
|
||||||
!BitsetMaskSubset(scan->Arg(0), active, word, read)) continue;
|
EquivalentValue(m_program, load.Arg(4), read.Arg(4))) reads[1] = &load;
|
||||||
|
const auto parts = std::span(reads).first(words / 4u);
|
||||||
DescriptorSource source;
|
DescriptorSource source;
|
||||||
if (!MakeRuntimeTableSource(read, source)) continue;
|
if (std::ranges::find(parts, nullptr) != parts.end() ||
|
||||||
|
!BitsetMaskSubset(scan->Arg(0), active, word, parts) ||
|
||||||
|
!MakeRuntimeTableSource(read, source)) continue;
|
||||||
indirect.selector.emplace(DescriptorSource::IndirectDescriptor::SelectorRead {
|
indirect.selector.emplace(DescriptorSource::IndirectDescriptor::SelectorRead {
|
||||||
.source = InternSource(source), .stride = 16u, .offset = 0u, .bitset = true});
|
.source = InternSource(source), .stride = words * 4u, .offset = 0u, .bitset = true});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2726,7 +2887,8 @@ private:
|
|||||||
first.SetArg(1, Value(maximum));
|
first.SetArg(1, Value(maximum));
|
||||||
indirect.selector_first = Value(&first);
|
indirect.selector_first = Value(&first);
|
||||||
indirect.key_count = Value(maximum + 1u);
|
indirect.key_count = Value(maximum + 1u);
|
||||||
if (maximum == 127u) MatchBitsetSelector(lane->Arg(0), lane->Arg(1), bounds, indirect);
|
if (maximum == 127u || maximum == 255u)
|
||||||
|
MatchBitsetSelector(lane->Arg(0), lane->Arg(1), bounds, (maximum + 1u) / 32u, indirect);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2993,6 +3155,47 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The EXEC predicate of the image access through use; empty for any other use.
|
||||||
|
Value AccessPredicate(const Use& use, bool sampler) const {
|
||||||
|
const auto& access = *use.user;
|
||||||
|
if (use.operand != (sampler ? 1u : 0u) ||
|
||||||
|
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
|
||||||
|
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
|
||||||
|
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
|
||||||
|
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
|
||||||
|
const auto mask = access.Arg(access.NumArgs() - 1u);
|
||||||
|
return mask.GetType() == Type::U1 ? mask : Value {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// A key no bound covers takes its maximum from a signed clamp to a constant, on the key or on
|
||||||
|
// the key plus a constant in an earlier-tracked sibling read of the same table. Negative keys
|
||||||
|
// and keys past that maximum read the first row.
|
||||||
|
uint32_t ClampedTableMaximum(Value key, const DescriptorSource& table,
|
||||||
|
const DescriptorSource::IndirectDescriptor& indirect) const {
|
||||||
|
uint32_t row = 0, offset = 0;
|
||||||
|
const auto clamped = [&](Value value) {
|
||||||
|
const auto* min = value.Resolve().TryInstruction();
|
||||||
|
if (min != nullptr && min->GetOpcode() == ValueOpcode::SMin32)
|
||||||
|
for (uint32_t arg = 0; arg < 2u; ++arg)
|
||||||
|
if (ImmediateU32(min->Arg(arg), row)) return min->Arg(arg ^ 1u);
|
||||||
|
return Value {};
|
||||||
|
};
|
||||||
|
if (!clamped(key).IsEmpty()) return row;
|
||||||
|
for (const auto& sibling: m_indirect_descriptors) {
|
||||||
|
const auto& read = *m_sources[sibling.source].indirect_descriptor;
|
||||||
|
const auto* add = clamped(sibling.key).Resolve().TryInstruction();
|
||||||
|
if (add == nullptr || add->GetOpcode() != ValueOpcode::IAdd32 ||
|
||||||
|
m_sources[read.table_source] != table ||
|
||||||
|
read.table_offset != indirect.table_offset ||
|
||||||
|
read.table_stride != indirect.table_stride ||
|
||||||
|
read.table_immediate != indirect.table_immediate) continue;
|
||||||
|
for (uint32_t arg = 0; arg < 2u; ++arg)
|
||||||
|
if (ImmediateU32(add->Arg(arg), offset) &&
|
||||||
|
EquivalentValue(m_program, add->Arg(arg ^ 1u), key)) return row;
|
||||||
|
}
|
||||||
|
return UINT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
bool TryMakeTableDescriptor(Inst& handle, const DescriptorSource& descriptor,
|
bool TryMakeTableDescriptor(Inst& handle, const DescriptorSource& descriptor,
|
||||||
IndirectDescriptorPlan& plan) {
|
IndirectDescriptorPlan& plan) {
|
||||||
const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource;
|
const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource;
|
||||||
@@ -3019,6 +3222,7 @@ private:
|
|||||||
} else if (!plan.table_indexed &&
|
} else if (!plan.table_indexed &&
|
||||||
MatchScalarMaterialKey(key, indirect, material_source,
|
MatchScalarMaterialKey(key, indirect, material_source,
|
||||||
!sampler && table_source.dword_count == 4u, sampler)) {
|
!sampler && table_source.dword_count == 4u, sampler)) {
|
||||||
|
indirect.selector->excluded = ExcludedKeyOnEntry(key, handle.Parent());
|
||||||
// Keep live descriptor payloads, but project descriptor-only SMEM onto
|
// Keep live descriptor payloads, but project descriptor-only SMEM onto
|
||||||
// the GPU key using the same plan as other indirect image reads.
|
// the GPU key using the same plan as other indirect image reads.
|
||||||
plan.retain_reads |= table_source.dword_count == 4u;
|
plan.retain_reads |= table_source.dword_count == 4u;
|
||||||
@@ -3080,36 +3284,42 @@ private:
|
|||||||
if (indirect.key_count.IsEmpty() &&
|
if (indirect.key_count.IsEmpty() &&
|
||||||
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, false) &&
|
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, false) &&
|
||||||
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, true) &&
|
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, true) &&
|
||||||
!MatchReversedImageRange(key, indirect)) {
|
!MatchReversedImageRange(key, indirect) && !MatchWorkgroupKey(key, indirect)) {
|
||||||
const auto maximum = SelectorMaximum(key);
|
auto maximum = SelectorMaximum(key);
|
||||||
if (maximum >= MaxIndirectDescriptorProbes) return false;
|
if (maximum >= MaxIndirectDescriptorProbes) {
|
||||||
indirect.key_count = Value(maximum + 1u);
|
// Each image access bounds the key on the lanes it executes.
|
||||||
|
maximum = 0u;
|
||||||
|
for (const auto& use: handle.Uses()) {
|
||||||
|
const auto active = AccessPredicate(use, false);
|
||||||
|
if (active.IsEmpty()) {
|
||||||
|
maximum = UINT32_MAX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maximum = std::max(maximum, SelectorMaximum(key, {}, 0, active));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maximum >= MaxIndirectDescriptorProbes)
|
||||||
|
maximum = ClampedTableMaximum(key, table_source, indirect);
|
||||||
|
if (maximum < MaxIndirectDescriptorProbes)
|
||||||
|
indirect.key_count = Value(maximum + 1u);
|
||||||
|
else if (!ProgramLoopCount(key, indirect))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if ((table_offset & 3u) != 0u) return false;
|
if ((table_offset & 3u) != 0u) return false;
|
||||||
} else {
|
} else {
|
||||||
// Keep the GPU selector and descriptor reads, and preserve the proven scalar
|
// Keep the GPU selector and descriptor reads, and preserve the proven scalar
|
||||||
// offset domain within the bounded image table.
|
// offset domain within the bounded image table.
|
||||||
if (sampler || plan.split_offsets) return false;
|
if (sampler || plan.split_offsets) return false;
|
||||||
const auto active = [&](const Use& use, bool sampler) -> Value {
|
|
||||||
const auto& access = *use.user;
|
|
||||||
if (use.operand != (sampler ? 1u : 0u) ||
|
|
||||||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
|
|
||||||
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
|
|
||||||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
|
|
||||||
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
|
|
||||||
const auto mask = access.Arg(access.NumArgs() - 1u);
|
|
||||||
return mask.GetType() == Type::U1 ? mask : Value {};
|
|
||||||
};
|
|
||||||
bool shared_selector = false;
|
bool shared_selector = false;
|
||||||
if (!plan.table_indexed) {
|
if (!plan.table_indexed) {
|
||||||
for (const auto& owner: m_indirect_descriptors) {
|
for (const auto& owner: m_indirect_descriptors) {
|
||||||
const auto& selected = *m_sources[owner.source].indirect_descriptor;
|
const auto& selected = *m_sources[owner.source].indirect_descriptor;
|
||||||
if (!selected.selector || !EquivalentValue(m_program, key, owner.key) ||
|
if (!selected.selector || !EquivalentValue(m_program, key, owner.key) ||
|
||||||
!std::ranges::all_of(handle.Uses(), [&](const Use& target) {
|
!std::ranges::all_of(handle.Uses(), [&](const Use& target) {
|
||||||
const auto target_active = active(target, false);
|
const auto target_active = AccessPredicate(target, false);
|
||||||
return !target_active.IsEmpty() &&
|
return !target_active.IsEmpty() &&
|
||||||
std::ranges::any_of(owner.handle->Uses(), [&](const Use& witness) {
|
std::ranges::any_of(owner.handle->Uses(), [&](const Use& witness) {
|
||||||
const auto owner_active = active(witness,
|
const auto owner_active = AccessPredicate(witness,
|
||||||
owner.handle->GetOpcode() == ValueOpcode::GetSamplerResource);
|
owner.handle->GetOpcode() == ValueOpcode::GetSamplerResource);
|
||||||
return !owner_active.IsEmpty() && AvailableBefore(*witness.user, *target.user) &&
|
return !owner_active.IsEmpty() && AvailableBefore(*witness.user, *target.user) &&
|
||||||
Implies(target_active, owner_active);
|
Implies(target_active, owner_active);
|
||||||
@@ -3430,6 +3640,42 @@ private:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bounds of an F32 value on the lanes where guard holds; NaN when unknown.
|
||||||
|
std::pair<float, float> FloatRange(Value value, Value guard, uint32_t depth = 0) const {
|
||||||
|
constexpr std::pair unknown {NAN, NAN};
|
||||||
|
value = ActiveValue(value, guard);
|
||||||
|
if (value.IsImmediate() && value.GetType() == Type::F32)
|
||||||
|
return {value.F32Value(), value.F32Value()};
|
||||||
|
const auto* inst = value.TryInstruction();
|
||||||
|
// Each multiply-add evaluates three operands; keep the search shallow.
|
||||||
|
if (depth > 12u || inst == nullptr) return unknown;
|
||||||
|
const auto range = [&](uint32_t arg) {
|
||||||
|
return FloatRange(inst->Arg(arg), guard, depth + 1u);
|
||||||
|
};
|
||||||
|
switch (inst->GetOpcode()) {
|
||||||
|
case ValueOpcode::BitCastF32U32:
|
||||||
|
case ValueOpcode::BitCastU32F32: return range(0);
|
||||||
|
// Clamping maps NaN to zero.
|
||||||
|
case ValueOpcode::FPSaturate32: return {0.0f, 1.0f};
|
||||||
|
case ValueOpcode::FPFloor32: {
|
||||||
|
const auto [low, high] = range(0);
|
||||||
|
return {std::floor(low), std::floor(high)};
|
||||||
|
}
|
||||||
|
case ValueOpcode::FPMad32: {
|
||||||
|
// The rounded multiply and add are monotonic, so the bounds lie at the corners.
|
||||||
|
const auto a = range(0), b = range(1), c = range(2);
|
||||||
|
if (!std::isfinite(a.first) || !std::isfinite(a.second) ||
|
||||||
|
!std::isfinite(b.first) || !std::isfinite(b.second)) return unknown;
|
||||||
|
const auto [low, high] = std::minmax({a.first * b.first, a.first * b.second,
|
||||||
|
a.second * b.first, a.second * b.second});
|
||||||
|
const std::pair bounds {low + c.first, high + c.second};
|
||||||
|
if (!std::isfinite(bounds.first) || !std::isfinite(bounds.second)) return unknown;
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
default: return unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t SelectorMaximum(Value value, SelectorBounds bounds = {}, uint32_t depth = 0,
|
uint32_t SelectorMaximum(Value value, SelectorBounds bounds = {}, uint32_t depth = 0,
|
||||||
Value guard = {}) const {
|
Value guard = {}) const {
|
||||||
if (depth > 32u) return UINT32_MAX;
|
if (depth > 32u) return UINT32_MAX;
|
||||||
@@ -3445,9 +3691,13 @@ private:
|
|||||||
switch (inst->GetOpcode()) {
|
switch (inst->GetOpcode()) {
|
||||||
case ValueOpcode::ReadFirstLane:
|
case ValueOpcode::ReadFirstLane:
|
||||||
return SelectedSelectorDomain(*inst, guard).maximum;
|
return SelectedSelectorDomain(*inst, guard).maximum;
|
||||||
case ValueOpcode::ReadLane:
|
case ValueOpcode::ReadLane: {
|
||||||
// ReadLane ignores EXEC; only context-free bounds on every source lane apply.
|
// ReadLane ignores EXEC; only context-free bounds on every source lane apply,
|
||||||
return PossibleU32Bits(inst->Arg(0));
|
// unless guard proves a waterfall key equal to the executing lanes' local value.
|
||||||
|
const auto local = EqualLocalKey(guard, value);
|
||||||
|
return local.IsEmpty() ? PossibleU32Bits(inst->Arg(0))
|
||||||
|
: SelectorMaximum(local, bounds, depth + 1u, guard);
|
||||||
|
}
|
||||||
case ValueOpcode::Phi: {
|
case ValueOpcode::Phi: {
|
||||||
const auto initial = InitialCandidateMask(value);
|
const auto initial = InitialCandidateMask(value);
|
||||||
if (!initial.IsEmpty()) return SelectorMaximum(initial, bounds, depth + 1u, guard);
|
if (!initial.IsEmpty()) return SelectorMaximum(initial, bounds, depth + 1u, guard);
|
||||||
@@ -3474,6 +3724,18 @@ private:
|
|||||||
NonzeroOnEntry(inst->Arg(0), inst->Parent()) ? 31u : UINT32_MAX;
|
NonzeroOnEntry(inst->Arg(0), inst->Parent()) ? 31u : UINT32_MAX;
|
||||||
case ValueOpcode::UMin32:
|
case ValueOpcode::UMin32:
|
||||||
case ValueOpcode::BitwiseAnd32: return std::min(maximum(0), maximum(1));
|
case ValueOpcode::BitwiseAnd32: return std::min(maximum(0), maximum(1));
|
||||||
|
case ValueOpcode::SMin32: {
|
||||||
|
// Signed and unsigned minimums agree on nonnegative operands.
|
||||||
|
const auto left = maximum(0), right = maximum(1);
|
||||||
|
if (std::max(left, right) <= INT32_MAX) return std::min(left, right);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ValueOpcode::ConvertS32F32: {
|
||||||
|
// The conversion truncates toward zero.
|
||||||
|
const auto [low, high] = FloatRange(inst->Arg(0), guard);
|
||||||
|
if (low >= 0.0f && high < 0x1p31f) return static_cast<uint32_t>(high);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ValueOpcode::SelectU32:
|
case ValueOpcode::SelectU32:
|
||||||
if (!guard.IsEmpty() && Implies(guard, inst->Arg(0))) return maximum(1);
|
if (!guard.IsEmpty() && Implies(guard, inst->Arg(0))) return maximum(1);
|
||||||
return SelectorPredicateFalse(inst->Arg(0), bounds, depth + 1u, guard)
|
return SelectorPredicateFalse(inst->Arg(0), bounds, depth + 1u, guard)
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
|
|||||||
case StageInputKind::GlobalInvocationId:
|
case StageInputKind::GlobalInvocationId:
|
||||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||||
break;
|
break;
|
||||||
|
case StageInputKind::BaseVertex: AddInput(info, kind, 0, 1, "gl_BaseVertex"); break;
|
||||||
|
case StageInputKind::BaseInstance: AddInput(info, kind, 0, 1, "gl_BaseInstance"); break;
|
||||||
case StageInputKind::PackedAncillary:
|
case StageInputKind::PackedAncillary:
|
||||||
case StageInputKind::Parameter: break;
|
case StageInputKind::Parameter: break;
|
||||||
}
|
}
|
||||||
@@ -366,7 +368,6 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
|||||||
info.function_lds |= kind == ResourceKind::Lds &&
|
info.function_lds |= kind == ResourceKind::Lds &&
|
||||||
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
||||||
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
||||||
info.subgroup_ballot = true;
|
|
||||||
info.subgroup_shuffle = true;
|
info.subgroup_shuffle = true;
|
||||||
info.subgroup_local_invocation_id = true;
|
info.subgroup_local_invocation_id = true;
|
||||||
}
|
}
|
||||||
@@ -445,6 +446,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
|||||||
case StageInputKind::InstanceIndex:
|
case StageInputKind::InstanceIndex:
|
||||||
case StageInputKind::FrontFacing:
|
case StageInputKind::FrontFacing:
|
||||||
case StageInputKind::LocalInvocationIndex:
|
case StageInputKind::LocalInvocationIndex:
|
||||||
|
case StageInputKind::BaseVertex:
|
||||||
|
case StageInputKind::BaseInstance:
|
||||||
if (component != 0u) {
|
if (component != 0u) {
|
||||||
return Fail("typed scalar builtin component is out of range");
|
return Fail("typed scalar builtin component is out of range");
|
||||||
}
|
}
|
||||||
@@ -517,7 +520,9 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
|||||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||||
break;
|
break;
|
||||||
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
|
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
|
||||||
case ValueOpcode::SubgroupUMinU32: info.subgroup_arithmetic = true; break;
|
case ValueOpcode::LiveMask: info.subgroup_ballot = info.helper_invocation = true; break;
|
||||||
|
case ValueOpcode::SubgroupUMinU32:
|
||||||
|
case ValueOpcode::SubgroupUMaxU32: info.subgroup_arithmetic = true; break;
|
||||||
case ValueOpcode::DppMoveU32:
|
case ValueOpcode::DppMoveU32:
|
||||||
case ValueOpcode::ReadFirstLane:
|
case ValueOpcode::ReadFirstLane:
|
||||||
case ValueOpcode::ReadLane: {
|
case ValueOpcode::ReadLane: {
|
||||||
@@ -576,7 +581,7 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
|
|||||||
info.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
|
info.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
|
||||||
info.uses_dispatch_threads = info.uses_swizzle = false;
|
info.uses_dispatch_threads = info.uses_swizzle = false;
|
||||||
info.bvh = info.subgroup_ballot = info.subgroup_vote = info.subgroup_barrier =
|
info.bvh = info.subgroup_ballot = info.subgroup_vote = info.subgroup_barrier =
|
||||||
info.subgroup_shuffle = info.subgroup_arithmetic =
|
info.subgroup_shuffle = info.subgroup_arithmetic = info.helper_invocation =
|
||||||
info.subgroup_local_invocation_id = info.compute_derivatives = info.image_gather_extended =
|
info.subgroup_local_invocation_id = info.compute_derivatives = info.image_gather_extended =
|
||||||
info.function_lds = info.function_scratch = info.pixel_valid_mask = info.buffer_int64_atomics =
|
info.function_lds = info.function_scratch = info.pixel_valid_mask = info.buffer_int64_atomics =
|
||||||
info.buffer_u8 = info.buffer_u16 = info.shared_int64_atomics = info.coherent_buffers = info.float64 = false;
|
info.buffer_u8 = info.buffer_u16 = info.shared_int64_atomics = info.coherent_buffers = info.float64 = false;
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
|
|||||||
case ValueOpcode::BitCastU32F32:
|
case ValueOpcode::BitCastU32F32:
|
||||||
case ValueOpcode::BitCastF32U32:
|
case ValueOpcode::BitCastF32U32:
|
||||||
case ValueOpcode::ConvertU32F32:
|
case ValueOpcode::ConvertU32F32:
|
||||||
|
case ValueOpcode::ConvertS32F32:
|
||||||
case ValueOpcode::ConvertF32U32:
|
case ValueOpcode::ConvertF32U32:
|
||||||
case ValueOpcode::CompositeConstructU64:
|
case ValueOpcode::CompositeConstructU64:
|
||||||
case ValueOpcode::CompositeExtractU64:
|
case ValueOpcode::CompositeExtractU64:
|
||||||
@@ -147,8 +148,8 @@ bool ImmediateBits(Value value, uint64_t& bits) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scratch layout in bytes: per descriptor source its eight DWORDs and a known DWORD, one
|
// Scratch layout in bytes: per descriptor source its eight DWORDs and a known DWORD, one
|
||||||
// activity byte per source, then one qword per walked value (all ones when unknown). The
|
// activity byte per source, then per image, then one qword per walked value (all ones when
|
||||||
// walker's value slots, captured addresses, reachability and states follow.
|
// unknown). The walker's value slots, captured addresses, reachability and states follow.
|
||||||
constexpr uint32_t DescriptorStride = 9u * sizeof(uint32_t);
|
constexpr uint32_t DescriptorStride = 9u * sizeof(uint32_t);
|
||||||
|
|
||||||
uint32_t ActiveOffset(const ResourcePlan& plan) {
|
uint32_t ActiveOffset(const ResourcePlan& plan) {
|
||||||
@@ -156,7 +157,8 @@ uint32_t ActiveOffset(const ResourcePlan& plan) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t OutputOffset(const ResourcePlan& plan) {
|
uint32_t OutputOffset(const ResourcePlan& plan) {
|
||||||
return (ActiveOffset(plan) + static_cast<uint32_t>(plan.descriptor_sources.size()) + 7u) & ~7u;
|
const auto active = plan.descriptor_sources.size() + plan.info.images.size();
|
||||||
|
return (ActiveOffset(plan) + static_cast<uint32_t>(active) + 7u) & ~7u;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t* Scratch(const ResourcePlan& plan) {
|
const uint8_t* Scratch(const ResourcePlan& plan) {
|
||||||
@@ -1038,6 +1040,23 @@ void WalkerCompiler::EmitStrict(const Node& node) {
|
|||||||
c.L(done);
|
c.L(done);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ValueOpcode::ConvertS32F32: {
|
||||||
|
// The conversion saturates and maps NaN to zero. cvttss2si's out-of-range result,
|
||||||
|
// INT32_MIN, is already right for negative overflow.
|
||||||
|
Xbyak::Label done;
|
||||||
|
c.movd(c.xmm0, c.eax);
|
||||||
|
c.xor_(c.eax, c.eax);
|
||||||
|
c.ucomiss(c.xmm0, c.xmm0);
|
||||||
|
c.jp(done, Near);
|
||||||
|
c.mov(c.ecx, 0x4f000000u);
|
||||||
|
c.movd(c.xmm1, c.ecx);
|
||||||
|
c.mov(c.eax, INT32_MAX);
|
||||||
|
c.ucomiss(c.xmm0, c.xmm1);
|
||||||
|
c.jae(done, Near);
|
||||||
|
c.cvttss2si(c.eax, c.xmm0);
|
||||||
|
c.L(done);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ValueOpcode::FPMul32:
|
case ValueOpcode::FPMul32:
|
||||||
c.movd(c.xmm0, c.eax);
|
c.movd(c.xmm0, c.eax);
|
||||||
c.movd(c.xmm1, c.ecx);
|
c.movd(c.xmm1, c.ecx);
|
||||||
@@ -1272,11 +1291,14 @@ void WalkerCompiler::Compile(std::span<const Value> values) {
|
|||||||
for (const auto slot: blocks[block].srt_reads) flat(slot);
|
for (const auto slot: blocks[block].srt_reads) flat(slot);
|
||||||
c.L(skip);
|
c.L(skip);
|
||||||
}
|
}
|
||||||
// A source no block lists is always active.
|
// A source or image no block lists is always active.
|
||||||
for (uint32_t source = 0; source < sources.size(); ++source) {
|
for (uint32_t index = 0; index < sources.size() + m_plan.info.images.size(); ++index) {
|
||||||
|
const bool image = index >= sources.size();
|
||||||
|
const uint32_t id = image ? index - static_cast<uint32_t>(sources.size()) : index;
|
||||||
bool listed = false;
|
bool listed = false;
|
||||||
for (uint32_t block = 0; block < blocks.size(); ++block) {
|
for (uint32_t block = 0; block < blocks.size(); ++block) {
|
||||||
if (std::ranges::find(blocks[block].sources, source) == blocks[block].sources.end()) continue;
|
const auto& list = image ? blocks[block].images : blocks[block].sources;
|
||||||
|
if (std::ranges::find(list, id) == list.end()) continue;
|
||||||
if (listed) {
|
if (listed) {
|
||||||
c.or_(c.al, c.byte[c.rdx + m_reach + block]);
|
c.or_(c.al, c.byte[c.rdx + m_reach + block]);
|
||||||
} else {
|
} else {
|
||||||
@@ -1284,7 +1306,7 @@ void WalkerCompiler::Compile(std::span<const Value> values) {
|
|||||||
}
|
}
|
||||||
listed = true;
|
listed = true;
|
||||||
}
|
}
|
||||||
const auto active = c.byte[c.rdx + ActiveOffset(m_plan) + source];
|
const auto active = c.byte[c.rdx + ActiveOffset(m_plan) + index];
|
||||||
if (listed) {
|
if (listed) {
|
||||||
c.mov(active, c.al);
|
c.mov(active, c.al);
|
||||||
} else {
|
} else {
|
||||||
@@ -1391,6 +1413,10 @@ bool WalkedActive(const ResourcePlan& plan, uint32_t source) {
|
|||||||
return Scratch(plan)[ActiveOffset(plan) + source] != 0u;
|
return Scratch(plan)[ActiveOffset(plan) + source] != 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WalkedImageActive(const ResourcePlan& plan, uint32_t image) {
|
||||||
|
return Scratch(plan)[ActiveOffset(plan) + plan.descriptor_sources.size() + image] != 0u;
|
||||||
|
}
|
||||||
|
|
||||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type) {
|
bool ValidateRuntimeValue(const ResourcePlan& program, Value value, RuntimeValueType type) {
|
||||||
return RuntimeValidator(program, type).Run(value);
|
return RuntimeValidator(program, type).Run(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ bool WalkSrt(const ResourcePlan& plan, const SrtRuntime& runtime, std::vector<ui
|
|||||||
bool WalkedValue(const ResourcePlan& plan, Value value, uint32_t& result);
|
bool WalkedValue(const ResourcePlan& plan, Value value, uint32_t& result);
|
||||||
bool WalkedDescriptor(const ResourcePlan& plan, uint32_t source, DescriptorValue& result);
|
bool WalkedDescriptor(const ResourcePlan& plan, uint32_t source, DescriptorValue& result);
|
||||||
bool WalkedActive(const ResourcePlan& plan, uint32_t source);
|
bool WalkedActive(const ResourcePlan& plan, uint32_t source);
|
||||||
|
bool WalkedImageActive(const ResourcePlan& plan, uint32_t image);
|
||||||
// Length of the guest load at rip if it belongs to the walker running on this thread, else 0.
|
// Length of the guest load at rip if it belongs to the walker running on this thread, else 0.
|
||||||
// The exception handler completes such a load as a zero read.
|
// The exception handler completes such a load as a zero read.
|
||||||
uint32_t SrtWalkerLoadLength(uint64_t rip);
|
uint32_t SrtWalkerLoadLength(uint64_t rip);
|
||||||
|
|||||||
@@ -348,14 +348,20 @@ void VisitInstruction(Pass& pass, Block* block, Inst& inst) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void RewriteToSsa(const BlockList& blocks) {
|
void RewriteToSsa(const BlockList& blocks) {
|
||||||
Pass pass;
|
Pass pass;
|
||||||
|
std::unordered_map<Block*, size_t> filled_predecessors;
|
||||||
for (auto* block: blocks) {
|
for (auto* block: blocks) {
|
||||||
|
if (block->ImmPredecessors().empty()) {
|
||||||
|
pass.Seal(block);
|
||||||
|
}
|
||||||
for (auto& inst: *block) {
|
for (auto& inst: *block) {
|
||||||
VisitInstruction(pass, block, inst);
|
VisitInstruction(pass, block, inst);
|
||||||
}
|
}
|
||||||
}
|
for (auto* successor: block->ImmSuccessors()) {
|
||||||
for (auto* block: blocks) {
|
if (++filled_predecessors[successor] == successor->ImmPredecessors().size()) {
|
||||||
pass.Seal(block);
|
pass.Seal(successor);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -422,6 +422,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
|||||||
key.push_back(info.lds_size_dwords);
|
key.push_back(info.lds_size_dwords);
|
||||||
key.push_back(info.pa_cl_vs_out_cntl);
|
key.push_back(info.pa_cl_vs_out_cntl);
|
||||||
key.push_back(info.user_clip_planes);
|
key.push_back(info.user_clip_planes);
|
||||||
|
key.push_back(info.indirect_offsets.vertex | (uint32_t {info.indirect_offsets.instance} << 8u));
|
||||||
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
key.push_back(static_cast<uint32_t>(info.clip_space.enabled));
|
||||||
if (info.clip_space.enabled) {
|
if (info.clip_space.enabled) {
|
||||||
for (const float value: info.clip_space.scale) {
|
for (const float value: info.clip_space.scale) {
|
||||||
@@ -490,7 +491,9 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
|||||||
key.push_back(static_cast<uint32_t>(info.object_primitive_id));
|
key.push_back(static_cast<uint32_t>(info.object_primitive_id));
|
||||||
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
|
key.push_back(static_cast<uint32_t>(info.alpha_blend_source));
|
||||||
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
|
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
|
||||||
key.push_back(info.target_packed_unorm_mask | (uint32_t {info.target_round_mode} << 8u));
|
key.push_back(info.target_packed_unorm_mask | (uint32_t {info.target_round_mode} << 8u) |
|
||||||
|
(uint32_t {info.target_uint_mask} << 16u) |
|
||||||
|
(uint32_t {info.target_sint_mask} << 24u));
|
||||||
const auto mask = info.target_shader_mask;
|
const auto mask = info.target_shader_mask;
|
||||||
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
|
key.push_back((mask | (mask >> 1u) | (mask >> 2u) | (mask >> 3u)) & 0x11111111u);
|
||||||
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
||||||
|
|||||||
@@ -124,6 +124,18 @@ struct ShaderTessellationInputInfo {
|
|||||||
uint32_t output_topology = 0;
|
uint32_t output_topology = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// GS user-data slots an indirect draw packet writes its record's base vertex and first instance
|
||||||
|
// to. Like the hardware index VGPRs, the vertex shader's indices exclude both bases once either
|
||||||
|
// slot is set.
|
||||||
|
struct ShaderIndirectOffsets {
|
||||||
|
static constexpr uint8_t None = UINT8_MAX;
|
||||||
|
|
||||||
|
uint8_t vertex = None;
|
||||||
|
uint8_t instance = None;
|
||||||
|
|
||||||
|
bool operator==(const ShaderIndirectOffsets&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct ShaderVertexInputInfo {
|
struct ShaderVertexInputInfo {
|
||||||
static constexpr int RES_MAX = 32;
|
static constexpr int RES_MAX = 32;
|
||||||
|
|
||||||
@@ -146,6 +158,7 @@ struct ShaderVertexInputInfo {
|
|||||||
ShaderClipSpaceTransform clip_space;
|
ShaderClipSpaceTransform clip_space;
|
||||||
ShaderMeshInputInfo mesh;
|
ShaderMeshInputInfo mesh;
|
||||||
ShaderTessellationInputInfo tess;
|
ShaderTessellationInputInfo tess;
|
||||||
|
ShaderIndirectOffsets indirect_offsets;
|
||||||
bool fetch_external = false;
|
bool fetch_external = false;
|
||||||
bool fetch_embedded = false;
|
bool fetch_embedded = false;
|
||||||
};
|
};
|
||||||
@@ -181,6 +194,9 @@ struct ShaderPixelInputInfo {
|
|||||||
uint32_t target_shader_mask = UINT32_MAX;
|
uint32_t target_shader_mask = UINT32_MAX;
|
||||||
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
||||||
uint8_t target_packed_unorm_mask = 0;
|
uint8_t target_packed_unorm_mask = 0;
|
||||||
|
// Exports to UINT and SINT targets, which take integer outputs of the same signedness.
|
||||||
|
uint8_t target_uint_mask = 0;
|
||||||
|
uint8_t target_sint_mask = 0;
|
||||||
// CB round mode per converted export: zero adds half an LSB, one truncates.
|
// CB round mode per converted export: zero adds half an LSB, one truncates.
|
||||||
uint8_t target_round_mode = 0;
|
uint8_t target_round_mode = 0;
|
||||||
uint32_t scratch_size_dwords = 0;
|
uint32_t scratch_size_dwords = 0;
|
||||||
|
|||||||
+505
-1103
File diff suppressed because it is too large.
Load diff
+12
-4
@@ -26,9 +26,10 @@ struct DebugStats {
|
|||||||
uint64_t flexible_total = 0;
|
uint64_t flexible_total = 0;
|
||||||
uint64_t flexible_used = 0;
|
uint64_t flexible_used = 0;
|
||||||
uint64_t pool_committed = 0;
|
uint64_t pool_committed = 0;
|
||||||
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
uint64_t cpu_page_entries = 0; // 2 MiB page-table entries in use.
|
||||||
uint64_t gpu_page_entries = 0;
|
uint64_t gpu_page_entries = 0;
|
||||||
uint64_t page_entries_total = 0; // Per side.
|
uint64_t cpu_page_entries_total = 0;
|
||||||
|
uint64_t gpu_page_entries_total = 0;
|
||||||
};
|
};
|
||||||
[[nodiscard]] DebugStats GetDebugStats();
|
[[nodiscard]] DebugStats GetDebugStats();
|
||||||
|
|
||||||
@@ -125,7 +126,6 @@ static_assert(sizeof(KernelMemoryPoolBlockStats) == 16,
|
|||||||
"KernelMemoryPoolBlockStats struct size is incorrect");
|
"KernelMemoryPoolBlockStats struct size is incorrect");
|
||||||
|
|
||||||
void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func);
|
void RegisterCallbacks(callback_func_t alloc_func, callback_func_t free_func);
|
||||||
void SetFlexibleMemorySize(uint64_t size);
|
|
||||||
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
|
int AllocateDirectMemory(int64_t search_start, int64_t search_end, size_t size, size_t alignment,
|
||||||
int memory_type, int64_t* phys_addr_out, bool automatic = false);
|
int memory_type, int64_t* phys_addr_out, bool automatic = false);
|
||||||
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
int MapAutomaticMemory(uint64_t vaddr, size_t size, int type, int prot);
|
||||||
@@ -197,6 +197,9 @@ int KYTY_SYSV_ABI KernelMemoryPoolGetBlockStats(KernelMemoryPoolBlockStats* outp
|
|||||||
|
|
||||||
uint64_t AllocateProgramMemory(uint64_t search_addr, uint64_t size,
|
uint64_t AllocateProgramMemory(uint64_t search_addr, uint64_t size,
|
||||||
Common::VirtualMemory::Mode mode, const char* name);
|
Common::VirtualMemory::Mode mode, const char* name);
|
||||||
|
// Loaded modules' segments use flexible memory without taking its backing.
|
||||||
|
void ChargeFlexibleMemory(uint64_t size);
|
||||||
|
void ReleaseFlexibleMemory(uint64_t size);
|
||||||
void SetProgramMemoryProtection(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode);
|
void SetProgramMemoryProtection(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode);
|
||||||
uint64_t AllocateRuntimeMemory(uint64_t search_addr, uint64_t size,
|
uint64_t AllocateRuntimeMemory(uint64_t search_addr, uint64_t size,
|
||||||
Common::VirtualMemory::Mode mode, const char* name,
|
Common::VirtualMemory::Mode mode, const char* name,
|
||||||
@@ -222,6 +225,11 @@ bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size);
|
|||||||
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
|
bool TestGuestAddressRangeIsOwned(uint64_t vaddr, uint64_t size);
|
||||||
bool TestGuestBackingOutsideAddressSpace();
|
bool TestGuestBackingOutsideAddressSpace();
|
||||||
uint64_t TestGuestBackingSize();
|
uint64_t TestGuestBackingSize();
|
||||||
|
uint64_t TestFlexibleBackingHighWater();
|
||||||
|
uint64_t TestGuestFreeRangeVisits();
|
||||||
|
uint64_t TestDirectMappingCount();
|
||||||
|
uint64_t TestFlexibleMappingCount();
|
||||||
|
uint64_t TestBackingBytesZeroed();
|
||||||
bool TestGuestFreeRangeBounds();
|
bool TestGuestFreeRangeBounds();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,9 @@ public:
|
|||||||
}
|
}
|
||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
std::memset(m_backing_base + backing_offset, 0, size);
|
std::memset(m_backing_base + backing_offset, 0, size);
|
||||||
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
|
g_test_backing_bytes_zeroed += size;
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,6 +290,18 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ProtectViews(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||||
|
std::shared_lock lock(m_mutex);
|
||||||
|
bool ok = true;
|
||||||
|
ForEachSparseRange(
|
||||||
|
m_maps, vaddr, size,
|
||||||
|
[&](const auto&, uint64_t start, uint64_t bytes) {
|
||||||
|
ok = ok && VirtualMemory::Protect(start, bytes, mode);
|
||||||
|
},
|
||||||
|
[&](uint64_t, uint64_t) { ok = false; });
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
bool MapFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
bool MapFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
||||||
VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) {
|
VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) {
|
||||||
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
||||||
@@ -324,15 +339,11 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Unmap(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
bool Unmap(uint64_t vaddr, uint64_t size) {
|
||||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto end = vaddr + size;
|
const auto end = vaddr + size;
|
||||||
std::vector<MapEntry> targets;
|
std::vector<MapEntry> targets;
|
||||||
{
|
{
|
||||||
@@ -350,42 +361,36 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<MapEntry> removed;
|
size_t removed = 0;
|
||||||
removed.reserve(targets.size());
|
auto rollback = [&] {
|
||||||
|
while (removed != 0) {
|
||||||
|
const auto& previous = targets[--removed];
|
||||||
|
EXIT_IF(!MapFixed(previous.vaddr, previous.size, previous.backing_offset,
|
||||||
|
previous.mode));
|
||||||
|
}
|
||||||
|
};
|
||||||
for (const auto& entry: targets) {
|
for (const auto& entry: targets) {
|
||||||
bool part_placeholder_preserved = false;
|
if (!UnmapOne(entry.vaddr, entry.size)) {
|
||||||
if (!UnmapOne(entry.vaddr, entry.size, &part_placeholder_preserved)) {
|
rollback();
|
||||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
|
||||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
|
||||||
}
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = false;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EXIT_IF(!part_placeholder_preserved);
|
++removed;
|
||||||
removed.push_back(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
|
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
|
||||||
// records this contiguous guest span as one free range, so normalize fragmented backing
|
// records this contiguous guest span as one free range, so normalize fragmented backing
|
||||||
// views into one placeholder before publishing it there.
|
// views into one placeholder before publishing it there.
|
||||||
if (removed.size() > 1 && !CoalescePlaceholders(vaddr, size)) {
|
if (removed > 1 && !CoalescePlaceholders(vaddr, size)) {
|
||||||
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
rollback();
|
||||||
EXIT_IF(!MapFixed(it->vaddr, it->size, it->backing_offset, it->mode));
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UnmapOne(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
bool UnmapOne(uint64_t vaddr, uint64_t size) {
|
||||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
if (g_test_backing_store_unmaps_before_failure == 0) {
|
if (g_test_backing_store_unmaps_before_failure == 0) {
|
||||||
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
||||||
@@ -428,9 +433,7 @@ public:
|
|||||||
m_maps.emplace(old.vaddr, old);
|
m_maps.emplace(old.vaddr, old);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,9 +454,6 @@ public:
|
|||||||
ok;
|
ok;
|
||||||
}
|
}
|
||||||
if (ok) {
|
if (ok) {
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,9 +481,6 @@ public:
|
|||||||
std::lock_guard lock(m_mutex);
|
std::lock_guard lock(m_mutex);
|
||||||
RemoveMappingsUnlocked(vaddr, size);
|
RemoveMappingsUnlocked(vaddr, size);
|
||||||
|
|
||||||
if (placeholder_preserved != nullptr) {
|
|
||||||
*placeholder_preserved = true;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -634,8 +631,6 @@ private:
|
|||||||
|
|
||||||
HANDLE m_handle = nullptr;
|
HANDLE m_handle = nullptr;
|
||||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
[[nodiscard]] static uint64_t GetMappableTestSize() { return 0x4000; }
|
|
||||||
|
|
||||||
static int GetProtect(VirtualMemory::Mode mode) {
|
static int GetProtect(VirtualMemory::Mode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case VirtualMemory::Mode::Read: return PROT_READ;
|
case VirtualMemory::Mode::Read: return PROT_READ;
|
||||||
@@ -760,7 +755,7 @@ public:
|
|||||||
if (size == 0 || UINT64_MAX - vaddr < size) {
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
||||||
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
||||||
});
|
});
|
||||||
@@ -864,11 +859,7 @@ public:
|
|||||||
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool placeholder_preserved = false;
|
if (!m_backing->Unmap(vaddr, size)) {
|
||||||
if (!m_backing->Unmap(vaddr, size, &placeholder_preserved)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!placeholder_preserved) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
||||||
@@ -876,12 +867,13 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reservations live in VirtualRanges; these free-range checks only read the host placeholders.
|
||||||
uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
alignment = std::max<uint64_t>(alignment != 0 ? alignment : PageSize(), PageSize());
|
alignment = std::max<uint64_t>(alignment != 0 ? alignment : PageSize(), PageSize());
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
if (search_addr == 0) {
|
if (search_addr == 0) {
|
||||||
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
||||||
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
||||||
@@ -898,7 +890,7 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
alignment = std::max<uint64_t>(alignment, PageSize());
|
alignment = std::max<uint64_t>(alignment, PageSize());
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return FindFreeAlignedUnlocked(search_start, search_end, size, alignment);
|
return FindFreeAlignedUnlocked(search_start, search_end, size, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,19 +898,19 @@ public:
|
|||||||
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return FindContaining(vaddr, size) != m_free.end();
|
return FindContaining(vaddr, size) != m_free.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
bool TestContainsFree(uint64_t vaddr, uint64_t size) {
|
bool TestContainsFree(uint64_t vaddr, uint64_t size) {
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return FindContaining(vaddr, size) != m_free.end();
|
return FindContaining(vaddr, size) != m_free.end();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool ReleaseFree(uint64_t vaddr, uint64_t size) {
|
bool ReleaseFree(uint64_t vaddr, uint64_t size) {
|
||||||
std::lock_guard lock(m_mutex);
|
std::shared_lock lock(m_mutex);
|
||||||
return FindContaining(vaddr, size) != m_free.end();
|
return FindContaining(vaddr, size) != m_free.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1041,10 +1033,6 @@ private:
|
|||||||
return (value & (PageSize() - 1u)) == 0;
|
return (value & (PageSize() - 1u)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool IsGranularityAligned(uint64_t value) const {
|
|
||||||
return (value & (m_granularity - 1u)) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProtectMappedUnlocked(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
bool ProtectMappedUnlocked(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
||||||
const auto end = vaddr + size;
|
const auto end = vaddr + size;
|
||||||
auto it = m_mapped.upper_bound(vaddr);
|
auto it = m_mapped.upper_bound(vaddr);
|
||||||
@@ -1055,8 +1043,20 @@ private:
|
|||||||
const auto& region = it->second;
|
const auto& region = it->second;
|
||||||
const auto range_start = std::max(vaddr, region.vaddr);
|
const auto range_start = std::max(vaddr, region.vaddr);
|
||||||
const auto range_end = std::min(end, region.vaddr + region.size);
|
const auto range_end = std::min(end, region.vaddr + region.size);
|
||||||
if (range_start < range_end &&
|
if (range_start >= range_end) {
|
||||||
!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
continue;
|
||||||
|
}
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||||
|
// Rollback can split a backing view without changing the owner interval.
|
||||||
|
// Windows protection calls must stay within each actual view.
|
||||||
|
if (region.type == MappingType::Backing) {
|
||||||
|
if (!m_backing->ProtectViews(range_start, range_end - range_start, mode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1312,12 +1312,21 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t FindFreeAlignedUnlocked(uint64_t search_start, uint64_t search_end, uint64_t size,
|
uint64_t FindFreeAlignedUnlocked(uint64_t search_start, uint64_t search_end, uint64_t size,
|
||||||
uint64_t alignment) {
|
uint64_t alignment) const {
|
||||||
if (search_start >= search_end || size > search_end - search_start) {
|
if (search_start >= search_end || size > search_end - search_start) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
for (const auto& [base, range_size]: m_free) {
|
// Include the predecessor because search_start can lie inside a free range.
|
||||||
const auto candidate = AlignUp(std::max(base, search_start), alignment);
|
auto it = m_free.upper_bound(search_start);
|
||||||
|
if (it != m_free.begin()) {
|
||||||
|
--it;
|
||||||
|
}
|
||||||
|
for (; it != m_free.end() && it->first < search_end; ++it) {
|
||||||
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
|
g_test_free_range_visits.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
#endif
|
||||||
|
const auto& [base, range_size] = *it;
|
||||||
|
const auto candidate = AlignUp(std::max(base, search_start), alignment);
|
||||||
if (candidate < search_end && size <= search_end - candidate &&
|
if (candidate < search_end && size <= search_end - candidate &&
|
||||||
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
||||||
return candidate;
|
return candidate;
|
||||||
@@ -1331,9 +1340,8 @@ private:
|
|||||||
if (next == m_free.begin()) {
|
if (next == m_free.begin()) {
|
||||||
return m_free.end();
|
return m_free.end();
|
||||||
}
|
}
|
||||||
auto it = std::prev(next);
|
auto it = std::prev(next);
|
||||||
auto end = it->first + it->second;
|
return GuestFreeRangeContains(it->first, it->second, vaddr, size) ? it : m_free.end();
|
||||||
return (vaddr >= it->first && vaddr + size <= end ? it : m_free.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SplitUnlocked(std::map<uint64_t, uint64_t>::iterator it, uint64_t vaddr, uint64_t size) {
|
bool SplitUnlocked(std::map<uint64_t, uint64_t>::iterator it, uint64_t vaddr, uint64_t size) {
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ if(APPLE)
|
|||||||
install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS")
|
install(TARGETS kyty_emulator DESTINATION "KytyPS5.app/Contents/MacOS")
|
||||||
install(TARGETS kyty_updater DESTINATION "KytyPS5.app/Contents/MacOS")
|
install(TARGETS kyty_updater DESTINATION "KytyPS5.app/Contents/MacOS")
|
||||||
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION "KytyPS5.app/Contents/Resources/assets/sounds")
|
install(FILES "${KYTY_TROPHY_UNLOCK_SOUND}" DESTINATION "KytyPS5.app/Contents/Resources/assets/sounds")
|
||||||
|
install(FILES ${KYTY_NOTO_SANS_FONTS} DESTINATION "KytyPS5.app/Contents/Resources/assets/fonts")
|
||||||
install(DIRECTORY "${CMAKE_SOURCE_DIR}/assets/cursors" DESTINATION "KytyPS5.app/Contents/Resources/assets")
|
install(DIRECTORY "${CMAKE_SOURCE_DIR}/assets/cursors" DESTINATION "KytyPS5.app/Contents/Resources/assets")
|
||||||
# Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle.
|
# Deploy and ad-hoc sign the Qt frameworks and plugins in the bundle.
|
||||||
find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH)
|
find_program(KYTY_MACDEPLOYQT NAMES macdeployqt PATHS "${Qt6_DIR}/../../../bin" NO_DEFAULT_PATH)
|
||||||
|
|||||||
@@ -115,7 +115,17 @@
|
|||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="comboBox_screen_resolution">
|
<widget class="QComboBox" name="comboBox_screen_resolution">
|
||||||
<property name="toolTip"><string>Window resolution</string></property>
|
<property name="toolTip"><string>Window resolution as WIDTHxHEIGHT; pick a preset or type a custom size. Non-16:9 sizes such as 21:9 show the game with black bars unless Stretch to window is on.</string></property>
|
||||||
|
<property name="editable"><bool>true</bool></property>
|
||||||
|
<property name="insertPolicy"><enum>QComboBox::NoInsert</enum></property>
|
||||||
|
<item><property name="text"><string notr="true">1280x720</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">1920x1080</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">2560x1440</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">3840x2160</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">2560x1080</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">3440x1440</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">3840x1600</string></property></item>
|
||||||
|
<item><property name="text"><string notr="true">5120x2160</string></property></item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
@@ -256,6 +266,12 @@
|
|||||||
<property name="text"><string>Auto-hide cursor</string></property>
|
<property name="text"><string>Auto-hide cursor</string></property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_stretch">
|
||||||
|
<property name="toolTip"><string>Stretch the game image to fill the window instead of keeping its aspect ratio with black bars</string></property>
|
||||||
|
<property name="text"><string>Stretch to window</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkBox_perf_overlay">
|
<widget class="QCheckBox" name="checkBox_perf_overlay">
|
||||||
<property name="toolTip"><string>Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view.</string></property>
|
<property name="toolTip"><string>Show FPS, frame times, CPU/GPU usage, RAM and memory pressure in the game window. Press F2 (Cmd+P on macOS) to toggle it, and add Shift for the detailed view.</string></property>
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ inline QStringList EnumToList() {
|
|||||||
auto me = QMetaEnum::fromType<T>();
|
auto me = QMetaEnum::fromType<T>();
|
||||||
int count = me.keyCount();
|
int count = me.keyCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
auto key = QString(me.key(i));
|
ret << QString(me.key(i));
|
||||||
ret << (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit()
|
|
||||||
? key.remove('R').toLower()
|
|
||||||
: key);
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -32,16 +29,12 @@ inline QStringList EnumToList() {
|
|||||||
template <class T>
|
template <class T>
|
||||||
T TextToEnum(const QString& text) {
|
T TextToEnum(const QString& text) {
|
||||||
auto me = QMetaEnum::fromType<T>();
|
auto me = QMetaEnum::fromType<T>();
|
||||||
return static_cast<T>(me.keyToValue(
|
return static_cast<T>(me.keyToValue(text.toUtf8().constData()));
|
||||||
((text.size() > 1 && text.at(0).isDigit()) ? 'R' + text.toUpper() : text).toUtf8().data()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
QString EnumToText(T value) {
|
QString EnumToText(T value) {
|
||||||
auto me = QMetaEnum::fromType<T>();
|
return QString(QMetaEnum::fromType<T>().valueToKey(static_cast<int>(value)));
|
||||||
auto key = QString(me.valueToKey(static_cast<int>(value)));
|
|
||||||
return (key.startsWith('R') && key.size() > 2 && key.at(1).isDigit() ? key.remove('R').toLower()
|
|
||||||
: key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControllerSettings {
|
struct ControllerSettings {
|
||||||
@@ -80,13 +73,10 @@ public:
|
|||||||
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
static constexpr int DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||||
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
static constexpr int MAX_CONSOLE_LANGUAGE = 29;
|
||||||
|
|
||||||
enum class Resolution {
|
static constexpr int MIN_SCREEN_SIZE = 320;
|
||||||
R1280X720,
|
static constexpr int MAX_SCREEN_SIZE = 16384;
|
||||||
R1920X1080,
|
|
||||||
R2560X1440,
|
static QString NormalizeResolution(const QString& text);
|
||||||
R3840X2160,
|
|
||||||
};
|
|
||||||
Q_ENUM(Resolution)
|
|
||||||
|
|
||||||
enum class ShaderOptimizationType { None, Size, Performance };
|
enum class ShaderOptimizationType { None, Size, Performance };
|
||||||
Q_ENUM(ShaderOptimizationType)
|
Q_ENUM(ShaderOptimizationType)
|
||||||
@@ -117,7 +107,7 @@ public:
|
|||||||
// Controller preferences always come from the global configuration.
|
// Controller preferences always come from the global configuration.
|
||||||
ControllerSettings controller;
|
ControllerSettings controller;
|
||||||
|
|
||||||
Resolution screen_resolution = Resolution::R1280X720;
|
QString screen_resolution = QStringLiteral("1280x720");
|
||||||
QString user_name = "Kyty";
|
QString user_name = "Kyty";
|
||||||
int user_id = Config::DEFAULT_USER_ID;
|
int user_id = Config::DEFAULT_USER_ID;
|
||||||
QString audio_input_device;
|
QString audio_input_device;
|
||||||
@@ -126,6 +116,7 @@ public:
|
|||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
bool hdr_enabled = false;
|
bool hdr_enabled = false;
|
||||||
bool hide_cursor_enabled = false;
|
bool hide_cursor_enabled = false;
|
||||||
|
bool stretch_enabled = false;
|
||||||
bool readback_linear_images = false;
|
bool readback_linear_images = false;
|
||||||
bool readback_tiled_images = false;
|
bool readback_tiled_images = false;
|
||||||
bool sync_raw_image_buffers = false;
|
bool sync_raw_image_buffers = false;
|
||||||
@@ -168,6 +159,7 @@ public:
|
|||||||
fullscreen_enabled = other.fullscreen_enabled;
|
fullscreen_enabled = other.fullscreen_enabled;
|
||||||
hdr_enabled = other.hdr_enabled;
|
hdr_enabled = other.hdr_enabled;
|
||||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||||
|
stretch_enabled = other.stretch_enabled;
|
||||||
readback_linear_images = other.readback_linear_images;
|
readback_linear_images = other.readback_linear_images;
|
||||||
readback_tiled_images = other.readback_tiled_images;
|
readback_tiled_images = other.readback_tiled_images;
|
||||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||||
|
|||||||
@@ -46,11 +46,15 @@ private:
|
|||||||
QLineEdit* m_addons_dir_edit = nullptr;
|
QLineEdit* m_addons_dir_edit = nullptr;
|
||||||
QToolButton* m_remove_game_dir_button = nullptr;
|
QToolButton* m_remove_game_dir_button = nullptr;
|
||||||
bool m_global_settings = false;
|
bool m_global_settings = false;
|
||||||
|
bool m_geometry_restored = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void Init(const Configuration& info);
|
void Init(const Configuration& info);
|
||||||
void InitGameDirectories();
|
void InitGameDirectories();
|
||||||
void AddGameDirectoryItem(const QString& dir);
|
void AddGameDirectoryItem(const QString& dir);
|
||||||
|
void MakeTabsScrollable();
|
||||||
|
void GrowToFitContents();
|
||||||
|
void ShowTab(QWidget* page);
|
||||||
|
|
||||||
void moveEvent(QMoveEvent* event) override;
|
void moveEvent(QMoveEvent* event) override;
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
QString Configuration::NormalizeResolution(const QString& text) {
|
||||||
|
// Older settings stored the enum key, e.g. "R1920X1080".
|
||||||
|
static const QRegularExpression re(QStringLiteral("^\\s*R?(\\d+)\\s*[xX]\\s*(\\d+)\\s*$"));
|
||||||
|
const auto match = re.match(text);
|
||||||
|
if (!match.hasMatch()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const int width = match.captured(1).toInt();
|
||||||
|
const int height = match.captured(2).toInt();
|
||||||
|
if (width < MIN_SCREEN_SIZE || height < MIN_SCREEN_SIZE || width > MAX_SCREEN_SIZE ||
|
||||||
|
height > MAX_SCREEN_SIZE) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return QStringLiteral("%1x%2").arg(width).arg(height);
|
||||||
|
}
|
||||||
|
|
||||||
QVariantMap Configuration::GameSettings() const {
|
QVariantMap Configuration::GameSettings() const {
|
||||||
QVariantMap values;
|
QVariantMap values;
|
||||||
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
|
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
|
||||||
@@ -12,6 +30,7 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
KYTY_CFG_SET(fullscreen_enabled);
|
KYTY_CFG_SET(fullscreen_enabled);
|
||||||
KYTY_CFG_SET(hdr_enabled);
|
KYTY_CFG_SET(hdr_enabled);
|
||||||
KYTY_CFG_SET(hide_cursor_enabled);
|
KYTY_CFG_SET(hide_cursor_enabled);
|
||||||
|
KYTY_CFG_SET(stretch_enabled);
|
||||||
KYTY_CFG_SET(readback_linear_images);
|
KYTY_CFG_SET(readback_linear_images);
|
||||||
KYTY_CFG_SET(readback_tiled_images);
|
KYTY_CFG_SET(readback_tiled_images);
|
||||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||||
@@ -48,7 +67,8 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
template <class Settings>
|
template <class Settings>
|
||||||
void Configuration::ReadGameSettingsValues(const Settings& s) {
|
void Configuration::ReadGameSettingsValues(const Settings& s) {
|
||||||
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
|
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
|
||||||
KYTY_CFG_GET(screen_resolution);
|
const auto resolution = NormalizeResolution(s.value("screen_resolution").toString());
|
||||||
|
screen_resolution = resolution.isEmpty() ? QStringLiteral("1280x720") : resolution;
|
||||||
user_name = s.value("user_name", user_name).toString();
|
user_name = s.value("user_name", user_name).toString();
|
||||||
bool user_id_ok = false;
|
bool user_id_ok = false;
|
||||||
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
|
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
|
||||||
@@ -64,6 +84,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
|||||||
KYTY_CFG_GET(fullscreen_enabled);
|
KYTY_CFG_GET(fullscreen_enabled);
|
||||||
KYTY_CFG_GET(hdr_enabled);
|
KYTY_CFG_GET(hdr_enabled);
|
||||||
KYTY_CFG_GET(hide_cursor_enabled);
|
KYTY_CFG_GET(hide_cursor_enabled);
|
||||||
|
KYTY_CFG_GET(stretch_enabled);
|
||||||
KYTY_CFG_GET(readback_linear_images);
|
KYTY_CFG_GET(readback_linear_images);
|
||||||
KYTY_CFG_GET(readback_tiled_images);
|
KYTY_CFG_GET(readback_tiled_images);
|
||||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||||
@@ -134,7 +155,9 @@ bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error)
|
|||||||
if (!it.value().isString()) {
|
if (!it.value().isString()) {
|
||||||
return fail(tr("Game setting %1 must be a string.").arg(it.key()));
|
return fail(tr("Game setting %1 must be a string.").arg(it.key()));
|
||||||
}
|
}
|
||||||
values.insert(it.key(), it.value().toString());
|
const auto value = it.value().toString();
|
||||||
|
values.insert(it.key(), it.key() == "screen_resolution" ? NormalizeResolution(value)
|
||||||
|
: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadGameSettingsValues(values);
|
ReadGameSettingsValues(values);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QScrollArea>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
@@ -122,10 +124,10 @@ static QString GameDirectoryKey(const QString& dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
|
ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* parent)
|
||||||
: QDialog(parent, Qt::WindowCloseButtonHint), m_ui(new Ui::ConfigurationEditDialog),
|
: QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
|
||||||
m_info(info) {
|
m_ui(new Ui::ConfigurationEditDialog), m_info(info) {
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
setMinimumWidth(width());
|
MakeTabsScrollable();
|
||||||
InitGameDirectories();
|
InitGameDirectories();
|
||||||
m_ui->controller_group->setVisible(false);
|
m_ui->controller_group->setVisible(false);
|
||||||
m_ui->settings_button->setVisible(false);
|
m_ui->settings_button->setVisible(false);
|
||||||
@@ -170,9 +172,11 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
|||||||
|
|
||||||
setSizeGripEnabled(true);
|
setSizeGripEnabled(true);
|
||||||
|
|
||||||
restoreGeometry(g_last_geometry);
|
|
||||||
|
|
||||||
Init(info);
|
Init(info);
|
||||||
|
m_geometry_restored = restoreGeometry(g_last_geometry);
|
||||||
|
if (!m_geometry_restored) {
|
||||||
|
GrowToFitContents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ConfigurationEditDialog::g_last_geometry;
|
QByteArray ConfigurationEditDialog::g_last_geometry;
|
||||||
@@ -248,7 +252,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
|||||||
info.audio_input_device);
|
info.audio_input_device);
|
||||||
}
|
}
|
||||||
microphone->setCurrentIndex(microphone->findData(info.audio_input_device));
|
microphone->setCurrentIndex(microphone->findData(info.audio_input_device));
|
||||||
ListInit(m_ui->comboBox_screen_resolution, info.screen_resolution);
|
m_ui->comboBox_screen_resolution->setCurrentText(info.screen_resolution);
|
||||||
ListInit(m_ui->comboBox_present_mode, info.present_mode);
|
ListInit(m_ui->comboBox_present_mode, info.present_mode);
|
||||||
m_ui->comboBox_gpu->clear();
|
m_ui->comboBox_gpu->clear();
|
||||||
m_ui->comboBox_gpu->addItem(tr("Auto"));
|
m_ui->comboBox_gpu->addItem(tr("Auto"));
|
||||||
@@ -285,6 +289,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
|||||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||||
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
||||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||||
|
m_ui->checkBox_stretch->setChecked(info.stretch_enabled);
|
||||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||||
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
||||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||||
@@ -413,8 +418,46 @@ void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs, const Q
|
|||||||
m_game_dirs_group->setVisible(true);
|
m_game_dirs_group->setVisible(true);
|
||||||
m_ui->controller_group->setVisible(true);
|
m_ui->controller_group->setVisible(true);
|
||||||
update_game_directory_buttons();
|
update_game_directory_buttons();
|
||||||
|
if (!m_geometry_restored) {
|
||||||
|
GrowToFitContents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationEditDialog::MakeTabsScrollable() {
|
||||||
|
auto* tabs = m_ui->settings_tabs;
|
||||||
|
for (int i = 0; i < tabs->count(); i++) {
|
||||||
|
auto* page = tabs->widget(i);
|
||||||
|
auto title = tabs->tabText(i);
|
||||||
|
auto* scroll = new QScrollArea;
|
||||||
|
scroll->setWidgetResizable(true);
|
||||||
|
scroll->setFrameShape(QFrame::NoFrame);
|
||||||
|
tabs->removeTab(i);
|
||||||
|
scroll->setWidget(page);
|
||||||
|
tabs->insertTab(i, scroll, title);
|
||||||
|
}
|
||||||
|
tabs->setCurrentIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationEditDialog::GrowToFitContents() {
|
||||||
layout()->activate();
|
layout()->activate();
|
||||||
resize(size().expandedTo(minimumSizeHint()));
|
auto* tabs = m_ui->settings_tabs;
|
||||||
|
QSize content;
|
||||||
|
for (int i = 0; i < tabs->count(); i++) {
|
||||||
|
content = content.expandedTo(
|
||||||
|
static_cast<QScrollArea*>(tabs->widget(i))->widget()->minimumSizeHint());
|
||||||
|
}
|
||||||
|
const QSize chrome = size() - tabs->currentWidget()->size();
|
||||||
|
resize(size().expandedTo(content + chrome).boundedTo(screen()->availableGeometry().size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigurationEditDialog::ShowTab(QWidget* page) {
|
||||||
|
auto* tabs = m_ui->settings_tabs;
|
||||||
|
for (int i = 0; i < tabs->count(); i++) {
|
||||||
|
if (tabs->widget(i)->isAncestorOf(page)) {
|
||||||
|
tabs->setCurrentIndex(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ConfigurationEditDialog::GetAddonsDirectory() const {
|
QString ConfigurationEditDialog::GetAddonsDirectory() const {
|
||||||
@@ -474,13 +517,14 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
|||||||
info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt();
|
info.controller.keyboard_player = ui.comboBox_keyboard_player->currentData().toInt();
|
||||||
}
|
}
|
||||||
info.screen_resolution =
|
info.screen_resolution =
|
||||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
Configuration::NormalizeResolution(ui.comboBox_screen_resolution->currentText());
|
||||||
info.present_mode =
|
info.present_mode =
|
||||||
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
||||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||||
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
||||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||||
|
info.stretch_enabled = ui.checkBox_stretch->isChecked();
|
||||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||||
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
||||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||||
@@ -518,7 +562,7 @@ void ConfigurationEditDialog::save() {
|
|||||||
for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file,
|
for (auto* field: {m_ui->lineEdit_shader_log_folder, m_ui->lineEdit_printf_file,
|
||||||
m_ui->lineEdit_cmd_dump_folder}) {
|
m_ui->lineEdit_cmd_dump_folder}) {
|
||||||
if (field->isEnabled() && field->text().isEmpty()) {
|
if (field->isEnabled() && field->text().isEmpty()) {
|
||||||
m_ui->settings_tabs->setCurrentWidget(m_ui->debug_tab);
|
ShowTab(m_ui->debug_tab);
|
||||||
field->setFocus();
|
field->setFocus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -540,6 +584,16 @@ void ConfigurationEditDialog::save() {
|
|||||||
tr("User ID cannot be 254 (everyone) or 255 (system)"));
|
tr("User ID cannot be 254 (everyone) or 255 (system)"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Configuration::NormalizeResolution(m_ui->comboBox_screen_resolution->currentText())
|
||||||
|
.isEmpty()) {
|
||||||
|
ShowTab(m_ui->settings_tab);
|
||||||
|
m_ui->comboBox_screen_resolution->setFocus();
|
||||||
|
QMessageBox::critical(this, tr("Save failed"),
|
||||||
|
tr("Screen resolution must be WIDTHxHEIGHT, each between %1 and %2")
|
||||||
|
.arg(Configuration::MIN_SCREEN_SIZE)
|
||||||
|
.arg(Configuration::MAX_SCREEN_SIZE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
UpdateInfo(m_info, *m_ui, m_global_settings);
|
UpdateInfo(m_info, *m_ui, m_global_settings);
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,8 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
|||||||
m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal.
|
m_ui->close_terminal->setVisible(false); // The emulator runs without a terminal.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/);
|
main_dialog->setWindowFlags(Qt::Dialog | Qt::WindowMinMaxButtonsHint |
|
||||||
|
Qt::WindowCloseButtonHint);
|
||||||
|
|
||||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
@@ -240,7 +241,7 @@ static void AddFlag(QStringList& args, bool enabled, const char* name) {
|
|||||||
|
|
||||||
static QStringList CreateEmulatorArgs(const Configuration& info) {
|
static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||||
QStringList args;
|
QStringList args;
|
||||||
auto r = EnumToText(info.screen_resolution).split('x');
|
auto r = Configuration::NormalizeResolution(info.screen_resolution).split('x');
|
||||||
|
|
||||||
if (r.size() != 2) {
|
if (r.size() != 2) {
|
||||||
return {};
|
return {};
|
||||||
@@ -266,6 +267,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
|||||||
AddFlag(args, info.fullscreen_enabled, "--fullscreen");
|
AddFlag(args, info.fullscreen_enabled, "--fullscreen");
|
||||||
AddFlag(args, info.hdr_enabled, "--hdr");
|
AddFlag(args, info.hdr_enabled, "--hdr");
|
||||||
AddFlag(args, info.hide_cursor_enabled, "--hide-cursor");
|
AddFlag(args, info.hide_cursor_enabled, "--hide-cursor");
|
||||||
|
AddFlag(args, info.stretch_enabled, "--stretch");
|
||||||
AddFlag(args, info.readback_linear_images, "--readback-linear-images");
|
AddFlag(args, info.readback_linear_images, "--readback-linear-images");
|
||||||
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
|
AddFlag(args, info.readback_tiled_images, "--readback-tiled-images");
|
||||||
AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers");
|
AddFlag(args, info.sync_raw_image_buffers, "--sync-raw-image-buffers");
|
||||||
|
|||||||
@@ -96,6 +96,13 @@ int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info) {
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int KYTY_SYSV_ABI AcmBatchJobPriority(AcmBatchInfo* batch_info, uint32_t priority) {
|
||||||
|
PRINT_NAME();
|
||||||
|
(void)batch_info;
|
||||||
|
(void)priority;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
||||||
uint32_t count, const void* const ir[],
|
uint32_t count, const void* const ir[],
|
||||||
const float* gain, void* const out[]) {
|
const float* gain, void* const out[]) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info
|
|||||||
AcmBatchError* batch_error, AcmBatchId* batch);
|
AcmBatchError* batch_error, AcmBatchId* batch);
|
||||||
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
|
int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout);
|
||||||
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
|
int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info);
|
||||||
|
int KYTY_SYSV_ABI AcmBatchJobPriority(AcmBatchInfo* batch_info, uint32_t priority);
|
||||||
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in,
|
||||||
uint32_t count, const void* const ir[],
|
uint32_t count, const void* const ir[],
|
||||||
const float* gain, void* const out[]);
|
const float* gain, void* const out[]);
|
||||||
|
|||||||
+42
-2
@@ -2480,6 +2480,9 @@ static constexpr uint32_t AGC_DRAW_INDIRECT_MULTI_END_TAG = 0xc6000000u;
|
|||||||
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_32_TAG = 0xc8010000u;
|
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_32_TAG = 0xc8010000u;
|
||||||
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_64_TAG = 0xc8020000u;
|
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_64_TAG = 0xc8020000u;
|
||||||
static constexpr uint32_t AGC_WAIT_USER_DATA_END_TAG = 0xc8000000u;
|
static constexpr uint32_t AGC_WAIT_USER_DATA_END_TAG = 0xc8000000u;
|
||||||
|
static constexpr uint32_t AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG = 0xc9010000u;
|
||||||
|
static constexpr uint32_t AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG = 0xc9020000u;
|
||||||
|
static constexpr uint32_t AGC_MEM_SEMAPHORE_END_TAG = 0xc9000000u;
|
||||||
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_DW = 4u;
|
static constexpr uint32_t AGC_WAIT_USER_DATA_BEGIN_DW = 4u;
|
||||||
static constexpr uint32_t AGC_INTERNAL_DATA_PACKET_SIZE_DW = 3u;
|
static constexpr uint32_t AGC_INTERNAL_DATA_PACKET_SIZE_DW = 3u;
|
||||||
|
|
||||||
@@ -3077,6 +3080,42 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
|
|||||||
poll_cycles);
|
poll_cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t* KYTY_SYSV_ABI AgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address,
|
||||||
|
uint8_t mailbox, uint8_t signal_type, uint8_t op) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(op != 6u && op != 7u);
|
||||||
|
|
||||||
|
auto* cmd =
|
||||||
|
buf->AllocateDW(AGC_WAIT_USER_DATA_BEGIN_DW + AGC_INTERNAL_DATA_PACKET_SIZE_DW + 4u);
|
||||||
|
if (cmd == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto address_value = reinterpret_cast<uint64_t>(address);
|
||||||
|
const bool signal = (op == 6u);
|
||||||
|
cmd[0] = KYTY_PM4(AGC_WAIT_USER_DATA_BEGIN_DW, Pm4::IT_SET_UCONFIG_REG, 1u);
|
||||||
|
cmd[1] = AGC_INTERNAL_DATA_REGISTER;
|
||||||
|
cmd[2] = (signal ? AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG : AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG) |
|
||||||
|
static_cast<uint32_t>((address_value >> 32u) & 0xffffu);
|
||||||
|
cmd[3] = static_cast<uint32_t>(address_value);
|
||||||
|
|
||||||
|
// A signal pads with a NOP header where a wait ends with its end marker.
|
||||||
|
auto* semaphore = cmd + AGC_WAIT_USER_DATA_BEGIN_DW;
|
||||||
|
if (signal) {
|
||||||
|
semaphore[0] = KYTY_PM4(AGC_INTERNAL_DATA_PACKET_SIZE_DW, Pm4::IT_NOP, 0u);
|
||||||
|
semaphore += AGC_INTERNAL_DATA_PACKET_SIZE_DW;
|
||||||
|
}
|
||||||
|
semaphore[0] = KYTY_PM4(4, Pm4::IT_MEM_SEMAPHORE, 0u);
|
||||||
|
semaphore[1] = static_cast<uint32_t>(address_value) & ~0x7u;
|
||||||
|
semaphore[2] = static_cast<uint32_t>(address_value >> 32u);
|
||||||
|
semaphore[3] = (static_cast<uint32_t>(op) << 29u) | ((signal_type & 0x1u) << 20u) |
|
||||||
|
((mailbox & 0x1u) << 16u);
|
||||||
|
if (!signal) {
|
||||||
|
write_agc_internal_data_packet(semaphore + 4, AGC_MEM_SEMAPHORE_END_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) {
|
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
@@ -3840,11 +3879,12 @@ bool AgcIsInternalDataPacket(uint32_t cmd_id, const uint32_t* payload) {
|
|||||||
const auto size_dw = KYTY_PM4_LEN(cmd_id);
|
const auto size_dw = KYTY_PM4_LEN(cmd_id);
|
||||||
if (size_dw == AGC_WAIT_USER_DATA_BEGIN_DW) {
|
if (size_dw == AGC_WAIT_USER_DATA_BEGIN_DW) {
|
||||||
const auto tag = payload[1] & 0xffff0000u;
|
const auto tag = payload[1] & 0xffff0000u;
|
||||||
return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG;
|
return tag == AGC_WAIT_USER_DATA_BEGIN_32_TAG || tag == AGC_WAIT_USER_DATA_BEGIN_64_TAG ||
|
||||||
|
tag == AGC_MEM_SEMAPHORE_SIGNAL_BEGIN_TAG || tag == AGC_MEM_SEMAPHORE_WAIT_BEGIN_TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
return size_dw == AGC_INTERNAL_DATA_PACKET_SIZE_DW &&
|
return size_dw == AGC_INTERNAL_DATA_PACKET_SIZE_DW &&
|
||||||
(payload[1] == AGC_WAIT_USER_DATA_END_TAG ||
|
(payload[1] == AGC_WAIT_USER_DATA_END_TAG || payload[1] == AGC_MEM_SEMAPHORE_END_TAG ||
|
||||||
payload[1] == AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG ||
|
payload[1] == AGC_DRAW_INDIRECT_MULTI_BEGIN_TAG ||
|
||||||
payload[1] == AGC_DRAW_INDIRECT_MULTI_END_TAG);
|
payload[1] == AGC_DRAW_INDIRECT_MULTI_END_TAG);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,8 @@ uint32_t* KYTY_SYSV_ABI AgcAcbWaitRegMem(CommandBuffer* buf, uint8_t size, uint8
|
|||||||
uint8_t cache_policy, const volatile void* address,
|
uint8_t cache_policy, const volatile void* address,
|
||||||
uint64_t reference, uint64_t mask, uint32_t poll_cycles);
|
uint64_t reference, uint64_t mask, uint32_t poll_cycles);
|
||||||
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size);
|
uint64_t KYTY_SYSV_ABI AgcAcbWaitOnAddressGetSize(uint8_t size);
|
||||||
|
uint32_t* KYTY_SYSV_ABI AgcAcbMemSemaphore(CommandBuffer* buf, const volatile void* address,
|
||||||
|
uint8_t mailbox, uint8_t signal_type, uint8_t op);
|
||||||
uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy,
|
uint32_t* KYTY_SYSV_ABI AgcAcbDmaData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy,
|
||||||
uint64_t dst_address_or_offset, uint8_t src,
|
uint64_t dst_address_or_offset, uint8_t src,
|
||||||
uint8_t src_cache_policy,
|
uint8_t src_cache_policy,
|
||||||
|
|||||||
@@ -519,7 +519,20 @@ void GameController::SetDevices(int active_id) {
|
|||||||
m_trigger_state = {};
|
m_trigger_state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Input that changes no button or touch contact updates the newest unread sample, so a burst of
|
||||||
|
// stick or sensor events between two reads cannot push unread button or touch changes out.
|
||||||
void GameController::AddState() {
|
void GameController::AddState() {
|
||||||
|
if (m_states_num != 0) {
|
||||||
|
const auto newest = (m_first_state + m_states_num - 1) % STATES_MAX;
|
||||||
|
auto& last = m_states[newest];
|
||||||
|
if (!m_obtained[newest] && last.buttons == m_state.buttons &&
|
||||||
|
last.touch[0].down == m_state.touch[0].down &&
|
||||||
|
last.touch[1].down == m_state.touch[1].down) {
|
||||||
|
last = m_state;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_states_num >= STATES_MAX) {
|
if (m_states_num >= STATES_MAX) {
|
||||||
m_states_num = STATES_MAX - 1;
|
m_states_num = STATES_MAX - 1;
|
||||||
m_first_state = (m_first_state + 1) % STATES_MAX;
|
m_first_state = (m_first_state + 1) % STATES_MAX;
|
||||||
|
|||||||
@@ -658,6 +658,8 @@ constexpr int SAVE_DATA_ERROR_BUSY_FOR_SAVING = -2137063402
|
|||||||
constexpr int SAVE_DATA_ERROR_LIMITATION_OVER = -2137063401; /* 0x809F0017 */
|
constexpr int SAVE_DATA_ERROR_LIMITATION_OVER = -2137063401; /* 0x809F0017 */
|
||||||
constexpr int SAVE_DATA_ERROR_EVENT_BUSY = -2137063400; /* 0x809F0018 */
|
constexpr int SAVE_DATA_ERROR_EVENT_BUSY = -2137063400; /* 0x809F0018 */
|
||||||
constexpr int SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = -2137063399; /* 0x809F0019 */
|
constexpr int SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = -2137063399; /* 0x809F0019 */
|
||||||
|
constexpr int SAVE_DATA_ERROR_NO_NEED_CONVERT = -2137063394; /* 0x809F001E */
|
||||||
|
constexpr int SAVE_DATA_ERROR_BLOCKS_FEWER_THAN_SRC = -2137063392; /* 0x809F0020 */
|
||||||
|
|
||||||
} // namespace SaveData
|
} // namespace SaveData
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ LIB_DEFINE(InitAgcDriver_1) {
|
|||||||
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
|
LIB_FUNC("b-oySn+G2tE", Gen5::AgcAcbJumpGetSize);
|
||||||
LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind);
|
LIB_FUNC("DwICrVxerkY", Gen5::AgcAcbRewind);
|
||||||
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
|
LIB_FUNC("htn36gPnBk4", Gen5::AgcAcbWaitRegMem);
|
||||||
|
LIB_FUNC("q4VuU-QsLOE", Gen5::AgcAcbMemSemaphore);
|
||||||
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
|
LIB_FUNC("idlaArvdXEs", Gen5::AgcAcbWaitOnAddressGetSize);
|
||||||
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
|
LIB_FUNC("-RnpfpxIhec", Gen5::AgcAcbDmaData);
|
||||||
LIB_FUNC("qzMN2XKGA4k", Gen5::AgcAcbCopyData);
|
LIB_FUNC("qzMN2XKGA4k", Gen5::AgcAcbCopyData);
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ LIB_DEFINE(InitAudio_1_Acm) {
|
|||||||
LIB_FUNC("8fe55ktlNVo", Acm::AcmBatchStartBuffers);
|
LIB_FUNC("8fe55ktlNVo", Acm::AcmBatchStartBuffers);
|
||||||
LIB_FUNC("RLN3gRlXJLE", Acm::AcmBatchWait);
|
LIB_FUNC("RLN3gRlXJLE", Acm::AcmBatchWait);
|
||||||
LIB_FUNC("r7z5YQFZo+U", Acm::AcmBatchJobNotification);
|
LIB_FUNC("r7z5YQFZo+U", Acm::AcmBatchJobNotification);
|
||||||
|
LIB_FUNC("GTfJ64qFCPw", Acm::AcmBatchJobPriority);
|
||||||
LIB_FUNC("u70oWo92SYQ", Acm::AcmConvReverbSharedInput);
|
LIB_FUNC("u70oWo92SYQ", Acm::AcmConvReverbSharedInput);
|
||||||
LIB_FUNC("9nLbWmRDpa8", Acm::AcmConvReverbSharedIr);
|
LIB_FUNC("9nLbWmRDpa8", Acm::AcmConvReverbSharedIr);
|
||||||
LIB_FUNC("KovqaFbmtsM", Acm::AcmFft);
|
LIB_FUNC("KovqaFbmtsM", Acm::AcmFft);
|
||||||
|
|||||||
+119
-88
@@ -13,6 +13,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Libs::Audio {
|
namespace Libs::Audio {
|
||||||
@@ -168,17 +169,12 @@ struct AudioOut2MasteringStatesV2 {
|
|||||||
|
|
||||||
static std::atomic_uint64_t g_audioout2_next_context {1};
|
static std::atomic_uint64_t g_audioout2_next_context {1};
|
||||||
static std::atomic_uint64_t g_audioout2_next_port {1};
|
static std::atomic_uint64_t g_audioout2_next_port {1};
|
||||||
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
static AudioOut2UserHandle g_audioout2_next_user = 1;
|
||||||
|
|
||||||
struct AudioOut2ContextState {
|
static constexpr uint32_t AUDIOOUT2_CHANNEL_PORTS_MAX = 32;
|
||||||
bool used = false;
|
static constexpr uint32_t AUDIOOUT2_OBJECT_PORTS_MAX = 512;
|
||||||
AudioOut2ContextHandle handle = 0;
|
static constexpr uint32_t AUDIOOUT2_PORT_SLOT_BITS = 10;
|
||||||
uint32_t queue_depth = 4;
|
static constexpr uint32_t AUDIOOUT2_CONTEXT_SLOT_BITS = 4;
|
||||||
uint32_t queued = 0;
|
|
||||||
uint32_t num_grains = 512;
|
|
||||||
uint64_t last_update = 0;
|
|
||||||
int object_audio_handle = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AudioOut2PortStateEntry {
|
struct AudioOut2PortStateEntry {
|
||||||
bool used = false;
|
bool used = false;
|
||||||
@@ -196,6 +192,19 @@ struct AudioOut2PortStateEntry {
|
|||||||
bool attributes_locked = false;
|
bool attributes_locked = false;
|
||||||
std::vector<uint8_t> pcm_data;
|
std::vector<uint8_t> pcm_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AudioOut2ContextState {
|
||||||
|
bool used = false;
|
||||||
|
AudioOut2ContextHandle handle = 0;
|
||||||
|
uint32_t queue_depth = 4;
|
||||||
|
uint32_t queued = 0;
|
||||||
|
uint32_t num_grains = 512;
|
||||||
|
uint64_t last_update = 0;
|
||||||
|
int object_audio_handle = 0;
|
||||||
|
uint32_t max_ports = 0;
|
||||||
|
// Channel slots precede object slots; each partition has its own creation limit.
|
||||||
|
std::vector<AudioOut2PortStateEntry> ports;
|
||||||
|
};
|
||||||
struct AudioOut2SpeakerArrayState {
|
struct AudioOut2SpeakerArrayState {
|
||||||
bool used = false;
|
bool used = false;
|
||||||
uint32_t num_speakers = 0;
|
uint32_t num_speakers = 0;
|
||||||
@@ -223,13 +232,16 @@ struct AudioOut2Submission {
|
|||||||
static Common::Mutex g_audioout2_state_mutex;
|
static Common::Mutex g_audioout2_state_mutex;
|
||||||
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
static std::array<AudioOut2ContextState, 16> g_audioout2_contexts;
|
||||||
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
static std::array<AudioOut2Submission, 16> g_audioout2_submissions;
|
||||||
static std::array<AudioOut2PortStateEntry, 256> g_audioout2_ports;
|
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
||||||
static std::vector<AudioOut2UserHandle> g_audioout2_users;
|
|
||||||
static Common::Mutex g_audioout2_speaker_array_mutex;
|
static Common::Mutex g_audioout2_speaker_array_mutex;
|
||||||
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
static std::array<AudioOut2SpeakerArrayState, 32> g_audioout2_speaker_arrays;
|
||||||
static Common::Mutex g_audioout2_latency_mutex;
|
static Common::Mutex g_audioout2_latency_mutex;
|
||||||
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
static std::array<AudioOut2LatencyState, 16> g_audioout2_latencies;
|
||||||
|
|
||||||
|
static_assert(AUDIOOUT2_CHANNEL_PORTS_MAX + AUDIOOUT2_OBJECT_PORTS_MAX <=
|
||||||
|
(1u << AUDIOOUT2_PORT_SLOT_BITS));
|
||||||
|
static_assert(g_audioout2_contexts.size() == (1u << AUDIOOUT2_CONTEXT_SLOT_BITS));
|
||||||
|
|
||||||
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
static constexpr int AUDIO_OUT2_ERROR_NOT_READY = -2144960504; /* 0x80268008 */
|
||||||
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
static constexpr int AUDIO_OUT2_ERROR_PORT_FULL = -2144960494; /* 0x80268012 */
|
||||||
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
static constexpr int AUDIO_OUT2_ERROR_INVALID_PARAM = -2144960511; /* 0x80268001 */
|
||||||
@@ -262,6 +274,11 @@ static AudioOut2Submission& audioout2_submission(const AudioOut2ContextState& co
|
|||||||
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
|
return g_audioout2_submissions[&context - g_audioout2_contexts.data()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ticks of a sixth of a microsecond: one 48 kHz sample lasts exactly 125 of them.
|
||||||
|
static uint64_t audioout2_clock() {
|
||||||
|
return LibKernel::KernelGetProcessTime() * 6;
|
||||||
|
}
|
||||||
|
|
||||||
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
static uint32_t audioout2_grain_micros(uint32_t grains) {
|
||||||
const auto sample_count = (grains == 0 ? 512u : grains);
|
const auto sample_count = (grains == 0 ? 512u : grains);
|
||||||
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
return std::max<uint32_t>((sample_count * 1000000u) / 48000u, 1000u);
|
||||||
@@ -331,49 +348,51 @@ static void audioout2_update_context_locked(AudioOut2ContextState* state) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto now = LibKernel::KernelGetProcessTime();
|
const auto now = audioout2_clock();
|
||||||
if (state->last_update == 0 || state->queued == 0) {
|
if (state->last_update == 0 || state->queued == 0) {
|
||||||
state->last_update = now;
|
state->last_update = now;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (now <= state->last_update) {
|
||||||
const auto grain_micros = static_cast<uint64_t>(audioout2_grain_micros(state->num_grains));
|
|
||||||
if (now <= state->last_update || grain_micros == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto elapsed = now - state->last_update;
|
const auto grain = uint64_t {state->num_grains} * 125;
|
||||||
const auto drained = std::min<uint64_t>(state->queued, elapsed / grain_micros);
|
const auto drained = std::min<uint64_t>(state->queued, (now - state->last_update) / grain);
|
||||||
if (drained == 0) {
|
if (drained == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->queued -= static_cast<uint32_t>(drained);
|
state->queued -= static_cast<uint32_t>(drained);
|
||||||
state->last_update += drained * grain_micros;
|
state->last_update += drained * grain;
|
||||||
if (state->queued == 0) {
|
if (state->queued == 0) {
|
||||||
state->last_update = now;
|
state->last_update = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle port) {
|
||||||
for (auto& state: g_audioout2_ports) {
|
const auto context_slot =
|
||||||
if (state.used && state.handle == port) {
|
(port >> AUDIOOUT2_PORT_SLOT_BITS) & ((1u << AUDIOOUT2_CONTEXT_SLOT_BITS) - 1u);
|
||||||
return &state;
|
const auto port_slot = port & ((1u << AUDIOOUT2_PORT_SLOT_BITS) - 1u);
|
||||||
}
|
auto& context = g_audioout2_contexts[context_slot];
|
||||||
|
if (context.used && port_slot < context.ports.size()) {
|
||||||
|
auto& state = context.ports[port_slot];
|
||||||
|
if (state.used && state.handle == port) return &state;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
|
||||||
const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
const auto bytes_per_sample =
|
||||||
|
(state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
|
||||||
return static_cast<size_t>(state.samples_num) *
|
return static_cast<size_t>(state.samples_num) *
|
||||||
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
static bool audioout2_context_has_queueable_device(const AudioOut2ContextState& context) {
|
||||||
for (const auto& state: g_audioout2_ports) {
|
for (const auto& state: context.ports) {
|
||||||
if (state.used && state.context == context.handle && !state.pcm_data.empty()) {
|
if (state.used && !state.pcm_data.empty()) {
|
||||||
const int handle = audioout2_port_type_is_object(state.port_type)
|
const int handle = audioout2_port_type_is_object(state.port_type)
|
||||||
? context.object_audio_handle
|
? context.object_audio_handle
|
||||||
: state.audio_handle;
|
: state.audio_handle;
|
||||||
@@ -427,18 +446,21 @@ static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, AudioOut2S
|
|||||||
out.object_mix.clear();
|
out.object_mix.clear();
|
||||||
std::vector<AudioInternal::OutputParam> params;
|
std::vector<AudioInternal::OutputParam> params;
|
||||||
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
params.reserve(AudioInternal::OUT_PORTS_MAX);
|
||||||
for (const auto& state: g_audioout2_ports) {
|
// A port's PCM covers one grain: the push consumes it, so a port the game stops feeding
|
||||||
if (!state.used || state.context != ctx || state.pcm_data.empty()) {
|
// falls silent.
|
||||||
|
for (auto& state: context->ports) {
|
||||||
|
if (!state.used || state.pcm_data.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (audioout2_port_type_is_object(state.port_type)) {
|
if (audioout2_port_type_is_object(state.port_type)) {
|
||||||
audioout2_mix_object(out.object_mix, context->num_grains, state);
|
audioout2_mix_object(out.object_mix, context->num_grains, state);
|
||||||
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
} else if (state.audio_handle > 0 && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||||
const auto i = params.size();
|
const auto i = params.size();
|
||||||
out.pcm[i] = state.pcm_data;
|
out.pcm[i].swap(state.pcm_data);
|
||||||
out.gains[i] = state.gains;
|
out.gains[i] = state.gains;
|
||||||
params.push_back({state.audio_handle, out.pcm[i].data(), out.gains[i].data()});
|
params.push_back({state.audio_handle, out.pcm[i].data(), out.gains[i].data()});
|
||||||
}
|
}
|
||||||
|
state.pcm_data.clear();
|
||||||
}
|
}
|
||||||
if (!out.object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
if (!out.object_mix.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
|
||||||
params.push_back({context->object_audio_handle, out.object_mix.data()});
|
params.push_back({context->object_audio_handle, out.object_mix.data()});
|
||||||
@@ -467,22 +489,28 @@ int KYTY_SYSV_ABI AudioOut2ContextResetParam(AudioOut2ContextParam* params) {
|
|||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
|
|
||||||
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
std::memset(params, 0, sizeof(AudioOut2ContextParam));
|
||||||
params->max_ports = 256;
|
params->max_ports = 8;
|
||||||
params->max_object_ports = 256;
|
params->queue_depth = 1;
|
||||||
params->guarantee_object_ports = 0;
|
params->num_grains = 256;
|
||||||
params->queue_depth = 4;
|
|
||||||
params->num_grains = 512;
|
|
||||||
params->flags = 1;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool audioout2_valid_context_capacity(const AudioOut2ContextParam& params) {
|
||||||
|
return params.max_ports <= AUDIOOUT2_CHANNEL_PORTS_MAX &&
|
||||||
|
params.max_object_ports <= AUDIOOUT2_OBJECT_PORTS_MAX &&
|
||||||
|
params.guarantee_object_ports <= params.max_object_ports;
|
||||||
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
int KYTY_SYSV_ABI AudioOut2ContextQueryMemory(const AudioOut2ContextParam* params,
|
||||||
size_t* memory_size) {
|
size_t* memory_size) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
EXIT_NOT_IMPLEMENTED(memory_size == nullptr);
|
||||||
|
if (!audioout2_valid_context_capacity(*params)) {
|
||||||
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
const auto queue_depth = (params->queue_depth == 0 ? 4u : params->queue_depth);
|
||||||
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
*memory_size = 0x10000u + static_cast<size_t>(queue_depth) * 0x590u;
|
||||||
@@ -498,6 +526,9 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
|||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
EXIT_NOT_IMPLEMENTED(ctx == nullptr);
|
||||||
|
if (!audioout2_valid_context_capacity(*params)) {
|
||||||
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
const auto num_grains = params->num_grains == 0 ? 512u : params->num_grains;
|
||||||
const int object_audio_handle =
|
const int object_audio_handle =
|
||||||
@@ -524,8 +555,10 @@ int KYTY_SYSV_ABI AudioOut2ContextCreate(const AudioOut2ContextParam* params, vo
|
|||||||
state->handle = *ctx;
|
state->handle = *ctx;
|
||||||
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
state->queue_depth = params->queue_depth == 0 ? 4u : params->queue_depth;
|
||||||
state->num_grains = num_grains;
|
state->num_grains = num_grains;
|
||||||
state->last_update = LibKernel::KernelGetProcessTime();
|
state->last_update = audioout2_clock();
|
||||||
state->object_audio_handle = object_audio_handle;
|
state->object_audio_handle = object_audio_handle;
|
||||||
|
state->max_ports = params->max_ports;
|
||||||
|
state->ports.resize(params->max_ports + params->max_object_ports);
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
LOGF("\t buffer = 0x%016" PRIx64 "\n"
|
||||||
@@ -541,8 +574,8 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
LOGF("\t ctx = 0x%016" PRIx64 "\n", ctx);
|
||||||
|
|
||||||
std::array<int, 257> audio_handles {};
|
std::array<int, AUDIOOUT2_CHANNEL_PORTS_MAX + 1> audio_handles {};
|
||||||
size_t audio_handles_num = 0;
|
size_t audio_handles_num = 0;
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
auto* state = audioout2_find_context_locked(ctx);
|
auto* state = audioout2_find_context_locked(ctx);
|
||||||
@@ -550,15 +583,12 @@ int KYTY_SYSV_ABI AudioOut2ContextDestroy(AudioOut2ContextHandle ctx) {
|
|||||||
if (state->object_audio_handle > 0) {
|
if (state->object_audio_handle > 0) {
|
||||||
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
audio_handles[audio_handles_num++] = state->object_audio_handle;
|
||||||
}
|
}
|
||||||
*state = AudioOut2ContextState {};
|
for (const auto& port_state: state->ports) {
|
||||||
}
|
if (port_state.used && port_state.audio_handle > 0) {
|
||||||
for (auto& port_state: g_audioout2_ports) {
|
|
||||||
if (port_state.used && port_state.context == ctx) {
|
|
||||||
if (port_state.audio_handle > 0 && audio_handles_num < audio_handles.size()) {
|
|
||||||
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
audio_handles[audio_handles_num++] = port_state.audio_handle;
|
||||||
}
|
}
|
||||||
port_state = AudioOut2PortStateEntry {};
|
|
||||||
}
|
}
|
||||||
|
*state = AudioOut2ContextState {};
|
||||||
}
|
}
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
@@ -586,9 +616,7 @@ int KYTY_SYSV_ABI AudioOut2ContextAdvance(AudioOut2ContextHandle ctx) {
|
|||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
|
||||||
audioout2_update_context_locked(state);
|
audioout2_update_context_locked(state);
|
||||||
}
|
for (auto& port: state->ports) {
|
||||||
for (auto& port: g_audioout2_ports) {
|
|
||||||
if (port.used && port.context == ctx) {
|
|
||||||
port.attributes_locked = false;
|
port.attributes_locked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,7 +640,7 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
|
|||||||
sleep_micros = audioout2_grain_micros(state->num_grains);
|
sleep_micros = audioout2_grain_micros(state->num_grains);
|
||||||
if (state->queued < state->queue_depth || use_device_clock) {
|
if (state->queued < state->queue_depth || use_device_clock) {
|
||||||
if (state->queued == 0) {
|
if (state->queued == 0) {
|
||||||
state->last_update = LibKernel::KernelGetProcessTime();
|
state->last_update = audioout2_clock();
|
||||||
}
|
}
|
||||||
if (state->queued < state->queue_depth) {
|
if (state->queued < state->queue_depth) {
|
||||||
state->queued++;
|
state->queued++;
|
||||||
@@ -663,21 +691,19 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
EXIT_NOT_IMPLEMENTED(params == nullptr);
|
||||||
EXIT_NOT_IMPLEMENTED(port == nullptr);
|
EXIT_NOT_IMPLEMENTED(port == nullptr);
|
||||||
|
|
||||||
const auto next_port = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
const auto audio_format = audioout2_data_format_to_audio_format(params->data_format);
|
||||||
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
const auto audio_type = audioout2_port_type_to_audio_out_type(params->port_type);
|
||||||
|
const bool is_object = audioout2_port_type_is_object(params->port_type);
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
const auto* context_state = audioout2_find_context_locked(ctx);
|
auto* context_state = audioout2_find_context_locked(ctx);
|
||||||
if (context_state == nullptr) {
|
if (context_state == nullptr) {
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
if (audioout2_port_type_is_object(params->port_type) &&
|
if (is_object && (params->port_type != 0x100 || params->sampling_freq != 48000 ||
|
||||||
(params->port_type != 0x100 || context_state->object_audio_handle == 0 ||
|
(audio_format != AudioInternal::Format::FloatMono &&
|
||||||
params->sampling_freq != 48000 ||
|
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
||||||
(audio_format != AudioInternal::Format::FloatMono &&
|
|
||||||
audio_format != AudioInternal::Format::Signed16bitMono))) {
|
|
||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
@@ -688,42 +714,45 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
g_audioout2_state_mutex.Unlock();
|
g_audioout2_state_mutex.Unlock();
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
AudioOut2PortStateEntry* port_state = nullptr;
|
auto slots = std::span(context_state->ports);
|
||||||
for (auto& candidate: g_audioout2_ports) {
|
slots =
|
||||||
if (!candidate.used) {
|
is_object ? slots.subspan(context_state->max_ports) : slots.first(context_state->max_ports);
|
||||||
port_state = &candidate;
|
const auto available = std::find_if(slots.begin(), slots.end(),
|
||||||
break;
|
[](const auto& candidate) { return !candidate.used; });
|
||||||
}
|
if (available == slots.end()) {
|
||||||
}
|
g_audioout2_state_mutex.Unlock();
|
||||||
if (port_state != nullptr) {
|
|
||||||
*port_state = AudioOut2PortStateEntry {};
|
|
||||||
port_state->used = true;
|
|
||||||
port_state->handle = next_port;
|
|
||||||
port_state->context = ctx;
|
|
||||||
port_state->user = params->user_handle;
|
|
||||||
port_state->port_type = params->port_type;
|
|
||||||
port_state->data_format = params->data_format;
|
|
||||||
port_state->sampling_freq = params->sampling_freq;
|
|
||||||
port_state->samples_num = samples_num;
|
|
||||||
port_state->audio_format = audio_format;
|
|
||||||
port_state->gains.fill(1.0f);
|
|
||||||
}
|
|
||||||
g_audioout2_state_mutex.Unlock();
|
|
||||||
|
|
||||||
if (port_state == nullptr) {
|
|
||||||
return AUDIO_OUT2_ERROR_PORT_FULL;
|
return AUDIO_OUT2_ERROR_PORT_FULL;
|
||||||
}
|
}
|
||||||
|
const auto serial = g_audioout2_next_port.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
const auto context_slot = static_cast<uint64_t>(context_state - g_audioout2_contexts.data());
|
||||||
|
const auto port_slot = static_cast<uint64_t>(&*available - context_state->ports.data());
|
||||||
|
const auto next_port = (serial << (AUDIOOUT2_PORT_SLOT_BITS + AUDIOOUT2_CONTEXT_SLOT_BITS)) |
|
||||||
|
(context_slot << AUDIOOUT2_PORT_SLOT_BITS) | port_slot;
|
||||||
|
auto* port_state = &*available;
|
||||||
|
*port_state = AudioOut2PortStateEntry {};
|
||||||
|
port_state->used = true;
|
||||||
|
port_state->handle = next_port;
|
||||||
|
port_state->context = ctx;
|
||||||
|
port_state->user = params->user_handle;
|
||||||
|
port_state->port_type = params->port_type;
|
||||||
|
port_state->data_format = params->data_format;
|
||||||
|
port_state->sampling_freq = params->sampling_freq;
|
||||||
|
port_state->samples_num = samples_num;
|
||||||
|
port_state->audio_format = audio_format;
|
||||||
|
port_state->gains.fill(1.0f);
|
||||||
|
g_audioout2_state_mutex.Unlock();
|
||||||
|
|
||||||
int audio_handle = 0;
|
int audio_handle = 0;
|
||||||
|
|
||||||
if (audio_format != AudioInternal::Format::Unknown &&
|
if (audio_format != AudioInternal::Format::Unknown && !is_object) {
|
||||||
!audioout2_port_type_is_object(params->port_type)) {
|
|
||||||
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
audio_handle = AudioInternal::AudioOutOpen(audio_type, samples_num, params->sampling_freq,
|
||||||
audio_format);
|
audio_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_audioout2_state_mutex.Lock();
|
g_audioout2_state_mutex.Lock();
|
||||||
const bool reserved = port_state->used && port_state->handle == next_port;
|
// Context destruction may have freed or replaced the slots while the backend opened.
|
||||||
|
port_state = audioout2_find_port_locked(next_port);
|
||||||
|
const bool reserved = port_state != nullptr;
|
||||||
if (reserved) {
|
if (reserved) {
|
||||||
port_state->audio_handle = audio_handle;
|
port_state->audio_handle = audio_handle;
|
||||||
}
|
}
|
||||||
@@ -735,7 +764,7 @@ int KYTY_SYSV_ABI AudioOut2PortCreate(AudioOut2ContextHandle ctx, const AudioOut
|
|||||||
|
|
||||||
*port = next_port;
|
*port = next_port;
|
||||||
|
|
||||||
if (next_port <= 16 || (next_port % 600) == 0) {
|
if (serial <= 16 || (serial % 600) == 0) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
LOGF("\t ctx = 0x%016" PRIx64 "\n"
|
||||||
"\t port = 0x%016" PRIx64 "\n"
|
"\t port = 0x%016" PRIx64 "\n"
|
||||||
@@ -901,21 +930,23 @@ int KYTY_SYSV_ABI AudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
|||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
LOGF("\t handle = 0x%016" PRIx64 "\n", static_cast<uint64_t>(handle));
|
||||||
Common::LockGuard lock(g_audioout2_state_mutex);
|
Common::LockGuard lock(g_audioout2_state_mutex);
|
||||||
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
const auto it = std::find(g_audioout2_users.begin(), g_audioout2_users.end(), handle);
|
||||||
if (it == g_audioout2_users.end()) {
|
if (it == g_audioout2_users.end()) {
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
if (std::any_of(g_audioout2_ports.begin(), g_audioout2_ports.end(),
|
for (const auto& context: g_audioout2_contexts) {
|
||||||
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
if (std::any_of(context.ports.begin(), context.ports.end(),
|
||||||
return AUDIO_OUT2_ERROR_BUSY;
|
[handle](const auto& port) { return port.used && port.user == handle; })) {
|
||||||
|
return AUDIO_OUT2_ERROR_BUSY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_audioout2_users.erase(it);
|
g_audioout2_users.erase(it);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
int KYTY_SYSV_ABI AudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle,
|
||||||
uint32_t* context_attributes,
|
uint32_t* context_attributes,
|
||||||
uint32_t* port_attributes) {
|
uint32_t* port_attributes) {
|
||||||
if (context_attributes == nullptr || port_attributes == nullptr) {
|
if (context_attributes == nullptr || port_attributes == nullptr) {
|
||||||
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
return AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-10
@@ -38,8 +38,8 @@ struct Global {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Shadow {
|
struct Shadow {
|
||||||
uintptr_t start = 0;
|
uintptr_t start = 0;
|
||||||
int count = 0;
|
uint64_t covered_bytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AsanContext {
|
struct AsanContext {
|
||||||
@@ -50,6 +50,14 @@ struct AsanContext {
|
|||||||
static uint32_t g_fake_stack = 0;
|
static uint32_t g_fake_stack = 0;
|
||||||
static AsanContext* g_ctx = nullptr;
|
static AsanContext* g_ctx = nullptr;
|
||||||
|
|
||||||
|
// Allocation and release callbacks can partition the same guest range differently.
|
||||||
|
static uint64_t ShadowCoverage(uintptr_t page, uintptr_t min_addr, uintptr_t max_addr) {
|
||||||
|
const auto guest_start = (page - 0x10000000000ull) << 3u;
|
||||||
|
const auto guest_end = guest_start + 0x80000ull - 1;
|
||||||
|
return std::min<uintptr_t>(max_addr, guest_end) - std::max<uintptr_t>(min_addr, guest_start) +
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
|
||||||
static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
||||||
EXIT_IF(g_ctx == nullptr);
|
EXIT_IF(g_ctx == nullptr);
|
||||||
|
|
||||||
@@ -67,10 +75,11 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
|||||||
static_cast<uint64_t>(min_shadow), static_cast<uint64_t>(size));
|
static_cast<uint64_t>(min_shadow), static_cast<uint64_t>(size));
|
||||||
|
|
||||||
for (uintptr_t page = min_shadow; page <= max_shadow; page += page_size) {
|
for (uintptr_t page = min_shadow; page <= max_shadow; page += page_size) {
|
||||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||||
[page](const Shadow& s) { return s.start == page; });
|
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||||
|
[page](const Shadow& s) { return s.start == page; });
|
||||||
if (it != g_ctx->shadows.end()) {
|
if (it != g_ctx->shadows.end()) {
|
||||||
it->count++;
|
it->covered_bytes += bytes;
|
||||||
} else {
|
} else {
|
||||||
if (!Common::VirtualMemory::AllocFixed(page, page_size,
|
if (!Common::VirtualMemory::AllocFixed(page, page_size,
|
||||||
Common::VirtualMemory::Mode::ReadWrite)) {
|
Common::VirtualMemory::Mode::ReadWrite)) {
|
||||||
@@ -78,8 +87,8 @@ static void AllocShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shadow s;
|
Shadow s;
|
||||||
s.start = page;
|
s.start = page;
|
||||||
s.count = 1;
|
s.covered_bytes = bytes;
|
||||||
g_ctx->shadows.push_back(s);
|
g_ctx->shadows.push_back(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,14 +114,15 @@ static void FreeShadow(uintptr_t min_addr, uintptr_t max_addr) {
|
|||||||
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
const auto it = std::find_if(g_ctx->shadows.begin(), g_ctx->shadows.end(),
|
||||||
[page](const Shadow& s) { return s.start == page; });
|
[page](const Shadow& s) { return s.start == page; });
|
||||||
if (it != g_ctx->shadows.end()) {
|
if (it != g_ctx->shadows.end()) {
|
||||||
it->count--;
|
const auto bytes = ShadowCoverage(page, min_addr, max_addr);
|
||||||
|
if (bytes >= it->covered_bytes) {
|
||||||
if (it->count <= 0) {
|
|
||||||
if (!Common::VirtualMemory::Free(page)) {
|
if (!Common::VirtualMemory::Free(page)) {
|
||||||
EXIT("can't free shadow memory\n");
|
EXIT("can't free shadow memory\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
g_ctx->shadows.erase(it);
|
g_ctx->shadows.erase(it);
|
||||||
|
} else {
|
||||||
|
it->covered_bytes -= bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +136,14 @@ static void KernelFree(uintptr_t addr, size_t size) {
|
|||||||
FreeShadow(addr, addr + size - 1);
|
FreeShadow(addr, addr + size - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
||||||
|
void TestRegisterMemoryCallbacks() {
|
||||||
|
EXIT_IF(g_ctx != nullptr);
|
||||||
|
g_ctx = new AsanContext;
|
||||||
|
LibKernel::Memory::RegisterCallbacks(KernelAlloc, KernelFree);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void KYTY_SYSV_ABI asan_init() {
|
static void KYTY_SYSV_ABI asan_init() {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user