mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
131
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
83f8bfd3c9 | ||
|
|
dd5bc26c64 | ||
|
|
72903bfce6 | ||
|
|
59d8256b36 | ||
|
|
9a20c6f738 | ||
|
|
67c89e0915 | ||
|
|
5d8381e0f3 | ||
|
|
e864cb20bd | ||
|
|
40aaab482e | ||
|
|
ec8b75b244 | ||
|
|
9210264d47 | ||
|
|
66e21a6245 | ||
|
|
87e49282b8 | ||
|
|
1816004357 | ||
|
|
bfc377627a | ||
|
|
4b651f2ba7 | ||
|
|
8c01d8e849 | ||
|
|
e0c5ae090c | ||
|
|
906d25568f | ||
|
|
8291cfeb04 | ||
|
|
dbc7b8b2ff | ||
|
|
269e411ef2 | ||
|
|
170c107779 | ||
|
|
8dc0d56b7e | ||
|
|
d7856753e0 | ||
|
|
bdf48dcf3b | ||
|
|
f356fa3730 | ||
|
|
07f6420273 | ||
|
|
6aaa533296 | ||
|
|
dc40478e7b | ||
|
|
56a82bcd41 | ||
|
|
bf5dd62eea | ||
|
|
227f12ee4b | ||
|
|
246d511568 | ||
|
|
ba0e93daac | ||
|
|
77a8f30758 | ||
|
|
1ffaaa96b1 | ||
|
|
706aa030ad | ||
|
|
ecc30a1f2d | ||
|
|
3688412dca | ||
|
|
45f53236e8 | ||
|
|
9dcff95e80 | ||
|
|
719c44310a | ||
|
|
7bb618d41a | ||
|
|
87e815b250 | ||
|
|
f9e14084d6 | ||
|
|
38c884e8c1 | ||
|
|
27dc7c281f | ||
|
|
79837b423d | ||
|
|
aafe014d0c | ||
|
|
a358b847c1 | ||
|
|
a951a91703 | ||
|
|
a875d1b205 | ||
|
|
f5f4e7d48a | ||
|
|
86fed10b81 | ||
|
|
949de19bb0 | ||
|
|
c513a4a13a | ||
|
|
b54057942d | ||
|
|
b6495a5b67 | ||
|
|
5b315a9ea7 | ||
|
|
5f5d6effa4 | ||
|
|
c5016d9455 | ||
|
|
753f285198 | ||
|
|
d7c1896647 |
No files matched your search
@@ -32,7 +32,7 @@ jobs:
|
||||
windows:
|
||||
name: Build KytyPS5 (Windows)
|
||||
needs: metadata
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-2025
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
Vendored
+22
@@ -42,6 +42,28 @@ if(NOT KYTY_CLANG_CL)
|
||||
endif()
|
||||
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
|
||||
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
|
||||
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
|
||||
+51
-1
@@ -265,6 +265,7 @@ set(gpu_tiler_shader_names
|
||||
promote_d16
|
||||
demote_d16
|
||||
color_transform
|
||||
color_clear
|
||||
)
|
||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
@@ -336,7 +337,7 @@ if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak opus OpenXR::openxr_loader)
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak opus OpenXR::openxr_loader libzstd_static)
|
||||
|
||||
# Linux system libraries.
|
||||
if(LINUX)
|
||||
@@ -406,6 +407,8 @@ add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt xbyak::xbyak)
|
||||
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
|
||||
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
|
||||
@@ -450,6 +453,11 @@ add_executable(lru_cache_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_include_directories(lru_cache_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(slot_vector_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SlotVectorTests.cpp"
|
||||
)
|
||||
target_include_directories(slot_vector_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
||||
@@ -540,6 +548,19 @@ add_executable(png_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/PngEncTests.cpp
|
||||
target_link_libraries(png_enc_tests common fmt::fmt)
|
||||
target_include_directories(png_enc_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(jpeg_dec_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/JpegDecTests.cpp")
|
||||
target_link_libraries(jpeg_dec_tests common fmt::fmt)
|
||||
target_include_directories(jpeg_dec_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(jpeg_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/JpegEncTests.cpp")
|
||||
target_link_libraries(jpeg_enc_tests common fmt::fmt)
|
||||
target_include_directories(jpeg_enc_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(occlusion_queries_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/OcclusionQueriesTests.cpp")
|
||||
target_link_libraries(occlusion_queries_tests common fmt::fmt Vulkan::Headers)
|
||||
target_include_directories(occlusion_queries_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
||||
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
||||
@@ -631,6 +652,7 @@ if(BUILD_TESTING)
|
||||
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 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 trophy_system COMMAND $<TARGET_FILE:trophy_system_tests>)
|
||||
add_test(NAME avplayer_file COMMAND $<TARGET_FILE:avplayer_file_tests>)
|
||||
@@ -651,6 +673,9 @@ if(BUILD_TESTING)
|
||||
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||
add_test(NAME jpeg_dec COMMAND $<TARGET_FILE:jpeg_dec_tests>)
|
||||
add_test(NAME jpeg_enc COMMAND $<TARGET_FILE:jpeg_enc_tests>)
|
||||
add_test(NAME occlusion_queries COMMAND $<TARGET_FILE:occlusion_queries_tests>)
|
||||
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||
@@ -675,6 +700,14 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME wait_until_safe_for_rendering
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||
add_test(NAME pm4_flip
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||
add_test(NAME depth_bits
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-bits-only)
|
||||
add_test(NAME msaa_storage
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --msaa-storage-only)
|
||||
add_test(NAME pm4_context_state
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||
add_test(NAME gpu_tiler
|
||||
@@ -713,6 +746,7 @@ if(BUILD_TESTING)
|
||||
image_page_table_tests
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
build_flags_tests
|
||||
archive_file_tests
|
||||
trophy_system_tests
|
||||
avplayer_file_tests
|
||||
@@ -735,6 +769,9 @@ if(BUILD_TESTING)
|
||||
host_input_tests
|
||||
save_data_memory_tests
|
||||
png_enc_tests
|
||||
jpeg_dec_tests
|
||||
jpeg_enc_tests
|
||||
occlusion_queries_tests
|
||||
ces_tests
|
||||
save_data_dialog_tests
|
||||
http_uri_parse_tests
|
||||
@@ -778,6 +815,19 @@ add_custom_target(kyty_touch_cursors
|
||||
add_dependencies(kyty_emulator kyty_touch_cursors)
|
||||
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})
|
||||
if (WIN32)
|
||||
target_link_libraries(kyty_emulator opengl32)
|
||||
|
||||
@@ -46,6 +46,16 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
## Screenshots
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Cyberpunk 2077</strong><br>
|
||||
<img src="docs/screenshots/ps5-07.png" width="300" alt="Cyberpunk 2077 running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>MLB The Show 23</strong><br>
|
||||
<img src="docs/screenshots/ps5-08.png" width="300" alt="MLB The Show 23 running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Astro Bot</strong><br>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -109,4 +109,23 @@ std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::optional<AddContentEntry> ParseAddContent(std::string_view value) {
|
||||
static const std::regex label_pattern("^[A-Z0-9]{16}$");
|
||||
const auto eq = value.find('=');
|
||||
const auto label = value.substr(0, eq);
|
||||
if (!std::regex_match(label.begin(), label.end(), label_pattern)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AddContentEntry entry {{}, std::string(label), {}, {}};
|
||||
if (eq != std::string_view::npos) {
|
||||
entry.mount_dir = PathFromUtf8(FixFilenameSlash(value.substr(eq + 1)));
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::is_directory(entry.mount_dir, ec)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -2,7 +2,9 @@
|
||||
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
@@ -10,7 +12,7 @@ namespace Common {
|
||||
struct AddContentEntry {
|
||||
std::string content_id;
|
||||
std::string entitlement_label;
|
||||
std::filesystem::path mount_dir;
|
||||
std::filesystem::path mount_dir; // Empty for an add-on without extra data.
|
||||
std::string title;
|
||||
};
|
||||
|
||||
@@ -18,6 +20,9 @@ struct AddContentEntry {
|
||||
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
const std::string& title_id);
|
||||
|
||||
// Parses a user add-on: "LABEL" without extra data, or "LABEL=DIR" with its data in DIR.
|
||||
std::optional<AddContentEntry> ParseAddContent(std::string_view value);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_ADDITIONAL_CONTENT_H_ */
|
||||
@@ -29,6 +29,9 @@ enum class Counter {
|
||||
TextureUploadBytes,
|
||||
TextureDownloadBytes,
|
||||
TextureEvictions,
|
||||
ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata.
|
||||
ColorMetadataReadbackNs,
|
||||
ColorMetadataChecks, // GPU clear-key checks of DCC/CMASK metadata.
|
||||
BufferUploadBytes,
|
||||
BufferDownloadBytes,
|
||||
BufferEvictions,
|
||||
|
||||
@@ -133,6 +133,10 @@ bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
|
||||
bool ShaderCacheEnabled() {
|
||||
return g_config->shader_cache_enabled;
|
||||
}
|
||||
|
||||
ShaderOptimizationType GetShaderOptimizationType() {
|
||||
return g_config->shader_optimization_type;
|
||||
}
|
||||
@@ -197,6 +201,10 @@ bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
bool OcclusionQueriesEnabled() {
|
||||
return g_config->occlusion_queries_enabled;
|
||||
}
|
||||
|
||||
bool TrophyEnabled() {
|
||||
return g_config->trophy_enabled;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ struct ConfigOptions {
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
bool shader_validation_enabled = false;
|
||||
bool shader_cache_enabled = false;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||
LogDirection shader_log_direction = LogDirection::Silent;
|
||||
std::filesystem::path shader_log_folder = "_Shaders";
|
||||
@@ -88,6 +89,7 @@ struct ConfigOptions {
|
||||
bool readback_tiled_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool occlusion_queries_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
uint32_t trophy_duration_seconds = 6;
|
||||
bool perf_overlay_enabled = false;
|
||||
@@ -131,6 +133,7 @@ uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
bool ShaderValidationEnabled();
|
||||
bool ShaderCacheEnabled();
|
||||
ShaderOptimizationType GetShaderOptimizationType();
|
||||
LogDirection GetShaderLogDirection();
|
||||
std::filesystem::path GetShaderLogFolder();
|
||||
@@ -154,6 +157,7 @@ bool ReadbackLinearImagesEnabled();
|
||||
bool ReadbackTiledImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool OcclusionQueriesEnabled();
|
||||
bool TrophyEnabled();
|
||||
uint32_t GetTrophyDurationSeconds();
|
||||
bool PlayGoHackEnabled();
|
||||
|
||||
+52
-52
@@ -1,15 +1,15 @@
|
||||
#ifndef EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
#define EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -19,105 +19,105 @@ struct SlotId {
|
||||
static constexpr uint32_t INVALID_INDEX = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
constexpr SlotId() noexcept = default;
|
||||
constexpr SlotId(uint32_t value) noexcept: index(value), generation(1) {}
|
||||
constexpr SlotId(uint32_t value, uint32_t slot_generation) noexcept
|
||||
: index(value), generation(slot_generation) {}
|
||||
constexpr SlotId(uint32_t index) noexcept: index(index) {}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept { return index != INVALID_INDEX; }
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept {
|
||||
return index != INVALID_INDEX;
|
||||
}
|
||||
constexpr auto operator<=>(const SlotId&) const noexcept = default;
|
||||
|
||||
uint32_t index = INVALID_INDEX;
|
||||
uint32_t generation = 0;
|
||||
};
|
||||
|
||||
// Stable-address slot storage for cache resources that are intentionally non-movable.
|
||||
// Fixed-size chunks keep the stored, non-movable resources in place. Occupancy is a byte per
|
||||
// slot, not a bitset: a deferred erase runs while fault threads read other slots.
|
||||
template <typename T>
|
||||
class SlotVector {
|
||||
static constexpr uint32_t ChunkSize = 256;
|
||||
|
||||
public:
|
||||
SlotVector() = default;
|
||||
KYTY_CLASS_NO_COPY(SlotVector);
|
||||
|
||||
~SlotVector() noexcept {
|
||||
ForEach([](SlotId, T& value) { value.~T(); });
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator[](SlotId id) noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
return *m_values[id.index].value;
|
||||
return m_chunks[id.index / ChunkSize][id.index % ChunkSize].object;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
return *m_values[id.index].value;
|
||||
return m_chunks[id.index / ChunkSize][id.index % ChunkSize].object;
|
||||
}
|
||||
|
||||
[[nodiscard]] T* try_get(SlotId id) noexcept {
|
||||
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
|
||||
return is_allocated(id) ? &(*this)[id] : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* try_get(SlotId id) const noexcept {
|
||||
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
|
||||
return is_allocated(id) ? &(*this)[id] : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_allocated(SlotId id) const noexcept {
|
||||
return id && id.index < m_values.size() &&
|
||||
m_values[id.index].generation == id.generation && m_values[id.index].value.has_value();
|
||||
return id.index < m_stored.size() && m_stored[id.index] != 0;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
[[nodiscard]] SlotId insert(Args&&... args) {
|
||||
uint32_t index = 0;
|
||||
if (m_free_list.empty()) {
|
||||
index = static_cast<uint32_t>(m_values.size());
|
||||
m_values.emplace_back();
|
||||
m_values.back().value.emplace(std::forward<Args>(args)...);
|
||||
} else {
|
||||
index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
EXIT_IF(m_values[index].value.has_value());
|
||||
m_values[index].value.emplace(std::forward<Args>(args)...);
|
||||
Reserve();
|
||||
}
|
||||
++m_size;
|
||||
return SlotId {index, m_values[index].generation};
|
||||
const uint32_t index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
new (&(*this)[index]) T(std::forward<Args>(args)...);
|
||||
m_stored[index] = 1;
|
||||
return SlotId {index};
|
||||
}
|
||||
|
||||
void erase(SlotId id) noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
auto& slot = m_values[id.index];
|
||||
slot.value.reset();
|
||||
if (++slot.generation == 0) {
|
||||
slot.generation = 1;
|
||||
}
|
||||
(*this)[id].~T();
|
||||
m_stored[id.index] = 0;
|
||||
m_free_list.push_back(id.index);
|
||||
--m_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t size() const noexcept { return m_size; }
|
||||
[[nodiscard]] size_t capacity() const noexcept { return m_values.size(); }
|
||||
|
||||
template <typename F>
|
||||
void ForEach(F&& fn) {
|
||||
for (uint32_t index = 0; index < m_values.size(); ++index) {
|
||||
if (m_values[index].value) {
|
||||
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
|
||||
for (uint32_t index = 0; index < m_stored.size(); ++index) {
|
||||
if (m_stored[index] != 0) {
|
||||
fn(SlotId {index}, (*this)[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void ForEach(F&& fn) const {
|
||||
for (uint32_t index = 0; index < m_values.size(); ++index) {
|
||||
if (m_values[index].value) {
|
||||
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
|
||||
for (uint32_t index = 0; index < m_stored.size(); ++index) {
|
||||
if (m_stored[index] != 0) {
|
||||
fn(SlotId {index}, (*this)[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
std::optional<T> value;
|
||||
uint32_t generation = 1;
|
||||
union Slot {
|
||||
Slot() noexcept {}
|
||||
~Slot() noexcept {}
|
||||
|
||||
T object;
|
||||
};
|
||||
|
||||
std::deque<Slot> m_values;
|
||||
std::vector<uint32_t> m_free_list;
|
||||
size_t m_size = 0;
|
||||
void Reserve() {
|
||||
const auto first = static_cast<uint32_t>(m_stored.size());
|
||||
m_chunks.push_back(std::make_unique<Slot[]>(ChunkSize));
|
||||
m_stored.resize(first + ChunkSize);
|
||||
m_free_list.resize(ChunkSize);
|
||||
std::iota(m_free_list.rbegin(), m_free_list.rend(), first);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Slot[]>> m_chunks;
|
||||
std::vector<uint8_t> m_stored;
|
||||
std::vector<uint32_t> m_free_list;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -125,7 +125,7 @@ private:
|
||||
template <>
|
||||
struct std::hash<Common::SlotId> {
|
||||
[[nodiscard]] size_t operator()(Common::SlotId id) const noexcept {
|
||||
return std::hash<uint64_t> {}((static_cast<uint64_t>(id.generation) << 32u) | id.index);
|
||||
return std::hash<uint32_t> {}(id.index);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-5
@@ -99,10 +99,6 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
||||
|
||||
if (Common::File::IsFileExisting(param_json)) {
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
if (const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
flexible_memory_size != 0) {
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
if (cfg.splash_enabled) {
|
||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||
@@ -188,7 +184,7 @@ void Run(const RunOptions& options) {
|
||||
|
||||
MountSandboxDirs();
|
||||
|
||||
Loader::AddContentLoad(options.addcont_dir, title_id);
|
||||
Loader::AddContentLoad(options.addcont_dir, options.addcont, title_id);
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
Libs::InitAll(rt->Symbols());
|
||||
|
||||
|
||||
+7
-5
@@ -1,6 +1,7 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_EMULATOR_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_EMULATOR_H_
|
||||
|
||||
#include "common/additionalContent.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
@@ -9,11 +10,12 @@
|
||||
namespace Emulator {
|
||||
|
||||
struct RunOptions {
|
||||
Config::ConfigOptions config;
|
||||
std::filesystem::path app0_dir;
|
||||
std::filesystem::path elf;
|
||||
std::filesystem::path game_patch;
|
||||
std::filesystem::path addcont_dir;
|
||||
Config::ConfigOptions config;
|
||||
std::filesystem::path app0_dir;
|
||||
std::filesystem::path elf;
|
||||
std::filesystem::path game_patch;
|
||||
std::filesystem::path addcont_dir;
|
||||
std::vector<Common::AddContentEntry> addcont;
|
||||
};
|
||||
|
||||
void Run(const RunOptions& options);
|
||||
|
||||
@@ -27,6 +27,7 @@ struct IndirectDrawRegisters {
|
||||
uint32_t vertex_offset;
|
||||
uint32_t instance_offset;
|
||||
uint32_t index_offset;
|
||||
uint32_t draw_index;
|
||||
};
|
||||
|
||||
class Pm4Execution {
|
||||
@@ -50,13 +51,6 @@ private:
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
struct FlipInfo {
|
||||
int handle = 0;
|
||||
int index = 0;
|
||||
int flip_mode = 0;
|
||||
int64_t flip_arg = 0;
|
||||
};
|
||||
|
||||
CommandProcessor(RenderContext& renderer, int interrupt_event_id)
|
||||
: m_renderer(renderer), m_interrupt_event_id(interrupt_event_id) {}
|
||||
~CommandProcessor() = default;
|
||||
@@ -90,43 +84,21 @@ public:
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
IndirectDrawRegisters registers, uint32_t draw_initiator, bool indexed);
|
||||
void WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index,
|
||||
uint32_t event_write_source, void* dst_gpu_addr, uint32_t value,
|
||||
uint32_t interrupt_selector, uint32_t interrupt_context_id = 0);
|
||||
void WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index,
|
||||
uint32_t event_write_source, void* dst_gpu_addr, uint64_t value,
|
||||
uint32_t interrupt_selector, uint32_t interrupt_context_id = 0);
|
||||
void Flip();
|
||||
void Flip(void* dst_gpu_addr, uint32_t value);
|
||||
void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr,
|
||||
uint32_t value);
|
||||
void ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address, uint64_t data,
|
||||
uint32_t interrupt_context_id);
|
||||
void Flip(int handle, int index, int flip_mode, int64_t flip_arg);
|
||||
void PrepareCpuFlip(uint64_t request_id);
|
||||
void SynchronizeGpu();
|
||||
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
|
||||
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint64_t args_addr, uint32_t mode);
|
||||
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index, uint64_t event_address = 0);
|
||||
|
||||
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
|
||||
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
|
||||
|
||||
void ResetDeCe();
|
||||
void SetCeComplete(bool complete) { m_ce_complete = complete; }
|
||||
void WaitCe();
|
||||
void WaitDeDiff(uint32_t diff);
|
||||
void WaitForRewind(bool valid);
|
||||
void IncrementDe();
|
||||
void IncrementCe();
|
||||
|
||||
void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num);
|
||||
void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num);
|
||||
|
||||
template <typename T>
|
||||
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 WriteReferenceClock(uint64_t dst_address, uint32_t num_bytes);
|
||||
void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
|
||||
@@ -140,45 +112,34 @@ public:
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
|
||||
void ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain);
|
||||
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
[[nodiscard]] bool IsAsyncComputeQueue() const { return m_interrupt_event_id >= 0x20; }
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
|
||||
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution);
|
||||
void SuspendPm4();
|
||||
uint32_t NumInstances();
|
||||
const void* IndexAddress(uint32_t first_index) const;
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::Context m_saved_ctx;
|
||||
bool m_context_state_pushed = false;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
HW::UserSgprType m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
uint32_t m_index_type_and_size = 0;
|
||||
uint32_t m_index_buffer_size = 0;
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
// Persistent draw state: indirect draws update it for subsequent draws.
|
||||
uint32_t m_num_instances = 1;
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::Context m_saved_ctx;
|
||||
bool m_context_state_pushed = false;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
uint32_t m_index_type_and_size = 0;
|
||||
uint32_t m_index_buffer_size = 0;
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
// Persistent draw state. Indirect draws leave the address of their last instance count,
|
||||
// read only when a later draw inherits it.
|
||||
uint32_t m_num_instances = 1;
|
||||
uint64_t m_num_instances_addr = 0;
|
||||
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = false;
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
FlipInfo m_flip;
|
||||
const int m_interrupt_event_id;
|
||||
uint64_t m_submit_id = 0;
|
||||
uint64_t m_synthetic_occlusion_counter = 0;
|
||||
|
||||
@@ -199,10 +199,10 @@ constexpr auto MakeOpcodeDispatchTable() {
|
||||
g_cp_op_func[Pm4::IT_INDIRECT_BUFFER] = CpOpIndirectBuffer;
|
||||
g_cp_op_func[Pm4::IT_COPY_DATA] = CpOpCopyData;
|
||||
g_cp_op_func[Pm4::IT_EVENT_WRITE] = CpOpEventWrite;
|
||||
g_cp_op_func[Pm4::IT_EVENT_WRITE_EOP] = CpOpEventWriteEop;
|
||||
g_cp_op_func[Pm4::IT_EVENT_WRITE_EOS] = CpOpEventWriteEos;
|
||||
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
|
||||
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
|
||||
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_SET_CONTEXT_REG] = CpOpSetContextReg;
|
||||
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
|
||||
@@ -211,12 +211,6 @@ constexpr auto MakeOpcodeDispatchTable() {
|
||||
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG_INDIRECT] = CpOpIndirectCxRegs;
|
||||
g_cp_op_func[Pm4::IT_SET_SH_REG_INDIRECT] = CpOpIndirectShRegs;
|
||||
g_cp_op_func[Pm4::IT_SET_UCONFIG_REG_INDIRECT] = CpOpIndirectUcRegs;
|
||||
g_cp_op_func[Pm4::IT_WRITE_CONST_RAM] = CpOpWriteConstRam;
|
||||
g_cp_op_func[Pm4::IT_DUMP_CONST_RAM] = CpOpDumpConstRam;
|
||||
g_cp_op_func[Pm4::IT_INCREMENT_CE_COUNTER] = CpOpIncrementCeCounter;
|
||||
g_cp_op_func[Pm4::IT_INCREMENT_DE_COUNTER] = CpOpIncrementDeCounter;
|
||||
g_cp_op_func[Pm4::IT_WAIT_ON_CE_COUNTER] = CpOpWaitOnCeCounter;
|
||||
g_cp_op_func[Pm4::IT_WAIT_ON_DE_COUNTER_DIFF] = CpOpWaitOnDeCounterDiff;
|
||||
g_cp_op_func[Pm4::IT_GET_LOD_STATS] = CpOpGetLodStats;
|
||||
g_cp_op_func[Pm4::IT_WAIT_REG_MEM_64] = CpOpWaitRegMem64;
|
||||
|
||||
@@ -231,7 +225,6 @@ constexpr auto MakeCustomOpcodeDispatchTable() {
|
||||
g_cp_op_custom_func[Pm4::R_PUSH_MARKER] = CpOpPushMarker;
|
||||
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
|
||||
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
|
||||
g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem;
|
||||
g_cp_op_custom_func[Pm4::R_CONTEXT_STATE] = CpOpContextState;
|
||||
|
||||
return g_cp_op_custom_func;
|
||||
|
||||
@@ -133,22 +133,16 @@ uint32_t CpOpWriteData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, u
|
||||
uint32_t CpOpIndirectBuffer(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpCopyData(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpEventWrite(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpEventWriteEop(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpEventWriteEos(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 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 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 CpOpIndirectCxRegs(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndirectShRegs(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndirectUcRegs(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWriteConstRam(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDumpConstRam(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIncrementCeCounter(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIncrementDeCounter(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitOnCeCounter(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitOnDeCounterDiff(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpGetLodStats(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDispatchReset(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitRegMem32(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
@@ -157,7 +151,6 @@ uint32_t CpOpPushMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t,
|
||||
uint32_t CpOpPopMarker(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitRegMem64(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpFlip(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpReleaseMem(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -54,34 +54,12 @@ namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t GcrGl2MetadataInvalidate = 1u << 1u;
|
||||
constexpr uint32_t GcrGl0VectorInvalidate = 1u << 2u;
|
||||
constexpr uint32_t GcrGl1Invalidate = 1u << 3u;
|
||||
constexpr uint32_t GcrGl2Unshared = 1u << 4u;
|
||||
constexpr uint32_t GcrGl2Invalidate = 1u << 8u;
|
||||
constexpr uint32_t GcrGl2Writeback = 1u << 9u;
|
||||
constexpr uint32_t GcrOrder012 = 1u << 10u;
|
||||
constexpr uint32_t GcrOrder210 = 2u << 10u;
|
||||
constexpr uint32_t GcrKnownMask = GcrGl2MetadataInvalidate | GcrGl0VectorInvalidate |
|
||||
GcrGl1Invalidate | GcrGl2Unshared | GcrGl2Invalidate |
|
||||
GcrGl2Writeback | GcrOrder012 | GcrOrder210;
|
||||
constexpr uint32_t RegisterSelectorMask = 0x70000000u;
|
||||
constexpr uint32_t RegisterSelectorMask = 0x70000000u;
|
||||
|
||||
constexpr uint32_t NormalizeRegisterOffset(uint32_t raw_offset) {
|
||||
return raw_offset & ~RegisterSelectorMask;
|
||||
}
|
||||
|
||||
uint32_t ReleaseMemCacheActionFromGcr(uint32_t gcr_cntl) {
|
||||
return ((gcr_cntl & GcrGl2Writeback) != 0 ? 0x38u : 0x00u);
|
||||
}
|
||||
|
||||
void LogUnknownReleaseMemGcr(uint32_t gcr_cntl) {
|
||||
const uint32_t unknown = (gcr_cntl & ~GcrKnownMask);
|
||||
if (unknown != 0) {
|
||||
LOGF("\t warning: release_mem uses unknown GCR bits: 0x%04" PRIx32 "\n", unknown);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static HW::BlendControl DecodeBlendControl(uint32_t value) {
|
||||
@@ -1068,9 +1046,8 @@ KYTY_HW_SH_PARSER(HwShSetCsUserSgpr) {
|
||||
auto write_num = UserSgprWriteNum(slot, reg_num, 16u, "cs");
|
||||
|
||||
for (uint32_t i = 0; i < write_num; i++) {
|
||||
cp.GetShCtx().SetCsUserSgpr(slot + i, buffer[i], cp.GetUserDataMarker());
|
||||
cp.GetShCtx().SetCsUserSgpr(slot + i, buffer[i]);
|
||||
}
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
|
||||
return reg_num;
|
||||
}
|
||||
@@ -1085,9 +1062,8 @@ KYTY_HW_SH_PARSER(HwShSetPsUserSgpr) {
|
||||
auto write_num = UserSgprWriteNum(slot, reg_num, 32u, "ps");
|
||||
|
||||
for (uint32_t i = 0; i < write_num; i++) {
|
||||
cp.GetShCtx().SetPsUserSgpr(slot + i, buffer[i], cp.GetUserDataMarker());
|
||||
cp.GetShCtx().SetPsUserSgpr(slot + i, buffer[i]);
|
||||
}
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
|
||||
return reg_num;
|
||||
}
|
||||
@@ -1102,9 +1078,8 @@ KYTY_HW_SH_PARSER(HwShSetGsUserSgpr) {
|
||||
auto write_num = UserSgprWriteNum(slot, reg_num, 32u, "gs");
|
||||
|
||||
for (uint32_t i = 0; i < write_num; i++) {
|
||||
cp.GetShCtx().SetGsUserSgpr(slot + i, buffer[i], cp.GetUserDataMarker());
|
||||
cp.GetShCtx().SetGsUserSgpr(slot + i, buffer[i]);
|
||||
}
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
|
||||
return reg_num;
|
||||
}
|
||||
@@ -1119,9 +1094,8 @@ KYTY_HW_SH_PARSER(HwShSetHsUserSgpr) {
|
||||
auto write_num = UserSgprWriteNum(slot, reg_num, 32u, "hs");
|
||||
|
||||
for (uint32_t i = 0; i < write_num; i++) {
|
||||
cp.GetShCtx().SetHsUserSgpr(slot + i, buffer[i], cp.GetUserDataMarker());
|
||||
cp.GetShCtx().SetHsUserSgpr(slot + i, buffer[i]);
|
||||
}
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
|
||||
return reg_num;
|
||||
}
|
||||
@@ -1608,7 +1582,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirect) {
|
||||
// The native indexed packet enables the high-half first-index destination in word 3.
|
||||
const IndirectDrawRegisters registers {
|
||||
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);
|
||||
|
||||
@@ -1628,10 +1602,12 @@ KYTY_CP_OP_PARSER(CpOpDrawIndirectMulti) {
|
||||
const auto stride_in_bytes = buffer[7];
|
||||
const auto draw_initiator = buffer[8];
|
||||
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 {
|
||||
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) {
|
||||
count_addr = nullptr;
|
||||
@@ -1700,24 +1676,6 @@ KYTY_CP_OP_PARSER(CpOpContextState) {
|
||||
return packet_size_dw - 1u;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpDumpConstRam) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0038300);
|
||||
|
||||
auto offset = buffer[0];
|
||||
auto dw_num = buffer[1];
|
||||
auto* dst = reinterpret_cast<uint32_t*>(buffer[2] | (static_cast<uint64_t>(buffer[3]) << 32u));
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(dw_num >= 0x3000);
|
||||
EXIT_NOT_IMPLEMENTED(offset > 0xbffc);
|
||||
EXIT_NOT_IMPLEMENTED((offset & 0x3u) != 0);
|
||||
|
||||
cp.DumpConstRam(dst, offset, dw_num);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpEventWrite) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -1738,90 +1696,16 @@ KYTY_CP_OP_PARSER(CpOpEventWrite) {
|
||||
return packet_size_dw - 1u;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpEventWriteEop) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0044700);
|
||||
|
||||
uint32_t cache_policy = (buffer[0] >> 25u) & 0x3u;
|
||||
uint32_t event_write_dest = ((buffer[0] >> 23u) & 0x10u) | ((buffer[2] >> 16u) & 0x01u);
|
||||
uint32_t eop_event_type = (buffer[0]) & 0x3fu;
|
||||
uint32_t cache_action = (buffer[0] >> 12u) & 0x3fu;
|
||||
uint32_t event_index = (buffer[0] >> 8u) & 0x7u;
|
||||
uint32_t event_write_source = ((buffer[2] >> 29u) & 0x7u);
|
||||
uint32_t interrupt_selector = (buffer[2] >> 24u) & 0x7u;
|
||||
auto* dst_gpu_addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2] & 0xffffu) << 32u));
|
||||
uint64_t value = (buffer[3] | (static_cast<uint64_t>(buffer[4]) << 32u));
|
||||
|
||||
cp.WriteAtEndOfPipe64(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, dst_gpu_addr, value, interrupt_selector);
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpEventWriteEos) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0034802);
|
||||
|
||||
uint32_t cache_policy = (buffer[0] >> 25u) & 0x3u;
|
||||
uint32_t event_write_dest = 0;
|
||||
uint32_t eop_event_type = (buffer[0]) & 0x3fu;
|
||||
uint32_t cache_action = (buffer[0] >> 12u) & 0x3fu;
|
||||
uint32_t event_index = (buffer[0] >> 8u) & 0x7u;
|
||||
uint32_t event_write_source = ((buffer[2] >> 29u) & 0x7u);
|
||||
uint32_t interrupt_selector = (buffer[2] >> 24u) & 0x7u;
|
||||
|
||||
auto* dst_gpu_addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2] & 0xffffu) << 32u));
|
||||
uint32_t value = buffer[3];
|
||||
|
||||
cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, dst_gpu_addr, value, interrupt_selector);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpFlip) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc004105c);
|
||||
|
||||
CommandProcessor::FlipInfo f;
|
||||
|
||||
f.handle = static_cast<int>(buffer[0]);
|
||||
f.index = static_cast<int>(buffer[1]);
|
||||
f.flip_mode = static_cast<int>(buffer[2]);
|
||||
f.flip_arg = static_cast<int64_t>(buffer[3] | (static_cast<uint64_t>(buffer[4]) << 32u));
|
||||
|
||||
cp.SetFlip(f);
|
||||
cp.Flip();
|
||||
cp.Flip(static_cast<int>(buffer[0]), static_cast<int>(buffer[1]), static_cast<int>(buffer[2]),
|
||||
static_cast<int64_t>(buffer[3] | (static_cast<uint64_t>(buffer[4]) << 32u)));
|
||||
return 5;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpIncrementCeCounter) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008400);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 1);
|
||||
|
||||
cp.IncrementCe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpIncrementDeCounter) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008500);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 0);
|
||||
|
||||
cp.IncrementDe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpIndexType) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -2103,50 +1987,12 @@ KYTY_CP_OP_PARSER(CpOpIndirectUcRegs) {
|
||||
return KYTY_PM4_LEN(cmd_id) - 1u;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpMarker) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
uint32_t len_dw = ((cmd_id >> 16u) & 0x3fffu);
|
||||
|
||||
switch (buffer[0]) {
|
||||
case 0x68750004: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
|
||||
case 0x6875000d: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
|
||||
case 0x68750777: {
|
||||
cp.Flip();
|
||||
break;
|
||||
}
|
||||
case 0x68750778: {
|
||||
auto* addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
|
||||
uint32_t value = buffer[3];
|
||||
cp.Flip(addr, value);
|
||||
break;
|
||||
}
|
||||
case 0x68750781: {
|
||||
auto* addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
|
||||
uint32_t value = buffer[3];
|
||||
uint32_t eop_event_type = buffer[4];
|
||||
uint32_t cache_action = buffer[5];
|
||||
cp.FlipWithInterrupt(eop_event_type, cache_action, addr, value);
|
||||
break;
|
||||
}
|
||||
// NOP payloads are caller-owned data; only complete known marker tags execute.
|
||||
default: break;
|
||||
}
|
||||
|
||||
return len_dw + 1;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpNop) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
auto r = KYTY_PM4_R(cmd_id);
|
||||
|
||||
if (r == Pm4::R_ZERO) {
|
||||
if ((buffer[0] & 0xffff0000u) == 0x68750000u) {
|
||||
return CpOpMarker(cp, cmd_id, buffer, dw, num_dw);
|
||||
}
|
||||
return KYTY_PM4_LEN(cmd_id) - 1;
|
||||
}
|
||||
|
||||
@@ -2202,112 +2048,33 @@ KYTY_CP_OP_PARSER(CpOpPushMarker) {
|
||||
KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0061060);
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0064900);
|
||||
|
||||
uint32_t cache_policy = (buffer[0] >> 25u) & 0x3u;
|
||||
uint32_t eop_event_type = buffer[0] & 0x3fu;
|
||||
uint32_t event_index = (buffer[0] >> 8u) & 0x7u;
|
||||
uint32_t gcr_cntl = (buffer[0] >> 12u) & 0xfffu;
|
||||
uint32_t release_dst = (buffer[1] >> 16u) & 0x3u;
|
||||
uint32_t event_write_dest = 0;
|
||||
uint32_t data_sel = (buffer[1] >> 29u) & 0x7u;
|
||||
uint32_t interrupt_selector = (buffer[1] >> 24u) & 0x7u;
|
||||
auto* dst_gpu_addr =
|
||||
reinterpret_cast<void*>(buffer[2] | (static_cast<uint64_t>(buffer[3]) << 32u));
|
||||
uint64_t value = buffer[4] | (static_cast<uint64_t>(buffer[5]) << 32u);
|
||||
uint32_t interrupt_context_id = buffer[6] & 0x07ffffffu;
|
||||
|
||||
constexpr uint32_t ReleaseMemDstMemory = 0;
|
||||
constexpr uint32_t ReleaseMemDstTcL2 = 1;
|
||||
EXIT_NOT_IMPLEMENTED(release_dst != ReleaseMemDstMemory && release_dst != ReleaseMemDstTcL2);
|
||||
EXIT_NOT_IMPLEMENTED(data_sel != 0 && data_sel != 1 && data_sel != 2 && data_sel != 3 &&
|
||||
data_sel != 5);
|
||||
|
||||
LogUnknownReleaseMemGcr(gcr_cntl);
|
||||
|
||||
const bool gl2_writeback = ((gcr_cntl & GcrGl2Writeback) != 0);
|
||||
|
||||
auto trigger_interrupt = [&]() {
|
||||
switch (interrupt_selector) {
|
||||
case 0x00:
|
||||
case 0x03: break;
|
||||
case 0x01:
|
||||
case 0x02:
|
||||
case 0x04:
|
||||
cp.TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
cp.BufferFlush();
|
||||
break;
|
||||
default: EXIT("unknown release_mem interrupt selector\n");
|
||||
}
|
||||
};
|
||||
|
||||
// Cache actions only order GPU work, which the renderer's resource barriers already order.
|
||||
if (data_sel == 0 || interrupt_selector == 4 ||
|
||||
(release_dst == ReleaseMemDstMemory && dst_gpu_addr == nullptr)) {
|
||||
trigger_interrupt();
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
auto cache_action = ReleaseMemCacheActionFromGcr(gcr_cntl);
|
||||
cache_policy = 0;
|
||||
|
||||
if (data_sel == 1) {
|
||||
eop_event_type = 0x2f;
|
||||
event_index = 6;
|
||||
auto event_source = 2u;
|
||||
|
||||
cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action,
|
||||
event_index, event_source, dst_gpu_addr, static_cast<uint32_t>(value),
|
||||
interrupt_selector, interrupt_context_id);
|
||||
if (interrupt_selector == 0x01 || interrupt_selector == 0x02) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
if (data_sel == 5) {
|
||||
if (gl2_writeback) {
|
||||
cache_action = 0;
|
||||
}
|
||||
|
||||
eop_event_type = 0x2f;
|
||||
event_index = 6;
|
||||
auto event_source = 1u;
|
||||
|
||||
cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action,
|
||||
event_index, event_source, dst_gpu_addr, static_cast<uint32_t>(value),
|
||||
interrupt_selector, interrupt_context_id);
|
||||
if (interrupt_selector == 0x01) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
|
||||
return 7;
|
||||
}
|
||||
|
||||
const bool keep_native_event_index =
|
||||
(eop_event_type == 0x04 && cache_action == 0x00) ||
|
||||
(eop_event_type == 0x2f && event_index == 0x06 && cache_action == 0x38);
|
||||
|
||||
if (data_sel == 2) {
|
||||
if (!keep_native_event_index) {
|
||||
event_index = 0;
|
||||
}
|
||||
} else {
|
||||
EXIT_IF(data_sel != 3);
|
||||
if (!keep_native_event_index) {
|
||||
event_index = 0;
|
||||
}
|
||||
data_sel = 4;
|
||||
}
|
||||
|
||||
cp.WriteAtEndOfPipe64(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
data_sel, dst_gpu_addr, value, interrupt_selector, interrupt_context_id);
|
||||
// The event type, cache actions and destination cache only order GPU work, which the renderer's
|
||||
// resource barriers already order.
|
||||
cp.ReleaseMem(
|
||||
(buffer[1] >> 29u) & 0x7u, (buffer[1] >> 24u) & 0x7u,
|
||||
reinterpret_cast<void*>((buffer[2] & ~0x3u) | (static_cast<uint64_t>(buffer[3]) << 32u)),
|
||||
buffer[4] | (static_cast<uint64_t>(buffer[5]) << 32u), buffer[6] & 0x07ffffffu);
|
||||
|
||||
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_PROFILER_FUNCTION();
|
||||
|
||||
@@ -2524,42 +2291,6 @@ KYTY_CP_OP_PARSER(CpOpWaitRegMem64) {
|
||||
return CpOpWaitRegMemSized<uint64_t>(cp, cmd_id, buffer);
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpWaitOnCeCounter) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008600);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 1);
|
||||
|
||||
cp.WaitCe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpWaitOnDeCounterDiff) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008800);
|
||||
|
||||
cp.WaitDeDiff(buffer[0]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpWriteConstRam) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
auto dw_num = (cmd_id >> 16u) & 0x3fffu;
|
||||
auto offset = buffer[0];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(dw_num >= 0x3000);
|
||||
EXIT_NOT_IMPLEMENTED(offset > 0xbffc);
|
||||
EXIT_NOT_IMPLEMENTED((offset & 0x3u) != 0);
|
||||
|
||||
cp.WriteConstRam(offset, buffer + 1, dw_num);
|
||||
|
||||
return 1 + dw_num;
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpWriteData) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -3519,9 +3250,7 @@ void GraphicsInitJmpTablesShIndirect() {
|
||||
|
||||
for (uint32_t slot = 0; slot < 16; slot++) {
|
||||
g_hw_sh_indirect_func[Pm4::COMPUTE_USER_DATA_0 + slot] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::COMPUTE_USER_DATA_0;
|
||||
cp.GetShCtx().SetCsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetCsUserSgpr(cmd_offset - Pm4::COMPUTE_USER_DATA_0, value);
|
||||
};
|
||||
}
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
@@ -3532,21 +3261,15 @@ void GraphicsInitJmpTablesShIndirect() {
|
||||
for (uint32_t slot = 0; slot < 32; slot++) {
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_PS_0 + slot] =
|
||||
[](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::SPI_SHADER_USER_DATA_PS_0;
|
||||
cp.GetShCtx().SetPsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetPsUserSgpr(cmd_offset - Pm4::SPI_SHADER_USER_DATA_PS_0, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_GS_0 + slot] =
|
||||
[](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::SPI_SHADER_USER_DATA_GS_0;
|
||||
cp.GetShCtx().SetGsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetGsUserSgpr(cmd_offset - Pm4::SPI_SHADER_USER_DATA_GS_0, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_HS_0 + slot] =
|
||||
[](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::SPI_SHADER_USER_DATA_HS_0;
|
||||
cp.GetShCtx().SetHsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetHsUserSgpr(cmd_offset - Pm4::SPI_SHADER_USER_DATA_HS_0, value);
|
||||
};
|
||||
}
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -39,21 +40,6 @@ static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||
|
||||
struct DrawIndirectArgs {
|
||||
uint32_t vertex_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
struct DrawIndexedIndirectArgs {
|
||||
uint32_t index_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_index_location;
|
||||
uint32_t base_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
static bool GraphicsRunDebugDumpEnabled() {
|
||||
return Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
||||
@@ -106,6 +92,12 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::Wake() {
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
m_wake_pending = true;
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::ProcessCommands() {
|
||||
EXIT_IF(!IsGpuThread());
|
||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||
@@ -135,16 +127,14 @@ void GuestGpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
|
||||
done.acquire();
|
||||
}
|
||||
|
||||
void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands) {
|
||||
if (draw_commands.empty()) {
|
||||
void GuestGpu::Submit(std::span<const uint32_t> commands) {
|
||||
if (commands.empty()) {
|
||||
return;
|
||||
}
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = draw_commands;
|
||||
submission.constant_commands = constant_commands;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = commands;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
@@ -206,12 +196,10 @@ void CommandProcessor::Reset() {
|
||||
m_index_buffer_size = 0;
|
||||
m_index_base_addr = 0;
|
||||
m_num_instances = 1;
|
||||
m_num_instances_addr = 0;
|
||||
m_predicate_skip = false;
|
||||
m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
m_draw_indirect_args_base_addr = 0;
|
||||
m_dispatch_indirect_args_base_addr = 0;
|
||||
|
||||
std::memset(m_const_ram, 0, sizeof(m_const_ram));
|
||||
}
|
||||
|
||||
void CommandProcessor::ApplyContextStateOperation(ContextStateOperation operation) {
|
||||
@@ -244,6 +232,8 @@ void CommandProcessor::BufferInit() {
|
||||
|
||||
void CommandProcessor::BufferFlush() {
|
||||
GetScheduler().Flush();
|
||||
// The occlusion counters reach a guest that polls them without waiting for a later fence.
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferWait() {
|
||||
@@ -251,47 +241,12 @@ void CommandProcessor::BufferWait() {
|
||||
GetScheduler().Finish();
|
||||
}
|
||||
|
||||
void CommandProcessor::ResetDeCe() {
|
||||
m_de_count = 0;
|
||||
m_ce_count = 0;
|
||||
m_ce_complete = false;
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitCe() {
|
||||
if (m_ce_count <= m_de_count && !m_ce_complete) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitDeDiff(uint32_t diff) {
|
||||
EXIT_IF(m_de_count > m_ce_count);
|
||||
if (m_ce_count - m_de_count >= diff) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitForRewind(bool valid) {
|
||||
if (!valid) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::IncrementDe() {
|
||||
m_de_count++;
|
||||
}
|
||||
|
||||
void CommandProcessor::IncrementCe() {
|
||||
m_ce_count++;
|
||||
}
|
||||
|
||||
void CommandProcessor::WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num) {
|
||||
memcpy(m_const_ram + offset / 4, src, static_cast<size_t>(dw_num) * 4);
|
||||
}
|
||||
|
||||
void CommandProcessor::DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num) {
|
||||
memcpy(dst, m_const_ram + offset / 4, static_cast<size_t>(dw_num) * 4);
|
||||
}
|
||||
|
||||
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func) {
|
||||
switch (func) {
|
||||
case 0: return true;
|
||||
@@ -326,6 +281,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,
|
||||
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,
|
||||
uint32_t write_control) {
|
||||
const uint32_t dst_sel = ((write_control >> 30u) & 0x1u) | ((write_control >> 7u) & 0x1eu);
|
||||
@@ -478,7 +443,10 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
}
|
||||
if (selected_queue < 0) {
|
||||
gpu->m_processing = false;
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
// A wake that came before this wait must not be lost.
|
||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
}
|
||||
for (auto& queue: gpu->m_queues) {
|
||||
if (!queue.empty()) {
|
||||
queue.front().blocked = false;
|
||||
@@ -543,64 +511,34 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
if (first_slice) {
|
||||
submission.started = true;
|
||||
cp.SetSubmitId(++m_submit_id);
|
||||
cp.ResetDeCe();
|
||||
cp.SetFlip({});
|
||||
}
|
||||
|
||||
cp.BufferInit();
|
||||
bool complete = true;
|
||||
|
||||
switch (submission.type) {
|
||||
case SubmissionType::Graphics: {
|
||||
bool progressed = false;
|
||||
submission.constant_complete |= submission.constant_commands.empty();
|
||||
for (;;) {
|
||||
bool round_progress = false;
|
||||
if (!submission.constant_complete) {
|
||||
submission.constant_complete =
|
||||
cp.Process(submission.constant_execution, submission.constant_commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
round_progress |= submission.constant_execution.MadeProgress();
|
||||
}
|
||||
cp.SetCeComplete(submission.constant_complete);
|
||||
if (!submission.command_complete) {
|
||||
submission.command_complete =
|
||||
cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
round_progress |= submission.command_execution.MadeProgress();
|
||||
}
|
||||
progressed |= round_progress;
|
||||
complete = submission.command_complete && submission.constant_complete;
|
||||
if (complete || !round_progress) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (progressed) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::Compute: {
|
||||
const auto num_dw = static_cast<uint32_t>(submission.commands.size());
|
||||
const auto* buffer = submission.commands.data();
|
||||
static uint32_t compute_batch_log_count = 0;
|
||||
if (first_slice && num_dw <= 128 && compute_batch_log_count++ < 32) {
|
||||
LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(buffer), num_dw);
|
||||
for (uint32_t i = 0; i < std::min<uint32_t>(num_dw, 16); i++) {
|
||||
LOGF("\t compute[%02" PRIu32 "] = 0x%08" PRIx32 "\n", i, buffer[i]);
|
||||
}
|
||||
}
|
||||
case SubmissionType::Compute:
|
||||
if (first_slice) {
|
||||
const auto num_dw = static_cast<uint32_t>(submission.commands.size());
|
||||
const auto* buffer = submission.commands.data();
|
||||
static uint32_t compute_batch_log_count = 0;
|
||||
if (num_dw <= 128 && compute_batch_log_count++ < 32) {
|
||||
LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(buffer), num_dw);
|
||||
for (uint32_t i = 0; i < std::min<uint32_t>(num_dw, 16); i++) {
|
||||
LOGF("\t compute[%02" PRIu32 "] = 0x%08" PRIx32 "\n", i, buffer[i]);
|
||||
}
|
||||
}
|
||||
GraphicsDbgDumpDcb("cc", num_dw, buffer);
|
||||
}
|
||||
complete = cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
if (submission.command_execution.MadeProgress()) {
|
||||
[[fallthrough]];
|
||||
case SubmissionType::Graphics:
|
||||
complete =
|
||||
cp.Process(submission.execution, submission.commands) == Pm4ProcessResult::Complete;
|
||||
if (submission.execution.MadeProgress()) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::FlipPreparation:
|
||||
cp.PrepareCpuFlip(submission.flip_request_id);
|
||||
break;
|
||||
@@ -703,18 +641,6 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution) {
|
||||
opcode, KYTY_PM4_R(packet_header), packet_dw,
|
||||
reinterpret_cast<uint64_t>(packet), packet_header);
|
||||
}
|
||||
if (opcode == Pm4::IT_NOP && KYTY_PM4_R(packet_header) == Pm4::R_RELEASE_MEM &&
|
||||
packet_dw >= 7) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 128) {
|
||||
const auto dst = packet[3] | (static_cast<uint64_t>(packet[4]) << 32u);
|
||||
const auto val = packet[5] | (static_cast<uint64_t>(packet[6]) << 32u);
|
||||
LOGF("\t predicated skip: R_RELEASE_MEM dst=0x%016" PRIx64
|
||||
", value=0x%016" PRIx64 ", action=0x%08" PRIx32
|
||||
", gcr/data/int=0x%08" PRIx32 "\n",
|
||||
dst, val, packet[1], packet[2]);
|
||||
}
|
||||
}
|
||||
cursor.offset_dw += packet_dw;
|
||||
execution.m_made_progress = true;
|
||||
continue;
|
||||
@@ -784,7 +710,16 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
|
||||
num_instances = 1;
|
||||
}
|
||||
|
||||
m_num_instances = num_instances;
|
||||
m_num_instances = num_instances;
|
||||
m_num_instances_addr = 0;
|
||||
}
|
||||
|
||||
uint32_t CommandProcessor::NumInstances() {
|
||||
if (m_num_instances_addr != 0) {
|
||||
m_num_instances = *reinterpret_cast<const uint32_t*>(m_num_instances_addr);
|
||||
m_num_instances_addr = 0;
|
||||
}
|
||||
return m_num_instances;
|
||||
}
|
||||
|
||||
void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
|
||||
@@ -798,6 +733,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
return;
|
||||
case 0x01: {
|
||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
// One begin/end pair per DB; bit 63 marks each counter ready.
|
||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
|
||||
@@ -841,16 +777,12 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) {
|
||||
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
|
||||
args.base_vertex, args.first_instance);
|
||||
args.instance_count = NumInstances();
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
const void* CommandProcessor::IndexAddress(uint32_t first_index) const {
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
@@ -858,16 +790,16 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
case 2: index_size = 1; break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
|
||||
}
|
||||
return reinterpret_cast<const void*>(m_index_base_addr + first_index * index_size);
|
||||
}
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
DrawIndex({.index_count = index_count, .index_addr = IndexAddress(index_offset)});
|
||||
}
|
||||
|
||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||
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) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
return;
|
||||
@@ -879,11 +811,34 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters
|
||||
write(registers.vertex_offset, vertex_offset);
|
||||
write(registers.instance_offset, instance_offset);
|
||||
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,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
const auto args_size =
|
||||
indexed ? sizeof(vk::DrawIndexedIndirectCommand) : sizeof(vk::DrawIndirectCommand);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed);
|
||||
}
|
||||
|
||||
@@ -893,77 +848,64 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u));
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
// The guest records match Vulkan's indirect commands byte for byte.
|
||||
EXIT_NOT_IMPLEMENTED((stride_in_bytes & 3u) != 0 ||
|
||||
stride_in_bytes < (indexed ? sizeof(vk::DrawIndexedIndirectCommand)
|
||||
: sizeof(vk::DrawIndirectCommand)));
|
||||
|
||||
uint32_t draw_count = max_count_or_count;
|
||||
if (count_addr != nullptr) {
|
||||
draw_count = *count_addr;
|
||||
if (draw_count > max_count_or_count) {
|
||||
draw_count = max_count_or_count;
|
||||
}
|
||||
}
|
||||
|
||||
const auto draw_count = count_addr == nullptr
|
||||
? max_count_or_count
|
||||
: std::min(uint32_t {*count_addr}, max_count_or_count);
|
||||
if (draw_count == 0) {
|
||||
return;
|
||||
}
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::IndirectDraws, draw_count);
|
||||
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size);
|
||||
|
||||
uint64_t index_size = 0;
|
||||
if (indexed) {
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
case 1: index_size = 4; break;
|
||||
case 2: index_size = 1; break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
|
||||
}
|
||||
auto& executor = m_renderer.GetRenderExecutor();
|
||||
const auto args_addr = m_draw_indirect_args_base_addr + data_offset;
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// The GPU reads the records if the shaders allow; the VS prolog loads the patched SGPRs from
|
||||
// Vulkan's bases. With no SGPR patched, Vulkan applies the offsets, which matches while they
|
||||
// are zero.
|
||||
DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (IndirectDrawOffsets(registers, records.offsets) && (!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = m_index_buffer_size,
|
||||
.index_addr = IndexAddress(0),
|
||||
.index_type_and_size = m_index_type_and_size,
|
||||
.indirect = records})
|
||||
: executor.DrawAuto(m_submit_id, CurrentBuffer(), {.indirect = records}))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t max_indices = m_index_buffer_size != 0 ? m_index_buffer_size : UINT32_MAX;
|
||||
for (uint32_t i = 0; i < draw_count; i++) {
|
||||
const auto args_addr = m_draw_indirect_args_base_addr + data_offset +
|
||||
static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
|
||||
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location,
|
||||
args->start_instance_location, 0);
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
.first_vertex = args->start_vertex_location,
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0, i);
|
||||
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||
{.vertex_count = args.vertexCount,
|
||||
.instance_count = args.instanceCount,
|
||||
.first_vertex = args.firstVertex,
|
||||
.first_instance = args.firstInstance,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location,
|
||||
args->start_instance_location, args->start_index_location);
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
|
||||
|
||||
const uint32_t index_count =
|
||||
(m_index_buffer_size != 0
|
||||
? std::min(args->index_count_per_instance, m_index_buffer_size)
|
||||
: args->index_count_per_instance);
|
||||
if (GraphicsRunDebugDumpEnabled() && index_count != args->index_count_per_instance) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
|
||||
LOGF("\t DrawIndexIndirectMulti: clamped index_count from %" PRIu32 " to %" PRIu32
|
||||
" using INDEX_BUFFER_SIZE\n",
|
||||
args->index_count_per_instance, index_count);
|
||||
}
|
||||
}
|
||||
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = args->instance_count,
|
||||
.base_vertex = static_cast<int32_t>(args->base_vertex_location),
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||
args.firstInstance, args.firstIndex, i);
|
||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = std::min(args.indexCount, max_indices),
|
||||
.index_addr = IndexAddress(args.firstIndex),
|
||||
.instance_count = args.instanceCount,
|
||||
.index_type_and_size = m_index_type_and_size,
|
||||
.base_vertex = args.vertexOffset,
|
||||
.first_instance = args.firstInstance,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,221 +954,86 @@ void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
args.instance_count = NumInstances();
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
// Stall only this command processor, not the host command lane or other queues.
|
||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index))) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action,
|
||||
uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id) {
|
||||
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
|
||||
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
||||
uint64_t data, uint32_t interrupt_context_id) {
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
const auto bits = static_cast<unsigned>(sizeof(T) * 8u);
|
||||
const auto log_width = static_cast<int>(sizeof(T) * 2u);
|
||||
|
||||
LOGF("CommandProcessor::WriteAtEndOfPipe%u()\n"
|
||||
"\t cache_policy = 0x%08" PRIx32 "\n"
|
||||
"\t event_write_dest = 0x%08" PRIx32 "\n"
|
||||
"\t eop_event_type = 0x%08" PRIx32 "\n"
|
||||
"\t cache_action = 0x%08" PRIx32 "\n"
|
||||
"\t event_index = 0x%08" PRIx32 "\n"
|
||||
"\t event_write_source = 0x%08" PRIx32 "\n"
|
||||
"\t interrupt_selector = 0x%08" PRIx32 "\n"
|
||||
"\t interrupt_context = 0x%08" PRIx32 "\n"
|
||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||
"\t value = 0x%0*" PRIx64 "\n",
|
||||
bits, cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, interrupt_selector, interrupt_context_id,
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), log_width, static_cast<uint64_t>(value));
|
||||
LOGF("CommandProcessor::ReleaseMem()\n"
|
||||
"\t data_sel = %" PRIu32 "\n"
|
||||
"\t interrupt_selector = %" PRIu32 "\n"
|
||||
"\t address = 0x%016" PRIx64 "\n"
|
||||
"\t data = 0x%016" PRIx64 "\n"
|
||||
"\t interrupt_context = 0x%08" PRIx32 "\n",
|
||||
data_sel, interrupt_selector, reinterpret_cast<uint64_t>(address), data,
|
||||
interrupt_context_id);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cache_policy != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(event_write_dest != 0x00000000);
|
||||
|
||||
bool with_interrupt = false;
|
||||
switch (interrupt_selector) {
|
||||
case 0x00:
|
||||
case 0x03: with_interrupt = false; break;
|
||||
case 0x01:
|
||||
if (!IsAsyncComputeQueue()) {
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
return;
|
||||
}
|
||||
with_interrupt = true;
|
||||
break;
|
||||
case 0x02: with_interrupt = true; break;
|
||||
default: EXIT("unknown interrupt_selector\n");
|
||||
// QueuedInterrupt: kNone, kOnly, kAfterWriteConfirm, kDataAfterWriteConfirm, kSendIntCtx.
|
||||
EXIT_NOT_IMPLEMENTED(interrupt_selector > 4);
|
||||
const bool interrupt = (interrupt_selector != 0 && interrupt_selector != 3);
|
||||
// kSendIntCtx has no label, and kOnly writes none on graphics queues.
|
||||
if (address == nullptr || interrupt_selector == 4 ||
|
||||
(interrupt_selector == 1 && !IsAsyncComputeQueue())) {
|
||||
data_sel = 0;
|
||||
}
|
||||
if (data_sel == 0 && !interrupt) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto write32 = [&] {
|
||||
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
|
||||
auto data = static_cast<uint32_t>(value);
|
||||
EXIT_IF(dst == nullptr);
|
||||
(void)command.Handle();
|
||||
std::memcpy(dst, &data, sizeof(data));
|
||||
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
|
||||
if (with_interrupt) {
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
switch (event_write_source) {
|
||||
case 0x01:
|
||||
if constexpr (sizeof(T) == sizeof(uint32_t)) {
|
||||
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
|
||||
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
|
||||
SynchronizeGpu();
|
||||
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
|
||||
value >> 16u);
|
||||
if (with_interrupt) {
|
||||
m_renderer.TriggerInterrupt(m_interrupt_event_id, interrupt_context_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) {
|
||||
write32();
|
||||
return;
|
||||
}
|
||||
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
||||
switch (data_sel) {
|
||||
case 0: break;
|
||||
case 1: {
|
||||
const auto value = static_cast<uint32_t>(data);
|
||||
write_label(&value, sizeof(value));
|
||||
break;
|
||||
case 0x02:
|
||||
case 0x04:
|
||||
if constexpr (sizeof(T) == sizeof(uint32_t)) {
|
||||
if (event_write_source == 0x02 && eop_event_type == 0x2f && event_index == 0x06) {
|
||||
switch (cache_action) {
|
||||
case 0x00:
|
||||
case 0x38: write32(); return;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (event_write_source == 0x04) {
|
||||
value = Sync::ReadReferenceClock();
|
||||
}
|
||||
auto write64 = [&] {
|
||||
auto* dst = static_cast<uint64_t*>(dst_gpu_addr);
|
||||
EXIT_IF(dst == nullptr);
|
||||
(void)command.Handle();
|
||||
std::memcpy(dst, &value, sizeof(value));
|
||||
|
||||
if (with_interrupt) {
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
}
|
||||
};
|
||||
|
||||
switch (cache_action) {
|
||||
case 0x00:
|
||||
switch (eop_event_type) {
|
||||
case 0x04:
|
||||
if (event_index == 0x05) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x14:
|
||||
case 0x28:
|
||||
case 0x2f:
|
||||
if (event_index == 0x00) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x2b:
|
||||
case 0x2d:
|
||||
case 0x30:
|
||||
if (event_index == 0x00 && !with_interrupt) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case 0x38:
|
||||
switch (eop_event_type) {
|
||||
case 0x04:
|
||||
case 0x14:
|
||||
case 0x28:
|
||||
if (((eop_event_type == 0x04 || eop_event_type == 0x28) &&
|
||||
event_index == 0x05) ||
|
||||
(event_index == 0x00)) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x2b:
|
||||
case 0x2d:
|
||||
if (event_index == 0x00 && !with_interrupt) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 0x2f:
|
||||
if (event_index == 0x06 && !with_interrupt) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case 0x3b:
|
||||
if (eop_event_type == 0x04 && event_index == 0x05 && with_interrupt) {
|
||||
write64();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
||||
case 2: write_label(&data, sizeof(data)); break;
|
||||
case 5: {
|
||||
GetScheduler().Finish();
|
||||
const auto gds = static_cast<uint32_t>(data);
|
||||
Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(),
|
||||
static_cast<uint32_t*>(address), gds & 0xffffu, gds >> 16u);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
default: EXIT("unknown release_mem data selector: %" PRIu32 "\n", data_sel);
|
||||
}
|
||||
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
|
||||
void CommandProcessor::WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action,
|
||||
uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, uint32_t value,
|
||||
uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id) {
|
||||
WriteAtEndOfPipe(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, dst_gpu_addr, value, interrupt_selector,
|
||||
interrupt_context_id);
|
||||
}
|
||||
|
||||
void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action,
|
||||
uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, uint64_t value,
|
||||
uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id) {
|
||||
WriteAtEndOfPipe(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, dst_gpu_addr, value, interrupt_selector,
|
||||
interrupt_context_id);
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||
(void)CurrentBuffer().Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
||||
renderer.TriggerInterrupt(event_id, interrupt_context_id);
|
||||
});
|
||||
// GuestGpu::Process's end-of-slice flush submits the work this interrupt waits on.
|
||||
if (interrupt) {
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
||||
renderer.TriggerInterrupt(event_id, interrupt_context_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
@@ -1279,15 +1086,19 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
"\n",
|
||||
event_index, event_address);
|
||||
}
|
||||
if (auto& queries = m_renderer.GetOcclusionQueries(); queries.Enabled()) {
|
||||
queries.Dump(event_address);
|
||||
break;
|
||||
}
|
||||
static std::once_flag warning_once;
|
||||
std::call_once(warning_once, [] {
|
||||
std::printf("Warning: game uses occlusion queries, which are currently treated as "
|
||||
"always visible; GPU usage may be higher and FPS may be lower.\n");
|
||||
std::printf("Warning: game uses occlusion queries, which are treated as always "
|
||||
"visible without --occlusion-queries; GPU usage may be higher and FPS "
|
||||
"may be lower.\n");
|
||||
});
|
||||
|
||||
// Until host occlusion queries are implemented, publish an always-visible result. The
|
||||
// PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result
|
||||
// ready.
|
||||
// Without host occlusion queries, publish an always-visible result. The PS5 layout
|
||||
// contains one interleaved begin/end pair per DB, and bit 63 marks a result ready.
|
||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||
constexpr uint64_t counter_mask = ready_bit - 1u;
|
||||
auto* results = reinterpret_cast<volatile uint64_t*>(event_address);
|
||||
@@ -1303,69 +1114,20 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip() {
|
||||
void CommandProcessor::Flip(int handle, int index, int flip_mode, int64_t flip_arg) {
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n");
|
||||
}
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
auto request = Sync::PrepareVideoOutFlip(command, handle, index, flip_mode, flip_arg);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n"
|
||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||
"\t value = 0x%08" PRIx32 "\n",
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
|
||||
void* dst_gpu_addr, uint32_t value) {
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::FlipWithInterrupt()\n"
|
||||
"\t eop_event_type = 0x%08" PRIx32 "\n"
|
||||
"\t cache_action = 0x%08" PRIx32 "\n"
|
||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||
"\t value = 0x%08" PRIx32 "\n",
|
||||
eop_event_type, cache_action, reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
if (eop_event_type != 0x00000004 || cache_action != 0x00000038) {
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, request] {
|
||||
renderer.GetVideoOut().CompleteFlip(request);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
auto& command = CurrentBuffer();
|
||||
if (g_current_processor != nullptr) {
|
||||
@@ -1383,10 +1145,6 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::SynchronizeGpu() {
|
||||
GetScheduler().Finish();
|
||||
}
|
||||
|
||||
bool GuestGpu::IsGpuThread() noexcept {
|
||||
return g_gpu_thread;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@ public:
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
||||
void Wake();
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void Submit(std::span<const uint32_t> commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
// Insert an ordered graphics drain; only a previous suspend point can block the caller.
|
||||
@@ -58,14 +59,10 @@ private:
|
||||
SubmissionType type = SubmissionType::Graphics;
|
||||
uint32_t queue_id = 0;
|
||||
std::span<const uint32_t> commands;
|
||||
std::span<const uint32_t> constant_commands;
|
||||
Pm4Execution command_execution;
|
||||
Pm4Execution constant_execution;
|
||||
bool started = false;
|
||||
bool command_complete = false;
|
||||
bool constant_complete = false;
|
||||
bool blocked = false;
|
||||
uint64_t flip_request_id = 0;
|
||||
Pm4Execution execution;
|
||||
bool started = false;
|
||||
bool blocked = false;
|
||||
uint64_t flip_request_id = 0;
|
||||
};
|
||||
|
||||
void Enqueue(Submission submission);
|
||||
@@ -87,6 +84,7 @@ private:
|
||||
bool m_processing = false;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_wake_pending = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
|
||||
@@ -597,14 +597,10 @@ struct ShaderRegisters {
|
||||
}
|
||||
};
|
||||
|
||||
enum class UserSgprType { Unknown, Region, Vsharp };
|
||||
|
||||
struct UserSgprInfo {
|
||||
static constexpr int SGPRS_MAX = 32;
|
||||
|
||||
uint32_t value[SGPRS_MAX] = {0};
|
||||
UserSgprType type[SGPRS_MAX] = {};
|
||||
uint32_t count = 0;
|
||||
uint32_t value[SGPRS_MAX] = {0};
|
||||
};
|
||||
|
||||
struct VertexShaderInfo {
|
||||
@@ -1056,30 +1052,10 @@ public:
|
||||
void SetCsShader(const CsStageRegisters& cs_regs) { m_cs.cs_regs = cs_regs; }
|
||||
void SetCsWaveSize(uint8_t wave_size) { m_cs.cs_regs.wave_size = wave_size; }
|
||||
|
||||
void SetPsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_ps.ps_user_sgpr.value[id] = value;
|
||||
m_ps.ps_user_sgpr.type[id] = type;
|
||||
m_ps.ps_user_sgpr.count =
|
||||
((id + 1) > m_ps.ps_user_sgpr.count ? (id + 1) : m_ps.ps_user_sgpr.count);
|
||||
}
|
||||
void SetCsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_cs.cs_user_sgpr.value[id] = value;
|
||||
m_cs.cs_user_sgpr.type[id] = type;
|
||||
m_cs.cs_user_sgpr.count =
|
||||
((id + 1) > m_cs.cs_user_sgpr.count ? (id + 1) : m_cs.cs_user_sgpr.count);
|
||||
}
|
||||
void SetHsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_vs.hs_user_sgpr.value[id] = value;
|
||||
m_vs.hs_user_sgpr.type[id] = type;
|
||||
m_vs.hs_user_sgpr.count =
|
||||
((id + 1) > m_vs.hs_user_sgpr.count ? (id + 1) : m_vs.hs_user_sgpr.count);
|
||||
}
|
||||
void SetGsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_vs.gs_user_sgpr.value[id] = value;
|
||||
m_vs.gs_user_sgpr.type[id] = type;
|
||||
m_vs.gs_user_sgpr.count =
|
||||
((id + 1) > m_vs.gs_user_sgpr.count ? (id + 1) : m_vs.gs_user_sgpr.count);
|
||||
}
|
||||
void SetPsUserSgpr(uint32_t id, uint32_t value) { m_ps.ps_user_sgpr.value[id] = value; }
|
||||
void SetCsUserSgpr(uint32_t id, uint32_t value) { m_cs.cs_user_sgpr.value[id] = value; }
|
||||
void SetHsUserSgpr(uint32_t id, uint32_t value) { m_vs.hs_user_sgpr.value[id] = value; }
|
||||
void SetGsUserSgpr(uint32_t id, uint32_t value) { m_vs.gs_user_sgpr.value[id] = value; }
|
||||
|
||||
[[nodiscard]] const PixelShaderInfo& GetPs() const { return m_ps; }
|
||||
[[nodiscard]] const VertexShaderInfo& GetVs() const { return m_vs; }
|
||||
|
||||
@@ -25,7 +25,6 @@ constexpr auto MakeRegisterNames() {
|
||||
names[R_POP_MARKER] = "R_POP_MARKER";
|
||||
names[R_WRITE_DATA] = "R_WRITE_DATA";
|
||||
names[R_FLIP] = "R_FLIP";
|
||||
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
|
||||
names[R_DMA_DATA] = "R_DMA_DATA";
|
||||
names[R_CONTEXT_STATE] = "R_CONTEXT_STATE";
|
||||
return names;
|
||||
@@ -54,7 +53,6 @@ constexpr auto MakeOpcodeNames() {
|
||||
names[IT_DRAW_INDIRECT_MULTI] = "IT_DRAW_INDIRECT_MULTI";
|
||||
names[IT_DRAW_INDEX_AUTO] = "IT_DRAW_INDEX_AUTO";
|
||||
names[IT_NUM_INSTANCES] = "IT_NUM_INSTANCES";
|
||||
names[IT_INDIRECT_BUFFER_CNST] = "IT_INDIRECT_BUFFER_CNST";
|
||||
names[IT_DRAW_INDEX_OFFSET_2] = "IT_DRAW_INDEX_OFFSET_2";
|
||||
names[IT_WRITE_DATA] = "IT_WRITE_DATA";
|
||||
names[IT_MEM_SEMAPHORE] = "IT_MEM_SEMAPHORE";
|
||||
@@ -66,8 +64,6 @@ constexpr auto MakeOpcodeNames() {
|
||||
names[IT_PFP_SYNC_ME] = "IT_PFP_SYNC_ME";
|
||||
names[IT_SURFACE_SYNC] = "IT_SURFACE_SYNC";
|
||||
names[IT_EVENT_WRITE] = "IT_EVENT_WRITE";
|
||||
names[IT_EVENT_WRITE_EOP] = "IT_EVENT_WRITE_EOP";
|
||||
names[IT_EVENT_WRITE_EOS] = "IT_EVENT_WRITE_EOS";
|
||||
names[IT_RELEASE_MEM] = "IT_RELEASE_MEM";
|
||||
names[IT_DMA_DATA] = "IT_DMA_DATA";
|
||||
names[IT_ACQUIRE_MEM] = "IT_ACQUIRE_MEM";
|
||||
@@ -80,12 +76,6 @@ constexpr auto MakeOpcodeNames() {
|
||||
names[IT_SET_QUEUE_REG] = "IT_SET_QUEUE_REG";
|
||||
names[IT_SET_UCONFIG_REG] = "IT_SET_UCONFIG_REG";
|
||||
names[IT_SET_UCONFIG_REG_INDEX] = "IT_SET_UCONFIG_REG_INDEX";
|
||||
names[IT_WRITE_CONST_RAM] = "IT_WRITE_CONST_RAM";
|
||||
names[IT_DUMP_CONST_RAM] = "IT_DUMP_CONST_RAM";
|
||||
names[IT_INCREMENT_CE_COUNTER] = "IT_INCREMENT_CE_COUNTER";
|
||||
names[IT_INCREMENT_DE_COUNTER] = "IT_INCREMENT_DE_COUNTER";
|
||||
names[IT_WAIT_ON_CE_COUNTER] = "IT_WAIT_ON_CE_COUNTER";
|
||||
names[IT_WAIT_ON_DE_COUNTER_DIFF] = "IT_WAIT_ON_DE_COUNTER_DIFF";
|
||||
names[IT_DISPATCH_DRAW_PREAMBLE] = "IT_DISPATCH_DRAW_PREAMBLE";
|
||||
names[IT_DISPATCH_DRAW] = "IT_DISPATCH_DRAW";
|
||||
names[IT_GET_LOD_STATS] = "IT_GET_LOD_STATS";
|
||||
|
||||
@@ -40,7 +40,6 @@ constexpr uint32_t IT_INDEX_TYPE = 0x2A;
|
||||
constexpr uint32_t IT_DRAW_INDIRECT_MULTI = 0x2C;
|
||||
constexpr uint32_t IT_DRAW_INDEX_AUTO = 0x2D;
|
||||
constexpr uint32_t IT_NUM_INSTANCES = 0x2F;
|
||||
constexpr uint32_t IT_INDIRECT_BUFFER_CNST = 0x33;
|
||||
constexpr uint32_t IT_DRAW_INDEX_OFFSET_2 = 0x35;
|
||||
constexpr uint32_t IT_WRITE_DATA = 0x37;
|
||||
constexpr uint32_t IT_MEM_SEMAPHORE = 0x39;
|
||||
@@ -53,8 +52,6 @@ constexpr uint32_t IT_CP_DMA = 0x41;
|
||||
constexpr uint32_t IT_PFP_SYNC_ME = 0x42;
|
||||
constexpr uint32_t IT_SURFACE_SYNC = 0x43;
|
||||
constexpr uint32_t IT_EVENT_WRITE = 0x46;
|
||||
constexpr uint32_t IT_EVENT_WRITE_EOP = 0x47;
|
||||
constexpr uint32_t IT_EVENT_WRITE_EOS = 0x48;
|
||||
constexpr uint32_t IT_RELEASE_MEM = 0x49;
|
||||
constexpr uint32_t IT_DMA_DATA = 0x50;
|
||||
constexpr uint32_t IT_ACQUIRE_MEM = 0x58;
|
||||
@@ -67,12 +64,6 @@ constexpr uint32_t IT_SET_SH_REG = 0x76;
|
||||
constexpr uint32_t IT_SET_QUEUE_REG = 0x78;
|
||||
constexpr uint32_t IT_SET_UCONFIG_REG = 0x79;
|
||||
constexpr uint32_t IT_SET_UCONFIG_REG_INDEX = 0x7A;
|
||||
constexpr uint32_t IT_WRITE_CONST_RAM = 0x81;
|
||||
constexpr uint32_t IT_DUMP_CONST_RAM = 0x83;
|
||||
constexpr uint32_t IT_INCREMENT_CE_COUNTER = 0x84;
|
||||
constexpr uint32_t IT_INCREMENT_DE_COUNTER = 0x85;
|
||||
constexpr uint32_t IT_WAIT_ON_CE_COUNTER = 0x86;
|
||||
constexpr uint32_t IT_WAIT_ON_DE_COUNTER_DIFF = 0x88;
|
||||
constexpr uint32_t IT_DISPATCH_DRAW = 0x8D;
|
||||
constexpr uint32_t IT_GET_LOD_STATS = 0x8E;
|
||||
constexpr uint32_t IT_WAIT_REG_MEM_64 = 0x93;
|
||||
@@ -88,7 +79,6 @@ constexpr uint32_t R_PUSH_MARKER = 0x0B;
|
||||
constexpr uint32_t R_POP_MARKER = 0x0C;
|
||||
constexpr uint32_t R_WRITE_DATA = 0x15;
|
||||
constexpr uint32_t R_FLIP = 0x17;
|
||||
constexpr uint32_t R_RELEASE_MEM = 0x18;
|
||||
constexpr uint32_t R_DMA_DATA = 0x19;
|
||||
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ struct GraphicContext {
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool maintenance8_enabled = false;
|
||||
bool conditional_rendering_enabled = false;
|
||||
bool occlusion_queries_enabled = false;
|
||||
bool occlusion_query_precise_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
bool mesh_shader_enabled = false;
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||
@@ -42,6 +45,8 @@ struct GraphicContext {
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
uint32_t max_push_descriptors = 0;
|
||||
uint32_t device_clock_period_ps = 0;
|
||||
vk::DescriptorSetLayout image_heap_layout = nullptr;
|
||||
vk::ShaderStageFlags required_subgroup_size_stages = {};
|
||||
Common::Mutex queue_mutex;
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
|
||||
@@ -66,18 +66,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void MemoryTracker::QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE);
|
||||
const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE);
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
m_bda_sync.Add(begin, end - begin);
|
||||
}
|
||||
|
||||
RangeSet MemoryTracker::TakeBdaSync() {
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
return std::exchange(m_bda_sync, RangeSet {});
|
||||
}
|
||||
|
||||
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
@@ -100,7 +88,6 @@ void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
|
||||
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
@@ -141,8 +128,6 @@ void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
locks.clear();
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -29,10 +29,6 @@ public:
|
||||
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemory(uint64_t vaddr, uint64_t size);
|
||||
// Queue after changing ownership; a concurrent drain must not consume an event
|
||||
// before the dirty pages it describes become visible.
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] RangeSet TakeBdaSync();
|
||||
// Removes protection from a range and flushes GPU-owned data when required.
|
||||
template <typename Flush>
|
||||
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) noexcept {
|
||||
@@ -55,7 +51,6 @@ public:
|
||||
on_flush();
|
||||
}
|
||||
});
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
@@ -154,8 +149,6 @@ private:
|
||||
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
|
||||
std::vector<std::unique_ptr<RegionManager>> m_region_storage;
|
||||
std::mutex m_region_mutex;
|
||||
std::mutex m_bda_sync_mutex;
|
||||
RangeSet m_bda_sync;
|
||||
PageManager& m_page_manager;
|
||||
};
|
||||
|
||||
|
||||
+38
-9
@@ -127,7 +127,7 @@ void BufferCache::ChangeRegister(BufferId id) {
|
||||
}
|
||||
WriteDataBuffer(m_bda_pagetable_buffer, table_offset,
|
||||
addresses.data(), addresses.size() * sizeof(vk::DeviceAddress));
|
||||
m_memory_tracker.QueueBdaSync(buffer.CpuAddress(), buffer.Size());
|
||||
QueueBdaSync(buffer.CpuAddress(), buffer.Size());
|
||||
} else {
|
||||
const auto found = m_buffers.find(buffer.CpuAddress());
|
||||
EXIT_IF(found == m_buffers.end() || found->second != id);
|
||||
@@ -174,6 +174,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
total_size += Common::AlignUp(end - start, 64);
|
||||
});
|
||||
m_gpu_modified_ranges.Subtract(address, bytes);
|
||||
ForgetColorMetadataChecks(address, bytes);
|
||||
});
|
||||
if (copies.empty()) {
|
||||
return false;
|
||||
@@ -298,6 +299,7 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_memory_tracker.InvalidateRegion(vaddr, size,
|
||||
[this, vaddr, size] { ReadMemory(vaddr, size, true); });
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
|
||||
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
|
||||
@@ -317,6 +319,7 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
|
||||
}
|
||||
if (is_write) {
|
||||
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -519,6 +522,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
|
||||
if (is_written) {
|
||||
m_gpu_modified_ranges.Add(vaddr, size);
|
||||
ForgetColorMetadataChecks(vaddr, size);
|
||||
}
|
||||
return {&buffer, buffer.Offset(vaddr)};
|
||||
}
|
||||
@@ -623,6 +627,29 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
|
||||
return address + m_slot_buffers[id].Size() > vaddr;
|
||||
}
|
||||
|
||||
void BufferCache::MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) {
|
||||
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||
if ((keys & (1u << slot)) != 0) {
|
||||
m_checked_color_metadata[slot].Add(vaddr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const {
|
||||
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||
if ((keys & (1u << slot)) != 0 && !m_checked_color_metadata[slot].Contains(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferCache::ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size) {
|
||||
for (auto& checked: m_checked_color_metadata) {
|
||||
checked.Subtract(vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
}
|
||||
@@ -694,14 +721,16 @@ void BufferCache::ProcessFaultBuffer() {
|
||||
m_fault_manager.ProcessFaultBuffer();
|
||||
}
|
||||
|
||||
void BufferCache::SynchronizeBda(const RangeSet& mapped) {
|
||||
// Take before walking: writes queued during synchronization remain pending.
|
||||
const auto pending = m_memory_tracker.TakeBdaSync();
|
||||
pending.ForEach([&](uint64_t begin, uint64_t end) {
|
||||
mapped.ForEachInRange(begin, end - begin, [this](uint64_t start, uint64_t finish) {
|
||||
SynchronizeBuffersInRange(start, finish - start);
|
||||
});
|
||||
});
|
||||
void BufferCache::QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE);
|
||||
const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE);
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
m_bda_sync.Add(begin, end - begin);
|
||||
}
|
||||
|
||||
RangeSet BufferCache::TakeBdaSync() {
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
return std::exchange(m_bda_sync, {});
|
||||
}
|
||||
|
||||
void BufferCache::SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size) {
|
||||
|
||||
+16
-9
@@ -11,7 +11,9 @@
|
||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -79,20 +81,21 @@ public:
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
// Color metadata the GPU found free of the clear keys in `keys` (bits of code >> 5) stays so
|
||||
// until a GPU write or download.
|
||||
void MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys);
|
||||
[[nodiscard]] bool IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const;
|
||||
void ProcessFaultBuffer();
|
||||
void SynchronizeBda(const RangeSet& mapped);
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
m_memory_tracker.QueueBdaSync(vaddr, size);
|
||||
}
|
||||
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
|
||||
// Queue after the range's state change becomes visible, or a drain could consume it first.
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] RangeSet TakeBdaSync();
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
bool IsBufferInvalid(BufferId id) const {
|
||||
const auto* buffer = m_slot_buffers.try_get(id);
|
||||
return buffer == nullptr || buffer->is_deleted;
|
||||
}
|
||||
bool IsBufferInvalid(BufferId id) const { return !id || m_slot_buffers[id].is_deleted; }
|
||||
|
||||
using BufferMap = std::map<uint64_t, BufferId>;
|
||||
struct OverlapResult {
|
||||
@@ -117,13 +120,13 @@ private:
|
||||
void DeleteBuffer(BufferId id);
|
||||
[[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
|
||||
bool is_written, bool is_texel_buffer);
|
||||
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] vk::Buffer UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> copies,
|
||||
uint64_t total_size);
|
||||
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
// Synchronous downloads publish before returning; asynchronous callers wait before reuse.
|
||||
template <bool async>
|
||||
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
void ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
@@ -135,7 +138,11 @@ private:
|
||||
BufferMap m_buffers;
|
||||
PageTable m_page_table;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
// One per TileManager::ColorClearSlots key slot.
|
||||
std::array<RangeSet, 8> m_checked_color_metadata;
|
||||
MemoryTracker m_memory_tracker;
|
||||
std::mutex m_bda_sync_mutex;
|
||||
RangeSet m_bda_sync;
|
||||
StreamBuffer m_staging_buffer;
|
||||
StreamBuffer m_stream_buffer;
|
||||
StreamBuffer m_download_buffer;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -118,6 +119,13 @@ public:
|
||||
|
||||
class const_iterator final {
|
||||
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); }
|
||||
const_iterator& operator++() noexcept {
|
||||
++m_index;
|
||||
|
||||
+221
-134
@@ -229,6 +229,7 @@ void TextureCache::UnregisterImage(ImageId id) {
|
||||
if (!image.registered) {
|
||||
return;
|
||||
}
|
||||
image.HeapChanged();
|
||||
UntrackImage(id);
|
||||
ImagePageTable::PageRange pages {};
|
||||
if (!ImagePageTable::TryGetPageRange(image.info.data.address, image.info.data.size, pages)) {
|
||||
@@ -250,24 +251,24 @@ void TextureCache::UnregisterImage(ImageId id) {
|
||||
}
|
||||
|
||||
void TextureCache::DeleteImage(ImageId id) {
|
||||
auto* image = m_slot_images.try_get(id);
|
||||
if (image == nullptr || !image->registered) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (!image.registered) {
|
||||
return;
|
||||
}
|
||||
if (image->depth_id) {
|
||||
std::erase(m_slot_images[image->depth_id].stencil_ids, id);
|
||||
if (image.depth_id) {
|
||||
std::erase(m_slot_images[image.depth_id].stencil_ids, id);
|
||||
} else {
|
||||
// Detach first: deleting each association also unlinks it from its depth owner.
|
||||
for (const auto association: std::exchange(image->stencil_ids, {})) {
|
||||
for (const auto association: std::exchange(image.stencil_ids, {})) {
|
||||
FreeImage(association);
|
||||
}
|
||||
}
|
||||
if (image->IsGpuModified()) {
|
||||
if (image.IsGpuModified()) {
|
||||
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
|
||||
}
|
||||
m_download_images.erase(id);
|
||||
if (image->info.metadata.kind == ImageMetadataKind::Htile) {
|
||||
m_surface_metas.erase(image->info.metadata.range.address);
|
||||
if (image.info.metadata.kind == ImageMetadataKind::Htile) {
|
||||
m_surface_metas.erase(image.info.metadata.range.address);
|
||||
}
|
||||
UnregisterImage(id);
|
||||
if (m_scheduler.Active()) {
|
||||
@@ -378,6 +379,7 @@ void TextureCache::UntrackImageHead(ImageId id) {
|
||||
const auto address = Common::AlignDown(begin + TRACKER_PAGE_SIZE, TRACKER_PAGE_SIZE);
|
||||
const auto size = address - begin;
|
||||
image.track_addr = address;
|
||||
image.HeapChanged();
|
||||
if (image.track_addr == image.track_addr_end) {
|
||||
MarkAsMaybeDirty(id, image);
|
||||
}
|
||||
@@ -395,6 +397,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
|
||||
const auto address = Common::AlignDown(end, TRACKER_PAGE_SIZE);
|
||||
const auto size = end - address;
|
||||
image.track_addr_end = address;
|
||||
image.HeapChanged();
|
||||
if (image.track_addr == image.track_addr_end) {
|
||||
MarkAsMaybeDirty(id, image);
|
||||
}
|
||||
@@ -426,12 +429,6 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t query_epoch = ++m_image_query_epoch;
|
||||
if (query_epoch == 0) {
|
||||
m_slot_images.ForEach([](ImageId, const Image& image) { image.query_epoch = 0; });
|
||||
query_epoch = ++m_image_query_epoch;
|
||||
}
|
||||
|
||||
ImageIds result;
|
||||
ForEachPage(address, size, [&](uint64_t page) {
|
||||
const auto* owners = m_image_page_table.Find(page);
|
||||
@@ -439,19 +436,17 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64
|
||||
return;
|
||||
}
|
||||
owners->ForEach([&](ImageId id) {
|
||||
auto* image = m_slot_images.try_get(id);
|
||||
if (image == nullptr) {
|
||||
const auto& image = m_slot_images[id];
|
||||
if (image.picked || !image.Overlaps(address, size, page_overlap)) {
|
||||
return;
|
||||
}
|
||||
if (image->query_epoch == query_epoch) {
|
||||
return;
|
||||
}
|
||||
image->query_epoch = query_epoch;
|
||||
if (image->Overlaps(address, size, page_overlap)) {
|
||||
result.push_back(id);
|
||||
}
|
||||
image.picked = true;
|
||||
result.push_back(id);
|
||||
});
|
||||
});
|
||||
for (const auto id: result) {
|
||||
m_slot_images[id].picked = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -493,16 +488,15 @@ void TextureCache::RefreshCopySource(ImageId id) {
|
||||
}
|
||||
|
||||
bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
const bool source_depth = source.info.IsDepth();
|
||||
const bool destination_depth = destination.info.IsDepth();
|
||||
if (source_depth == destination_depth) {
|
||||
// Converts guest D16 between a four-byte host depth aspect and two-byte color or D16 depth.
|
||||
const bool source_wide = DepthAspectTransferBytes(source.backing.format) == sizeof(uint32_t);
|
||||
if (source_wide == (DepthAspectTransferBytes(destination.backing.format) == sizeof(uint32_t))) {
|
||||
return false;
|
||||
}
|
||||
auto& depth = source_depth ? source : destination;
|
||||
auto& color = source_depth ? destination : source;
|
||||
const auto transfer_bytes = DepthAspectTransferBytes(depth.backing.format);
|
||||
auto& depth = source_wide ? source : destination;
|
||||
auto& narrow = source_wide ? destination : source;
|
||||
if (depth.info.bytes_per_block != sizeof(uint16_t) ||
|
||||
color.info.bytes_per_block != sizeof(uint16_t) || transfer_bytes != sizeof(uint32_t)) {
|
||||
narrow.info.bytes_per_block != sizeof(uint16_t)) {
|
||||
return false;
|
||||
}
|
||||
EXIT_IF(source.backing.samples != 1 || destination.backing.samples != 1 ||
|
||||
@@ -512,42 +506,42 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
|
||||
const auto layers = depth.info.resources.layers;
|
||||
const uint64_t depth_slice =
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * transfer_bytes;
|
||||
const uint64_t color_slice =
|
||||
static_cast<uint64_t>(color.info.pitch) * color.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || color_slice > UINT64_MAX / layers);
|
||||
const auto depth_size = depth_slice * layers;
|
||||
const auto color_size = color_slice * layers;
|
||||
const std::array depth_copies {
|
||||
static_cast<uint64_t>(depth.info.pitch) * depth.info.extent.height * sizeof(uint32_t);
|
||||
const uint64_t narrow_slice =
|
||||
static_cast<uint64_t>(narrow.info.pitch) * narrow.info.extent.height * sizeof(uint16_t);
|
||||
EXIT_IF(layers == 0 || depth_slice > UINT64_MAX / layers || narrow_slice > UINT64_MAX / layers);
|
||||
const std::array depth_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
depth.info.pitch,
|
||||
depth.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eDepth, 0, 0, layers},
|
||||
{},
|
||||
depth.info.extent}};
|
||||
const std::array color_copies {
|
||||
const std::array narrow_copies {
|
||||
vk::BufferImageCopy {0,
|
||||
color.info.pitch,
|
||||
color.info.extent.height,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 0, layers},
|
||||
narrow.info.pitch,
|
||||
narrow.info.extent.height,
|
||||
{narrow.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor,
|
||||
0, 0, layers},
|
||||
{},
|
||||
color.info.extent}};
|
||||
narrow.info.extent}};
|
||||
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_size);
|
||||
auto color_buffer = m_tiler.GetScratchBuffer(color_size, depth_buffer.buffer);
|
||||
auto depth_buffer = m_tiler.GetScratchBuffer(depth_slice * layers);
|
||||
auto narrow_buffer = m_tiler.GetScratchBuffer(narrow_slice * layers, depth_buffer.buffer);
|
||||
const TileManager::D16Layout promote_layout {
|
||||
.width = depth.info.extent.width,
|
||||
.height = depth.info.extent.height,
|
||||
.layers = layers,
|
||||
.source_row_stride = static_cast<uint64_t>(color.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * transfer_bytes,
|
||||
.source_slice_stride = color_slice,
|
||||
.source_row_stride = static_cast<uint64_t>(narrow.info.pitch) * sizeof(uint16_t),
|
||||
.target_row_stride = static_cast<uint64_t>(depth.info.pitch) * sizeof(uint32_t),
|
||||
.source_slice_stride = narrow_slice,
|
||||
.target_slice_stride = depth_slice,
|
||||
};
|
||||
const bool d32 = DepthAspectTransferFormat(depth.backing.format) == vk::Format::eD32Sfloat;
|
||||
if (source_depth) {
|
||||
if (source_wide) {
|
||||
source.Download(depth_copies, depth_buffer.buffer, depth_buffer.offset, depth_buffer.size);
|
||||
m_tiler.ConvertD16(depth_buffer, color_buffer, TileManager::D16Direction::Demote, d32,
|
||||
m_tiler.ConvertD16(depth_buffer, narrow_buffer, TileManager::D16Direction::Demote, d32,
|
||||
{.width = promote_layout.width,
|
||||
.height = promote_layout.height,
|
||||
.layers = promote_layout.layers,
|
||||
@@ -555,11 +549,12 @@ bool TextureCache::CopyD16(Image& destination, Image& source) {
|
||||
.target_row_stride = promote_layout.source_row_stride,
|
||||
.source_slice_stride = promote_layout.target_slice_stride,
|
||||
.target_slice_stride = promote_layout.source_slice_stride});
|
||||
destination.Upload(color_copies, color_buffer.buffer, color_buffer.offset,
|
||||
color_buffer.size);
|
||||
destination.Upload(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
} else {
|
||||
source.Download(color_copies, color_buffer.buffer, color_buffer.offset, color_buffer.size);
|
||||
m_tiler.ConvertD16(color_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
source.Download(narrow_copies, narrow_buffer.buffer, narrow_buffer.offset,
|
||||
narrow_buffer.size);
|
||||
m_tiler.ConvertD16(narrow_buffer, depth_buffer, TileManager::D16Direction::Promote, d32,
|
||||
promote_layout);
|
||||
destination.Upload(depth_copies, depth_buffer.buffer, depth_buffer.offset,
|
||||
depth_buffer.size);
|
||||
@@ -784,18 +779,26 @@ void Resolve(TextureCache& cache, ImageId cached_id, ImageId merged_id) {
|
||||
TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& requested,
|
||||
BindingType binding, ImageId cached_id,
|
||||
ImageId merged_id) {
|
||||
auto owner = m_slot_images.try_get(cached_id);
|
||||
if (owner == nullptr || !owner->registered) {
|
||||
auto& cached = m_slot_images[cached_id];
|
||||
if (!cached.registered) {
|
||||
return {merged_id};
|
||||
}
|
||||
if (BcAlias::Matches(requested, owner->info) && !owner->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);
|
||||
return {merged_id};
|
||||
}
|
||||
auto& cached = *owner;
|
||||
const auto current_tick = m_scheduler.CurrentTick();
|
||||
const bool safe_to_delete =
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval;
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval &&
|
||||
cached.heap_refs.load(std::memory_order_relaxed) == 0u;
|
||||
|
||||
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
|
||||
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
|
||||
@@ -865,9 +868,8 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
if (requested.pixel_format != cached.info.pixel_format ||
|
||||
requested.data.size <= cached.info.data.size) {
|
||||
const auto result_id = merged_id ? merged_id : cached_id;
|
||||
const auto result = m_slot_images.try_get(result_id);
|
||||
return {result != nullptr && ImageViewOps::FormatsCompatible(result->info.pixel_format,
|
||||
requested.pixel_format)
|
||||
return {ImageViewOps::FormatsCompatible(m_slot_images[result_id].info.pixel_format,
|
||||
requested.pixel_format)
|
||||
? result_id
|
||||
: ImageId {}};
|
||||
}
|
||||
@@ -1067,12 +1069,78 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
|
||||
// so discovery runs before final draw uploads and never holds the texture lock across it.
|
||||
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
|
||||
m_buffer_cache.ReadMemory(range.address, range.size, false);
|
||||
const auto slice_size = range.size / layers;
|
||||
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
|
||||
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
|
||||
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
|
||||
uint32_t keys = 0;
|
||||
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||
clears[key].aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
if (DecodeColorClear(desc, static_cast<uint8_t>(key << 5u),
|
||||
clears[key].clearValue.color)) {
|
||||
keys |= 1u << key;
|
||||
}
|
||||
}
|
||||
// No selected key to clear, or none left since the last check.
|
||||
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
// Compressed images cannot upload, so they must already hold native contents.
|
||||
const bool native_current = (image.usage.render_target || image.IsGpuModified()) &&
|
||||
!image.IsBufferModified() && !image.IsCpuDirty();
|
||||
// 2D-array attachment views select 2D layers or, of a volume, depth slices.
|
||||
if (m_graphics.conditional_rendering_enabled &&
|
||||
image.backing.image_type != vk::ImageType::e1D &&
|
||||
count <= m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers &&
|
||||
(image.backing.usage & vk::ImageUsageFlagBits::eColorAttachment) &&
|
||||
(m_graphics.GetFormatProperties(view.format).optimalTilingFeatures &
|
||||
vk::FormatFeatureFlagBits::eColorAttachment) &&
|
||||
(desc.info.metadata.compression == VideoOutCompression::Uncompressed ||
|
||||
native_current)) {
|
||||
// The GPU selects each slice's clear, so the CPU never waits for the metadata.
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::ColorMetadataChecks);
|
||||
InitializeImage(id);
|
||||
const bool expand = desc.type != BindingType::VideoOut;
|
||||
const auto [buffer, offset] =
|
||||
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
|
||||
const auto predicates = m_tiler.CheckColorClear(
|
||||
{buffer->Handle(), offset, viewed.size}, count, keys, expand);
|
||||
if (expand) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys);
|
||||
}
|
||||
const auto command = m_scheduler.Current().Handle();
|
||||
const auto area = BeginColorRendering(image, view.format, view.base_level,
|
||||
image_first, count, nullptr, command);
|
||||
vk::ClearRect rect {.rect = area, .layerCount = 1};
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
rect.baseArrayLayer = slice;
|
||||
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||
if ((keys & (1u << key)) != 0) {
|
||||
const vk::ConditionalRenderingBeginInfoEXT predicate {
|
||||
.buffer = predicates,
|
||||
.offset = (slice * clears.size() + key) * sizeof(uint32_t)};
|
||||
command.beginConditionalRenderingEXT(&predicate);
|
||||
command.clearAttachments(1, &clears[key], 1, &rect);
|
||||
command.endConditionalRenderingEXT();
|
||||
}
|
||||
}
|
||||
}
|
||||
command.endRendering();
|
||||
CommitGpuWrite(image);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the
|
||||
// scheduler, so discovery runs before final draw uploads and never holds the texture
|
||||
// lock across it.
|
||||
Common::DebugCounters::ScopedTimer timer(
|
||||
Common::DebugCounters::Counter::ColorMetadataReadbacks,
|
||||
Common::DebugCounters::Counter::ColorMetadataReadbackNs);
|
||||
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
|
||||
}
|
||||
const auto slice_size = range.size / layers;
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
uint8_t code = 0;
|
||||
@@ -1148,9 +1216,8 @@ ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
|
||||
ImageId association {};
|
||||
for (const auto id: FindImagesInRegion(stencil.address, stencil.size, false)) {
|
||||
const auto owner = m_slot_images.try_get(id);
|
||||
if (owner != nullptr && owner->info.data == stencil &&
|
||||
owner->info.extent == depth.info.extent) {
|
||||
const auto& owner = m_slot_images[id];
|
||||
if (owner.info.data == stencil && owner.info.extent == depth.info.extent) {
|
||||
association = id;
|
||||
}
|
||||
}
|
||||
@@ -1243,12 +1310,24 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
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) {
|
||||
for (const auto candidate: candidates) {
|
||||
const auto* owner = m_slot_images.try_get(candidate);
|
||||
if (candidate != result && owner != nullptr && owner->registered &&
|
||||
!owner->IsCpuDirty() &&
|
||||
BcAlias::Matches(m_slot_images[result].info, owner->info)) {
|
||||
const auto& owner = m_slot_images[candidate];
|
||||
if (candidate != result && owner.registered && !owner.IsCpuDirty() &&
|
||||
BcAlias::Matches(m_slot_images[result].info, owner.info)) {
|
||||
BcAlias::Resolve(*this, candidate, result);
|
||||
}
|
||||
}
|
||||
@@ -1311,14 +1390,14 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
|
||||
std::scoped_lock lock {m_lock};
|
||||
ImageIds matches;
|
||||
for (const auto id: FindImagesInRegion(address, size, false)) {
|
||||
auto owner = m_slot_images.try_get(id);
|
||||
if (owner == nullptr || owner->info.data.address != address) {
|
||||
const auto* owner = &m_slot_images[id];
|
||||
if (owner->info.data.address != address) {
|
||||
continue;
|
||||
}
|
||||
if (ensure_valid && owner->depth_id) {
|
||||
owner = m_slot_images.try_get(owner->depth_id);
|
||||
owner = &m_slot_images[owner->depth_id];
|
||||
}
|
||||
if (owner == nullptr || (ensure_valid && !owner->SafeToDownload())) {
|
||||
if (ensure_valid && !owner->SafeToDownload()) {
|
||||
continue;
|
||||
}
|
||||
matches.push_back(id);
|
||||
@@ -1335,11 +1414,8 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selected && ensure_valid) {
|
||||
const auto owner = m_slot_images.try_get(selected);
|
||||
if (owner != nullptr && owner->depth_id) {
|
||||
selected = owner->depth_id;
|
||||
}
|
||||
if (selected && ensure_valid && m_slot_images[selected].depth_id) {
|
||||
selected = m_slot_images[selected].depth_id;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
@@ -1360,9 +1436,8 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
||||
desc.info.data.End() <= image.info.stencil.End()) {
|
||||
for (const auto stencil_id:
|
||||
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
|
||||
const auto* stencil = m_slot_images.try_get(stencil_id);
|
||||
if (stencil != nullptr && stencil->depth_id == id &&
|
||||
stencil->info.data == image.info.stencil) {
|
||||
const auto& stencil = m_slot_images[stencil_id];
|
||||
if (stencil.depth_id == id && stencil.info.data == image.info.stencil) {
|
||||
RefreshImage(stencil_id);
|
||||
break;
|
||||
}
|
||||
@@ -1473,27 +1548,24 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
|
||||
vk::ImageAspectFlags aspect {};
|
||||
ImageSubresourceRange subresources;
|
||||
for (const auto id: FindImagesInRegion(address, size, false)) {
|
||||
auto owner = m_slot_images.try_get(id);
|
||||
if (owner == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto& owner = m_slot_images[id];
|
||||
vk::ImageAspectFlags candidate {};
|
||||
ImageId candidate_id = id;
|
||||
ImageSubresourceRange candidate_subresources {
|
||||
0, owner->info.resources.levels, 0, owner->info.TransferLayers()};
|
||||
if (owner->depth_id && owner->info.data.address == address &&
|
||||
owner->info.data.size == size) {
|
||||
0, owner.info.resources.levels, 0, owner.info.TransferLayers()};
|
||||
if (owner.depth_id && owner.info.data.address == address &&
|
||||
owner.info.data.size == size) {
|
||||
candidate = vk::ImageAspectFlagBits::eStencil;
|
||||
candidate_id = owner->depth_id;
|
||||
candidate_subresources = owner->stencil_subresources;
|
||||
owner = m_slot_images.try_get(candidate_id);
|
||||
if (owner == nullptr || owner->backing.image == nullptr || !owner->info.HasStencil()) {
|
||||
candidate_id = owner.depth_id;
|
||||
candidate_subresources = owner.stencil_subresources;
|
||||
const auto& depth = m_slot_images[candidate_id];
|
||||
if (depth.backing.image == nullptr || !depth.info.HasStencil()) {
|
||||
continue;
|
||||
}
|
||||
} else if (!owner->depth_id && owner->info.data.address == address &&
|
||||
owner->info.data.size == size) {
|
||||
candidate = owner->info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor;
|
||||
} else if (!owner.depth_id && owner.info.data.address == address &&
|
||||
owner.info.data.size == size) {
|
||||
candidate = owner.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor;
|
||||
}
|
||||
if (!candidate) {
|
||||
continue;
|
||||
@@ -1574,29 +1646,8 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
|
||||
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
|
||||
range.levelCount != 1);
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = range.baseMipLevel;
|
||||
view.base_layer = range.baseArrayLayer;
|
||||
view.layer_count = range.layerCount;
|
||||
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite, {}, command.Handle());
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = image.FindView(view);
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachment.clearValue = clear;
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = {
|
||||
std::max(image.info.extent.width >> range.baseMipLevel, 1u),
|
||||
std::max(image.info.extent.height >> range.baseMipLevel, 1u)};
|
||||
rendering.layerCount = range.layerCount;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.Handle().beginRendering(&rendering);
|
||||
BeginColorRendering(image, format, range.baseMipLevel, range.baseArrayLayer,
|
||||
range.layerCount, &clear, command.Handle());
|
||||
command.Handle().endRendering();
|
||||
CommitGpuWrite(image);
|
||||
return;
|
||||
@@ -1619,6 +1670,40 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
CommitGpuWrite(image);
|
||||
}
|
||||
|
||||
vk::Rect2D TextureCache::BeginColorRendering(Image& image, vk::Format format, uint32_t level,
|
||||
uint32_t layer, uint32_t layers,
|
||||
const vk::ClearValue* clear,
|
||||
vk::CommandBuffer command) {
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.type = layers == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = level;
|
||||
view.base_layer = layer;
|
||||
view.layer_count = layers;
|
||||
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
{}, command);
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = image.FindView(view);
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eLoad;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
if (clear != nullptr) {
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment.clearValue = *clear;
|
||||
}
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = {std::max(image.info.extent.width >> level, 1u),
|
||||
std::max(image.info.extent.height >> level, 1u)};
|
||||
rendering.layerCount = layers;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.beginRendering(&rendering);
|
||||
return rendering.renderArea;
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid memory-invalidation range\n");
|
||||
@@ -1752,6 +1837,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
copy_size != image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
ForgetColorMetadataChecks(image.info.data.address, copy_size);
|
||||
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels);
|
||||
return true;
|
||||
}
|
||||
@@ -1820,23 +1906,20 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
|
||||
const auto page_begin = Common::AlignDown(address, TRACKER_PAGE_SIZE);
|
||||
const auto page_end = Common::AlignUp(address + size, TRACKER_PAGE_SIZE);
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto owner = m_slot_images.try_get(id);
|
||||
if (owner == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (owner->Overlaps(address, size)) {
|
||||
owner->MarkCpuDirty();
|
||||
auto& owner = m_slot_images[id];
|
||||
if (owner.Overlaps(address, size)) {
|
||||
owner.MarkCpuDirty();
|
||||
UntrackImage(id);
|
||||
continue;
|
||||
}
|
||||
const auto image_begin = owner->info.data.address;
|
||||
const auto image_end = owner->info.data.End();
|
||||
const auto image_begin = owner.info.data.address;
|
||||
const auto image_end = owner.info.data.End();
|
||||
if (page_end < image_end) {
|
||||
UntrackImageHead(id);
|
||||
} else if (image_begin < page_begin) {
|
||||
UntrackImageTail(id);
|
||||
} else {
|
||||
MarkAsMaybeDirty(id, *owner);
|
||||
MarkAsMaybeDirty(id, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1935,6 +2018,11 @@ void TextureCache::RunGarbageCollector() {
|
||||
if (owner == nullptr || !owner->registered || owner->depth_id) {
|
||||
continue;
|
||||
}
|
||||
if (owner->heap_refs.load(std::memory_order_relaxed) != 0u) {
|
||||
// Resident heaps sample it without touching it.
|
||||
TouchImage(*owner);
|
||||
continue;
|
||||
}
|
||||
if (owner->IsGpuModified()) {
|
||||
const bool safe = owner->SafeToDownload();
|
||||
if (safe && owner->info.IsTiled()) {
|
||||
@@ -1971,8 +2059,7 @@ void TextureCache::ProcessDownloadImages() {
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: m_download_images) {
|
||||
const auto owner = m_slot_images.try_get(id);
|
||||
if (owner != nullptr && owner->registered && owner->IsGpuModified()) {
|
||||
if (m_slot_images[id].IsGpuModified()) {
|
||||
(void)DownloadImageMemory(id);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -43,6 +43,7 @@ public:
|
||||
ImageInfo info;
|
||||
ImageViewInfo view_info;
|
||||
BindingType type = BindingType::Texture;
|
||||
bool heap = false; // PS5: resolved for a resident image heap.
|
||||
};
|
||||
|
||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
@@ -150,6 +151,12 @@ private:
|
||||
// Caller holds m_lock. Volume layer ranges select depth slices.
|
||||
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
|
||||
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
|
||||
// Caller holds m_lock and ends rendering. Begins rendering `layers` layers of one color mip
|
||||
// through a `format` view, clearing them or, when `clear` is null, loading them; returns the
|
||||
// render area.
|
||||
vk::Rect2D BeginColorRendering(Image& image, vk::Format format, uint32_t level,
|
||||
uint32_t layer, uint32_t layers, const vk::ClearValue* clear,
|
||||
vk::CommandBuffer command);
|
||||
void PrepareImageCopy(Image& image);
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
@@ -178,13 +185,13 @@ private:
|
||||
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
|
||||
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
mutable uint32_t m_image_query_epoch = 0;
|
||||
bool m_readback_linear_images = false;
|
||||
bool m_readback_tiled_images = false;
|
||||
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class BufferCache;
|
||||
friend class RenderExecutor;
|
||||
friend class ImageHeap;
|
||||
friend bool BcAlias::Matches(const ImageInfo&, const ImageInfo&);
|
||||
friend bool BcAlias::Publish(TextureCache&, Image&);
|
||||
friend void BcAlias::Resolve(TextureCache&, ImageId, ImageId);
|
||||
|
||||
@@ -595,13 +595,6 @@ void LogDrawPhase(const char* draw_name, const char* phase) {
|
||||
}
|
||||
}
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: %s VS=%" PRIu64 " PS=%" PRIu64 "\n", phase, vertex_program_id,
|
||||
pixel_program_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void VpPrint(const char* func, const HW::ScreenViewport& vp,
|
||||
const HW::ScanModeControl& smc) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
@@ -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) {
|
||||
dirty_state.blend_constants = false;
|
||||
cmdbuf.setBlendConstants(blend_constants.data());
|
||||
|
||||
@@ -52,6 +52,9 @@ struct DynamicState {
|
||||
bool stencil_back_write_mask : 1;
|
||||
bool stencil_back_compare_mask : 1;
|
||||
|
||||
bool cull_mode : 1;
|
||||
bool front_face : 1;
|
||||
|
||||
bool blend_constants : 1;
|
||||
bool color_write_enables : 1;
|
||||
bool line_width : 1;
|
||||
@@ -86,6 +89,9 @@ struct DynamicState {
|
||||
uint32_t stencil_back_write_mask {};
|
||||
uint32_t stencil_back_compare_mask {};
|
||||
|
||||
vk::CullModeFlags cull_mode {};
|
||||
vk::FrontFace front_face {};
|
||||
|
||||
std::array<float, 4> blend_constants {};
|
||||
std::array<vk::Bool32, RENDER_COLOR_ATTACHMENTS_MAX> 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_) {
|
||||
if (blend_constants != blend_constants_) {
|
||||
blend_constants = blend_constants_;
|
||||
|
||||
@@ -90,7 +90,8 @@ ImageCreateFlags(const GraphicContext& graphics, const ImageInfo& info, vk::Imag
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
|
||||
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||
}
|
||||
if (info.samples == 1) {
|
||||
if (graphics.GetPhysicalDeviceProperties().limits.storageImageSampleCounts &
|
||||
static_cast<vk::SampleCountFlagBits>(info.samples)) {
|
||||
usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
return usage;
|
||||
@@ -211,6 +212,13 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destinat
|
||||
barriers.push_back(barrier);
|
||||
}
|
||||
|
||||
// Resident heaps sample their images in the read state; only leaving it invalidates them.
|
||||
if (std::ranges::any_of(barriers, [&](const vk::ImageMemoryBarrier2& barrier) {
|
||||
return barrier.oldLayout == ReadLayout() &&
|
||||
barrier.srcAccessMask == vk::AccessFlagBits2::eShaderRead;
|
||||
})) {
|
||||
HeapChanged();
|
||||
}
|
||||
state = {destination_stage, destination_access, destination_layout};
|
||||
return barriers;
|
||||
}
|
||||
@@ -417,15 +425,15 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const auto destination_height =
|
||||
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
||||
const bool copy = source.backing.samples == 1;
|
||||
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
|
||||
info.extent.width > destination_width || info.extent.height > destination_height ||
|
||||
EXIT_IF(layers == 0 ||
|
||||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
||||
: source.backing.format != backing.format));
|
||||
auto resolved_source_range = source_range;
|
||||
auto resolved_destination_range = destination_range;
|
||||
resolved_source_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();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <compare>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
@@ -71,6 +72,7 @@ public:
|
||||
void MarkCpuDirty() {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
HeapChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
|
||||
@@ -80,6 +82,7 @@ public:
|
||||
if (!IsCpuDirty()) {
|
||||
m_maybe_cpu_hash = HashGuestEdges();
|
||||
m_maybe_cpu_dirty = true;
|
||||
HeapChanged();
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
|
||||
@@ -104,7 +107,10 @@ public:
|
||||
void ClearGpuModified() noexcept { m_gpu_modified = false; }
|
||||
|
||||
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
|
||||
void MarkBufferModified() noexcept { m_buffer_modified = true; }
|
||||
void MarkBufferModified() noexcept {
|
||||
m_buffer_modified = true;
|
||||
HeapChanged();
|
||||
}
|
||||
void ClearBufferModified() noexcept { m_buffer_modified = false; }
|
||||
|
||||
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size,
|
||||
@@ -120,14 +126,30 @@ public:
|
||||
return backing.image == nullptr ? 0 : Common::AlignUp(info.data.size, 1024);
|
||||
}
|
||||
[[nodiscard]] uint64_t HashGuestEdges() const;
|
||||
[[nodiscard]] vk::ImageLayout ReadLayout() const noexcept {
|
||||
return info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
// Resident image heaps sample an image without rediscovering it until one changes its
|
||||
// contents, tracking, identity or layout.
|
||||
void HeapChanged() const noexcept {
|
||||
if (heap_refs.load(std::memory_order_relaxed) != 0u) {
|
||||
heap_epoch.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::atomic<uint64_t> heap_epoch {0};
|
||||
static inline std::atomic<uint64_t> serials {0};
|
||||
|
||||
// Unique per image, unlike ImageId: freed slots are reused.
|
||||
const uint64_t serial = serials.fetch_add(1u, std::memory_order_relaxed) + 1u;
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
std::vector<CachedImageView> views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
mutable uint32_t query_epoch = 0;
|
||||
mutable bool picked = false; // Set only while FindImagesInRegion collects it.
|
||||
uint64_t track_addr = 0;
|
||||
uint64_t track_addr_end = 0;
|
||||
ImageId depth_id {};
|
||||
@@ -137,6 +159,8 @@ public:
|
||||
ImageSubresourceRange stencil_subresources;
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
// Resident image heap elements that select this image.
|
||||
std::atomic<uint32_t> heap_refs {0};
|
||||
|
||||
private:
|
||||
friend struct ImageTestAccess;
|
||||
|
||||
@@ -378,6 +378,9 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
||||
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);
|
||||
};
|
||||
const auto unorm16 = [](uint64_t value) {
|
||||
return static_cast<float>(value & 0xffffu) / 65535.0f;
|
||||
};
|
||||
const auto float16 = [](uint32_t value) {
|
||||
const auto sign = (value & 0x8000u) << 16u;
|
||||
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);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
||||
case vk::Format::eR16G16Unorm:
|
||||
next.float32[1] = static_cast<float>(packed >> 16u) / 65535.0f;
|
||||
case vk::Format::eR16G16B16A16Unorm:
|
||||
next.float32[2] = unorm16(clear_words >> 32u);
|
||||
next.float32[3] = unorm16(clear_words >> 48u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Unorm:
|
||||
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
|
||||
case vk::Format::eR16G16Unorm:
|
||||
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;
|
||||
// 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:
|
||||
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
||||
[[fallthrough]];
|
||||
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::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
case vk::Format::eR32G32Uint:
|
||||
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:
|
||||
next.float32[0] = srgb8(packed);
|
||||
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.
|
||||
[[nodiscard]] inline bool DecodeColorDwordFill(vk::Format format, uint32_t packed,
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
if ((format == vk::Format::eR8Unorm || format == vk::Format::eR8Uint) &&
|
||||
packed != (packed & 0xffu) * 0x01010101u) {
|
||||
return false;
|
||||
}
|
||||
if (format == vk::Format::eR16G16B16A16Sfloat) {
|
||||
if (packed != 0) {
|
||||
return false;
|
||||
|
||||
@@ -101,6 +101,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_color_transform_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_color_clear_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
@@ -71,20 +72,11 @@ TileManager::~TileManager() {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_d16_to_d24 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d24, nullptr);
|
||||
}
|
||||
if (m_d16_to_d32 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d32, nullptr);
|
||||
}
|
||||
if (m_d24_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d24_to_d16, nullptr);
|
||||
}
|
||||
if (m_d32_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d32_to_d16, nullptr);
|
||||
}
|
||||
if (m_color_transform != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_color_transform, nullptr);
|
||||
for (auto pipeline: {m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform,
|
||||
m_color_clear}) {
|
||||
if (pipeline != nullptr) {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
@@ -220,42 +212,37 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
if (m_pipelines[slot] != nullptr) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
struct Shader {
|
||||
const uint32_t* code;
|
||||
size_t words;
|
||||
};
|
||||
static constexpr std::array<Shader, FamilyCount> shaders {{
|
||||
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
|
||||
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
|
||||
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
|
||||
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
|
||||
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
|
||||
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
|
||||
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
|
||||
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
|
||||
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
|
||||
}};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
static constexpr std::array<std::span<const uint32_t>, FamilyCount> shaders {
|
||||
GPU_TILER_STANDARD256_SPV, GPU_TILER_STANDARD4_SPV, GPU_TILER_STANDARD4_3D_SPV,
|
||||
GPU_TILER_STANDARD64_SPV, GPU_TILER_STANDARD64_3D_SPV, GPU_TILER_PRT_SPV,
|
||||
GPU_TILER_PRT_3D_SPV, GPU_TILER_RENDER_TARGET_SPV, GPU_TILER_DEPTH_SPV};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
const uint32_t direction_index = slot / (FamilyCount * BytesPerElementCount);
|
||||
const uint32_t family_index = (slot / BytesPerElementCount) % FamilyCount;
|
||||
const uint32_t values[] {1u << element_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
const auto module =
|
||||
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
|
||||
m_pipelines[slot] = CreatePipeline(shaders[family_index], &specialization);
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
vk::Pipeline TileManager::CreatePipeline(std::span<const uint32_t> code,
|
||||
const vk::SpecializationInfo* specialization) const {
|
||||
const auto module = CompileSPV(code, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
stage.pSpecializationInfo = specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_pipelines[slot]);
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create TileManager pipeline");
|
||||
return m_pipelines[slot];
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void TileManager::Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
@@ -434,39 +421,21 @@ uint32_t TileManager::ConversionRows(uint64_t offset, uint64_t row_stride, uint6
|
||||
|
||||
void TileManager::ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout) {
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
std::span<const uint32_t> code;
|
||||
if (direction == D16Direction::Promote) {
|
||||
pipeline_pointer = d32 ? &m_d16_to_d32 : &m_d16_to_d24;
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
} else {
|
||||
pipeline_pointer = d32 ? &m_d32_to_d16 : &m_d24_to_d16;
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
}
|
||||
auto& pipeline = *pipeline_pointer;
|
||||
if (pipeline == nullptr) {
|
||||
const uint32_t value = d32 ? 1u : 0u;
|
||||
const vk::SpecializationMapEntry entry {0, 0, sizeof(value)};
|
||||
const vk::SpecializationInfo specialization {1, &entry, sizeof(value), &value};
|
||||
const uint32_t* code = nullptr;
|
||||
size_t words = 0;
|
||||
if (direction == D16Direction::Promote) {
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_PROMOTE_D16_SPV);
|
||||
} else {
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
const auto module = CompileSPV({code, words}, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create D16 conversion pipeline");
|
||||
pipeline = CreatePipeline(code, &specialization);
|
||||
}
|
||||
|
||||
const uint64_t source_element =
|
||||
@@ -515,7 +484,7 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
vk::AccessFlagBits::eHostWrite |
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
|
||||
@@ -599,18 +568,7 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
|
||||
output.size < input.size);
|
||||
const auto pixels = static_cast<uint32_t>(input.size / element_bytes);
|
||||
if (m_color_transform == nullptr) {
|
||||
const auto module = CompileSPV(GPU_TILER_COLOR_TRANSFORM_SPV, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_color_transform);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create color transform pipeline");
|
||||
m_color_transform = CreatePipeline(GPU_TILER_COLOR_TRANSFORM_SPV, nullptr);
|
||||
}
|
||||
const auto input_binding = BindStorage(input, input.size);
|
||||
const auto output_binding = BindStorage(output, input.size);
|
||||
@@ -670,4 +628,60 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand) {
|
||||
struct Check {
|
||||
uint32_t metadata_base;
|
||||
uint32_t words;
|
||||
uint32_t keys;
|
||||
uint32_t expand;
|
||||
};
|
||||
static_assert(sizeof(Check) <= sizeof(Conversion));
|
||||
const uint32_t max_slices =
|
||||
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX);
|
||||
if (m_color_clear == nullptr) {
|
||||
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
|
||||
m_clear_predicates = std::make_unique<Buffer>(
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eConditionalRenderingEXT,
|
||||
uint64_t {max_slices} * ColorClearSlots * sizeof(uint32_t));
|
||||
}
|
||||
const auto source = BindStorage(metadata, metadata.size);
|
||||
const vk::DescriptorBufferInfo infos[] {source.info,
|
||||
{m_clear_predicates->Handle(), 0, VK_WHOLE_SIZE}};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
// Orders the metadata writers and the previous check's predicate reads before this check.
|
||||
vk::MemoryBarrier barrier {
|
||||
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, nullptr,
|
||||
0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys,
|
||||
expand};
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch(slices, 1, 1);
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 1, &barrier, 0, nullptr,
|
||||
0, nullptr);
|
||||
return m_clear_predicates->Handle();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -54,6 +54,13 @@ public:
|
||||
const D16Layout& layout);
|
||||
[[nodiscard]] Result TransformColor(Result input, ColorTransform transform, bool to_host);
|
||||
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
|
||||
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
|
||||
static constexpr uint32_t ColorClearSlots = 8;
|
||||
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte
|
||||
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when
|
||||
// `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
@@ -117,6 +124,8 @@ private:
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
const Tiling& tiling, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
[[nodiscard]] vk::Pipeline CreatePipeline(std::span<const uint32_t> code,
|
||||
const vk::SpecializationInfo* specialization) const;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
@@ -130,6 +139,8 @@ private:
|
||||
vk::Pipeline m_d24_to_d16 = nullptr;
|
||||
vk::Pipeline m_d32_to_d16 = nullptr;
|
||||
vk::Pipeline m_color_transform = nullptr;
|
||||
vk::Pipeline m_color_clear = nullptr;
|
||||
std::unique_ptr<Buffer> m_clear_predicates;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
// Queries no dump has read yet; past them a draw counts one sample.
|
||||
constexpr uint32_t QUERY_COUNT = 16384;
|
||||
|
||||
OcclusionQueries::OcclusionQueries(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_scheduler(scheduler) {
|
||||
if (!graphics.occlusion_queries_enabled) {
|
||||
return;
|
||||
}
|
||||
m_device = graphics.device;
|
||||
const vk::QueryPoolCreateInfo info {.queryType = vk::QueryType::eOcclusion,
|
||||
.queryCount = QUERY_COUNT};
|
||||
RequireVulkanSuccess(m_device.createQueryPool(&info, nullptr, &m_pool), "vkCreateQueryPool");
|
||||
m_device.resetQueryPool(m_pool, 0, QUERY_COUNT);
|
||||
m_results.resize(QUERY_COUNT);
|
||||
if (graphics.occlusion_query_precise_enabled) {
|
||||
m_control = vk::QueryControlFlagBits::ePrecise;
|
||||
}
|
||||
}
|
||||
|
||||
OcclusionQueries::~OcclusionQueries() {
|
||||
if (m_pool) {
|
||||
m_device.destroyQueryPool(m_pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t OcclusionQueries::BeginQuery(vk::CommandBuffer command) {
|
||||
if (m_begun - m_read == QUERY_COUNT) {
|
||||
m_uncounted++;
|
||||
return -1;
|
||||
}
|
||||
const auto query = static_cast<uint32_t>(m_begun++ % QUERY_COUNT);
|
||||
command.beginQuery(m_pool, query, m_control);
|
||||
return static_cast<int32_t>(query);
|
||||
}
|
||||
|
||||
void OcclusionQueries::Dump(uint64_t address) {
|
||||
// Begins dump to the results, ends 8 bytes into them.
|
||||
if ((address & 8u) == 0) {
|
||||
m_open++;
|
||||
} else if (m_open != 0) {
|
||||
m_open--;
|
||||
}
|
||||
m_dumps.push_back({address, m_scheduler.CurrentTick(), m_begun, m_uncounted});
|
||||
}
|
||||
|
||||
void OcclusionQueries::PublishDumps() {
|
||||
if (const auto last = m_dumps.back().queries; last != m_read) {
|
||||
m_scheduler.Wait(m_dumps.back().tick);
|
||||
// The unread queries wrap around the pool at most once.
|
||||
for (uint64_t read = m_read; read < last;) {
|
||||
const auto first = static_cast<uint32_t>(read % QUERY_COUNT);
|
||||
const auto count =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(last - read, QUERY_COUNT - first));
|
||||
RequireVulkanSuccess(
|
||||
m_device.getQueryPoolResults(m_pool, first, count, count * sizeof(uint64_t),
|
||||
m_results.data() + first, sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64),
|
||||
"vkGetQueryPoolResults");
|
||||
m_device.resetQueryPool(m_pool, first, count);
|
||||
read += count;
|
||||
}
|
||||
}
|
||||
for (const auto& dump: m_dumps) {
|
||||
for (; m_read < dump.queries; m_read++) {
|
||||
m_samples += m_results[m_read % QUERY_COUNT];
|
||||
}
|
||||
WriteZPassCounters(reinterpret_cast<volatile uint64_t*>(dump.address),
|
||||
m_samples + dump.uncounted, (dump.address & 8u) == 0);
|
||||
}
|
||||
m_dumps.clear();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandScheduler;
|
||||
struct GraphicContext;
|
||||
|
||||
// The guest's query results (16-byte aligned): a begin/end pair of running z-pass counters per DB,
|
||||
// 16 bytes apart; bit 63 marks a written counter. A query's samples are the sum of end - begin.
|
||||
constexpr uint32_t OCCLUSION_QUERY_DBS = 16;
|
||||
constexpr uint64_t OCCLUSION_QUERY_READY = 1ull << 63u;
|
||||
|
||||
// A dump of the running count, all of it in DB 0. A begin also clears its end counters.
|
||||
inline void WriteZPassCounters(volatile uint64_t* results, uint64_t samples, bool begin) {
|
||||
for (uint32_t db = 0; db < OCCLUSION_QUERY_DBS; db++) {
|
||||
results[db * 2] = OCCLUSION_QUERY_READY | (db == 0 ? samples : 0);
|
||||
if (begin) {
|
||||
results[db * 2 + 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The guest's z-pass counters from host occlusion queries: a draw counts its samples while a guest
|
||||
// query is open, and the dumps' counters are written before the guest can see the GPU ran them.
|
||||
class OcclusionQueries {
|
||||
public:
|
||||
OcclusionQueries(GraphicContext& graphics, CommandScheduler& scheduler);
|
||||
~OcclusionQueries();
|
||||
KYTY_CLASS_NO_COPY(OcclusionQueries);
|
||||
|
||||
[[nodiscard]] bool Enabled() const { return m_pool != nullptr; }
|
||||
|
||||
// In the draw's rendering: the query that counts its samples, or -1.
|
||||
[[nodiscard]] int32_t Begin(vk::CommandBuffer command) {
|
||||
return m_open == 0 ? -1 : BeginQuery(command);
|
||||
}
|
||||
void End(vk::CommandBuffer command, int32_t query) const {
|
||||
if (query >= 0) {
|
||||
command.endQuery(m_pool, static_cast<uint32_t>(query));
|
||||
}
|
||||
}
|
||||
|
||||
// A PIXEL_PIPE_STAT_DUMP to address, after the draws recorded so far.
|
||||
void Dump(uint64_t address);
|
||||
// Waits for the GPU to run the dumps and writes their counters.
|
||||
void Publish() {
|
||||
if (!m_dumps.empty()) {
|
||||
PublishDumps();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct PendingDump {
|
||||
uint64_t address;
|
||||
uint64_t tick;
|
||||
uint64_t queries;
|
||||
uint64_t uncounted;
|
||||
};
|
||||
|
||||
int32_t BeginQuery(vk::CommandBuffer command);
|
||||
void PublishDumps();
|
||||
|
||||
CommandScheduler& m_scheduler;
|
||||
vk::Device m_device;
|
||||
vk::QueryPool m_pool;
|
||||
vk::QueryControlFlags m_control;
|
||||
std::vector<PendingDump> m_dumps;
|
||||
std::vector<uint64_t> m_results;
|
||||
uint32_t m_open = 0;
|
||||
uint64_t m_begun = 0;
|
||||
uint64_t m_read = 0;
|
||||
uint64_t m_samples = 0;
|
||||
uint64_t m_uncounted = 0;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||
@@ -254,6 +254,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor3D &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool is_2d_msaa =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor2DMsaa && descriptor.Depth() == 0 &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
TileTextureBlockLayout tile_layout {};
|
||||
bool supported_tile = false;
|
||||
switch (tile) {
|
||||
@@ -284,8 +288,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool supported_swizzle =
|
||||
IsValidImageSwizzle(swizzle) &&
|
||||
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
|
||||
descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d || is_2d_msaa) &&
|
||||
supported_tile && descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
|
||||
!descriptor.MsaaDepth();
|
||||
}
|
||||
@@ -356,7 +360,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
}
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping =
|
||||
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
storage || resource.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
? vk::ComponentMapping {}
|
||||
: TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage);
|
||||
switch (resource.dimension) {
|
||||
@@ -515,7 +519,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto format = descriptor.Format();
|
||||
const auto format = ShaderRecompiler::IR::ReadsDepthBits(resource)
|
||||
? Prospero::BufferFormat::k16UNorm
|
||||
: descriptor.Format();
|
||||
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
||||
const bool shader_conversion =
|
||||
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
@@ -579,6 +585,10 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const auto alignment = desc.info.DataAlignment();
|
||||
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
|
||||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
|
||||
// Image heaps keep the descriptors of unmapped textures, which PS5 never samples.
|
||||
if (resource.heap && !m_context.IsMapped(address, desc.info.data.size)) {
|
||||
return {.layout = vk::ImageLayout::eGeneral};
|
||||
}
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, desc.info.data.size);
|
||||
}
|
||||
@@ -594,8 +604,13 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.view_info.base_level = view_base;
|
||||
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);
|
||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||
if (stencil_association) {
|
||||
@@ -662,9 +677,7 @@ void RenderExecutor::BindRenderTarget(ImageId id) {
|
||||
|
||||
void RenderExecutor::ResetBindings() {
|
||||
for (const auto id: m_bound_images) {
|
||||
if (auto* image = m_context.GetTextureCache().m_slot_images.try_get(id); image != nullptr) {
|
||||
image->binding = {};
|
||||
}
|
||||
m_context.GetTextureCache().m_slot_images[id].binding = {};
|
||||
}
|
||||
m_bound_images.clear();
|
||||
}
|
||||
@@ -673,7 +686,7 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& program = runtime.module->info;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
prepared.runtime = &runtime;
|
||||
prepared.gds = {nullptr, 0, VK_WHOLE_SIZE};
|
||||
@@ -684,6 +697,10 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
prepared.samplers.clear();
|
||||
prepared.shader_data.clear();
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
if (program.info.images[i].heap) {
|
||||
prepared.images[i] = {};
|
||||
continue;
|
||||
}
|
||||
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
|
||||
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
|
||||
binding.mip_views.swap(prepared.images[i].mip_views);
|
||||
@@ -711,7 +728,7 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
for (auto* stage: stages) {
|
||||
auto& prepared = *stage;
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& program = prepared.runtime->module->info;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
prepared.buffer_sources.resize(layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
@@ -768,7 +785,7 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& program = prepared.runtime->module->info;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
@@ -789,6 +806,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||
resource, 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) {
|
||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||
@@ -801,7 +821,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& program = prepared.runtime->module->info;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
auto& images = prepared.images;
|
||||
EXIT_IF(images.size() != program.info.images.size());
|
||||
@@ -809,12 +829,9 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
// Acquire each view before a later overlapping descriptor can replace its image.
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
if (!images[i].image_id) continue;
|
||||
const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id);
|
||||
if (old_image == nullptr || !old_image->registered ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
auto& old_image = texture_cache.m_slot_images[images[i].image_id];
|
||||
if (!old_image.registered || old_image.binding.needs_rebind) {
|
||||
old_image.binding = {};
|
||||
images[i] = ResolveTexture(program.info.images[i], snapshot.images[i]);
|
||||
BindImage(images[i].image_id,
|
||||
images[i].desc.type == TextureCache::BindingType::Storage);
|
||||
@@ -846,26 +863,28 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
|
||||
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors) {
|
||||
bool uses_dma = false;
|
||||
bool uses_dma = false;
|
||||
bool uses_image_heap = false;
|
||||
FindBuffers(stages);
|
||||
for (auto* stage: stages) {
|
||||
uses_dma |= stage->runtime->program->info.uses_dma;
|
||||
uses_dma |= stage->runtime->module->info.info.uses_dma;
|
||||
uses_image_heap |= stage->runtime->module->info.info.uses_image_heap;
|
||||
}
|
||||
if (uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
if (uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
for (auto* stage: stages) {
|
||||
RebindImages(*stage);
|
||||
}
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
for (auto& target: colors) {
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.m_slot_images.try_get(target.image_id);
|
||||
if (old_image == nullptr || !old_image->registered ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
auto& old_image = cache.m_slot_images[target.image_id];
|
||||
if (!old_image.registered || old_image.binding.needs_rebind) {
|
||||
old_image.binding = {};
|
||||
target.desc.view_info.base_level = target.guest_mip_level;
|
||||
target.desc.view_info.base_layer = target.guest_array_layer;
|
||||
target.image_id = cache.FindImage(target.desc);
|
||||
@@ -898,7 +917,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
: vk::ShaderStageFlags {};
|
||||
for (const auto* prepared: prepared_bindings) {
|
||||
EXIT_IF(prepared == nullptr || prepared->runtime == nullptr || !*prepared->runtime);
|
||||
const auto& program = *prepared->runtime->program;
|
||||
const auto& program = prepared->runtime->module->info;
|
||||
write_count += std::popcount(program.bindings.descriptor_mask);
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
@@ -919,17 +938,16 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
const auto& reads = reader->runtime->resources->specialization_reads;
|
||||
if (reads.empty()) continue;
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
if (writer->runtime->program->has_address_writes) {
|
||||
if (writer->runtime->module->info.has_address_writes) {
|
||||
EXIT("scalar resource reads cannot be proven disjoint from shader address writes\n");
|
||||
}
|
||||
}
|
||||
for (const auto [address, size]: reads) {
|
||||
for (const auto id: m_bound_images) {
|
||||
const auto* image = m_context.GetTextureCache().m_slot_images.try_get(id);
|
||||
if (image == nullptr ||
|
||||
(!image->binding.shader_write && !image->binding.is_target)) continue;
|
||||
for (const auto written: {image->info.data, image->info.stencil,
|
||||
image->info.metadata.range}) {
|
||||
const auto& image = m_context.GetTextureCache().m_slot_images[id];
|
||||
if (!image.binding.shader_write && !image.binding.is_target) continue;
|
||||
for (const auto written: {image.info.data, image.info.stencil,
|
||||
image.info.metadata.range}) {
|
||||
if (written.size != 0 && ImageRangeOverlaps(address, size,
|
||||
written.address, written.size)) {
|
||||
EXIT("scalar resource reads overlap an image or attachment write\n");
|
||||
@@ -937,7 +955,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
const auto& program = *writer->runtime->program;
|
||||
const auto& program = writer->runtime->module->info;
|
||||
for (const auto& resource: program.info.buffers) {
|
||||
if (!resource.written || resource.descriptor_index == UINT32_MAX) continue;
|
||||
const auto& written = writer->buffer_sources[resource.descriptor_index];
|
||||
@@ -953,6 +971,9 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pipeline.uses_image_heap) {
|
||||
m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout);
|
||||
}
|
||||
m_descriptor_images.resize(image_descriptor_count);
|
||||
m_descriptor_writes.clear();
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
@@ -960,7 +981,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
std::array<vk::DescriptorBufferInfo, 2> address_buffers;
|
||||
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
const auto& program = *prepared->runtime->program;
|
||||
const auto& program = prepared->runtime->module->info;
|
||||
auto& descriptors = *prepared;
|
||||
const auto shader_stage = NativeShaderStage(program.stage);
|
||||
const auto shader_stages = ShaderPipelineStages(shader_stage);
|
||||
@@ -1010,9 +1031,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
|
||||
range, vk_buffer);
|
||||
} else {
|
||||
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
image.Transit(image.ReadLayout(), vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
}
|
||||
binding.layout = image.backing.state.layout;
|
||||
}
|
||||
@@ -1083,6 +1102,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& resource = program.info.images[i];
|
||||
const auto& texture = descriptors.images[i];
|
||||
if (resource.heap) continue;
|
||||
EXIT_IF(texture.image_id &&
|
||||
resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
|
||||
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource);
|
||||
|
||||
@@ -0,0 +1,638 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
|
||||
// Smaller tables are often written for one draw, so they enter on a second draw of a frame.
|
||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||
|
||||
template <typename Pred, typename Fn>
|
||||
void ForEachRun(size_t begin, size_t end, Pred&& pred, Fn&& fn) {
|
||||
while (begin < end) {
|
||||
if (!pred(begin)) {
|
||||
++begin;
|
||||
continue;
|
||||
}
|
||||
auto run = begin + 1u;
|
||||
while (run < end && pred(run)) ++run;
|
||||
fn(begin, run);
|
||||
begin = run;
|
||||
}
|
||||
}
|
||||
|
||||
// Grows [begin, end) to cover [first, last).
|
||||
void Widen(size_t& begin, size_t& end, size_t first, size_t last) {
|
||||
const bool empty = begin == end;
|
||||
begin = empty ? first : std::min(begin, first);
|
||||
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.
|
||||
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);
|
||||
return {std::max(base, origin + begin * TRACKER_PAGE_SIZE),
|
||||
std::min(base + size, origin + end * TRACKER_PAGE_SIZE)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t ImageHeap::DescriptorHash::operator()(const DescriptorValue& value) const noexcept {
|
||||
return XXH3_64bits(value.dwords.data(), sizeof(value.dwords));
|
||||
}
|
||||
|
||||
ImageHeap::ImageHeap(RenderContext& context, PageManager& page_manager)
|
||||
: m_context(context), m_page_manager(page_manager) {
|
||||
auto& graphics = context.GetGraphics();
|
||||
vk::PhysicalDeviceVulkan12Properties limits {};
|
||||
vk::PhysicalDeviceProperties2 properties {};
|
||||
properties.pNext = &limits;
|
||||
graphics.physical_device.getProperties2(&properties);
|
||||
EXIT_NOT_IMPLEMENTED(std::min({limits.maxDescriptorSetUpdateAfterBindSampledImages,
|
||||
limits.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
||||
limits.maxPerStageUpdateAfterBindResources}) <
|
||||
IR::ImageHeapCapacity);
|
||||
|
||||
const vk::DescriptorBindingFlags binding_flags =
|
||||
vk::DescriptorBindingFlagBits::ePartiallyBound |
|
||||
vk::DescriptorBindingFlagBits::eUpdateAfterBind |
|
||||
vk::DescriptorBindingFlagBits::eUpdateUnusedWhilePending;
|
||||
vk::DescriptorSetLayoutBindingFlagsCreateInfo flags_info {};
|
||||
flags_info.bindingCount = 1;
|
||||
flags_info.pBindingFlags = &binding_flags;
|
||||
const vk::DescriptorSetLayoutBinding binding {0, vk::DescriptorType::eSampledImage,
|
||||
IR::ImageHeapCapacity,
|
||||
vk::ShaderStageFlagBits::eAll, nullptr};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info {};
|
||||
layout_info.pNext = &flags_info;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool;
|
||||
layout_info.bindingCount = 1;
|
||||
layout_info.pBindings = &binding;
|
||||
EXIT_IF(graphics.device.createDescriptorSetLayout(&layout_info, nullptr, &m_layout) !=
|
||||
vk::Result::eSuccess);
|
||||
const vk::DescriptorPoolSize pool_size {vk::DescriptorType::eSampledImage,
|
||||
IR::ImageHeapCapacity};
|
||||
vk::DescriptorPoolCreateInfo pool_info {};
|
||||
pool_info.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind;
|
||||
pool_info.maxSets = 1;
|
||||
pool_info.poolSizeCount = 1;
|
||||
pool_info.pPoolSizes = &pool_size;
|
||||
EXIT_IF(graphics.device.createDescriptorPool(&pool_info, nullptr, &m_pool) !=
|
||||
vk::Result::eSuccess);
|
||||
vk::DescriptorSetAllocateInfo allocate {};
|
||||
allocate.descriptorPool = m_pool;
|
||||
allocate.descriptorSetCount = 1;
|
||||
allocate.pSetLayouts = &m_layout;
|
||||
EXIT_IF(graphics.device.allocateDescriptorSets(&allocate, &m_set) != vk::Result::eSuccess);
|
||||
graphics.image_heap_layout = m_layout;
|
||||
// Slot word 0 selects element 0, which stays a null image.
|
||||
m_elements.push_back({.binding = {.layout = vk::ImageLayout::eShaderReadOnlyOptimal}});
|
||||
m_writes.push_back(0u);
|
||||
WriteDescriptors();
|
||||
}
|
||||
|
||||
ImageHeap::~ImageHeap() {
|
||||
for (const auto& memory: m_memories) Unwatch(*memory);
|
||||
auto& graphics = m_context.GetGraphics();
|
||||
graphics.image_heap_layout = nullptr;
|
||||
graphics.device.destroyDescriptorPool(m_pool, nullptr);
|
||||
graphics.device.destroyDescriptorSetLayout(m_layout, nullptr);
|
||||
}
|
||||
|
||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto size = request.size & ~uint64_t {3};
|
||||
if (request.count == 0u || request.step == 0u ||
|
||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||
return false;
|
||||
}
|
||||
auto memory = std::ranges::find_if(
|
||||
m_memories, [&](const auto& candidate) { return candidate->base == request.base; });
|
||||
if (memory == m_memories.end() && size < MinTableBytes &&
|
||||
m_sightings.try_emplace(request.base, m_draw).first->second == m_draw) {
|
||||
return false;
|
||||
}
|
||||
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||
if (!m_context.IsMapped(request.base, size)) return false;
|
||||
if (memory == m_memories.end()) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
memory = m_memories.insert(m_memories.end(),
|
||||
std::make_unique<Memory>(Memory {.base = request.base}));
|
||||
}
|
||||
Resize(**memory, size);
|
||||
}
|
||||
// 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.
|
||||
if ((*memory)->draw != m_draw) {
|
||||
if (!Refresh(**memory)) return false;
|
||||
(*memory)->draw = m_draw;
|
||||
}
|
||||
auto& views = (*memory)->views;
|
||||
const auto bytes = (*memory)->size;
|
||||
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) {
|
||||
return candidate->step == request.step && candidate->first == request.first &&
|
||||
candidate->limit == limit && candidate->profile == profile;
|
||||
});
|
||||
if (view == views.end()) {
|
||||
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,
|
||||
.step = request.step,
|
||||
.first = request.first,
|
||||
.count = count,
|
||||
.limit = limit,
|
||||
.profile = profile,
|
||||
.resolve_epoch = epoch,
|
||||
.layout_epoch = epoch});
|
||||
created->slots.resize(bytes / 4u + 1u);
|
||||
created->buffer = SlotBuffer(m_context, bytes);
|
||||
UpdateProbes(*created, 0u, bytes);
|
||||
// The first upload also clears the slots no probe selects.
|
||||
created->dirty_begin = 0;
|
||||
created->dirty_end = created->slots.size();
|
||||
view = views.insert(views.end(), std::move(created));
|
||||
} else {
|
||||
// Empty after the first acquire in a draw, since the table refreshes once per draw.
|
||||
UpdateProbes(**view, (*view)->changed_begin, (*view)->changed_end);
|
||||
(*view)->changed_begin = (*view)->changed_end = 0;
|
||||
}
|
||||
if (std::ranges::find(m_active, view->get()) == m_active.end()) m_active.push_back(view->get());
|
||||
const auto& selected = **view;
|
||||
result = {.slots = selected.buffer->BufferDeviceAddress(),
|
||||
.slot_count = static_cast<uint32_t>(selected.slots.size() - 1u),
|
||||
.kinds = selected.kinds};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageHeap::Refresh(Memory& memory) {
|
||||
// GPU writes leave no fault; reading the table downloads them.
|
||||
const bool gpu_dirty = m_context.GetBufferCache().HasGpuDirtyBytes(memory.base, memory.size);
|
||||
const auto pages = memory.dirty_pages.size();
|
||||
m_runs.clear();
|
||||
{
|
||||
std::scoped_lock lock {m_mutex};
|
||||
// Watch before reading, so a write during the read faults the page again.
|
||||
ForEachRun(
|
||||
0, pages, [&](size_t page) { return memory.dirty_pages[page] != 0u; },
|
||||
[&](size_t begin, size_t end) {
|
||||
std::fill(memory.dirty_pages.begin() + begin, memory.dirty_pages.begin() + end,
|
||||
uint8_t {0});
|
||||
Watch<true>(memory, begin, end);
|
||||
m_runs.emplace_back(begin, end);
|
||||
});
|
||||
}
|
||||
if (gpu_dirty) m_runs.assign(1u, {0u, pages});
|
||||
for (size_t run = 0; run < m_runs.size(); ++run) {
|
||||
const auto [address, end] = PageRange(memory.base, memory.size, m_runs[run].first,
|
||||
m_runs[run].second);
|
||||
m_scratch.resize((end - address) / 4u);
|
||||
if (!LibKernel::Memory::TryReadBufferBacking(address, m_scratch.data(), end - address)) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (; run < m_runs.size(); ++run) {
|
||||
MarkDirty(memory, m_runs[run].first, m_runs[run].second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto words = std::span(memory.words).subspan((address - memory.base) / 4u,
|
||||
m_scratch.size());
|
||||
size_t first = 0;
|
||||
size_t last = words.size();
|
||||
while (first < last && words[first] == m_scratch[first]) ++first;
|
||||
while (first < last && words[last - 1u] == m_scratch[last - 1u]) --last;
|
||||
if (first == last) continue;
|
||||
std::copy(m_scratch.begin() + first, m_scratch.begin() + last, words.begin() + first);
|
||||
const auto offset = address - memory.base;
|
||||
for (auto& view: memory.views) {
|
||||
Widen(view->changed_begin, view->changed_end, offset + first * 4u, offset + last * 4u);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageHeap::MarkDirty(Memory& memory, size_t begin, size_t end) {
|
||||
ForEachRun(
|
||||
begin, end, [&](size_t page) { return memory.dirty_pages[page] == 0u; },
|
||||
[&](size_t first, size_t last) {
|
||||
std::fill(memory.dirty_pages.begin() + first, memory.dirty_pages.begin() + last,
|
||||
uint8_t {1});
|
||||
Watch<false>(memory, first, last);
|
||||
});
|
||||
}
|
||||
|
||||
template <bool track>
|
||||
void ImageHeap::Watch(const Memory& memory, size_t begin, size_t end) {
|
||||
const auto [address, limit] = PageRange(memory.base, memory.size, begin, end);
|
||||
m_page_manager.UpdatePageWatchers<track>(address, limit - address);
|
||||
}
|
||||
|
||||
void ImageHeap::Unwatch(const Memory& memory) {
|
||||
ForEachRun(
|
||||
0, memory.dirty_pages.size(), [&](size_t page) { return memory.dirty_pages[page] == 0u; },
|
||||
[&](size_t begin, size_t end) { Watch<false>(memory, begin, end); });
|
||||
}
|
||||
|
||||
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (auto& memory: m_memories) {
|
||||
// Watches cover whole pages, so a write next to the table must unwatch its page too.
|
||||
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,
|
||||
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::UpdateProbes(View& view, uint64_t begin, uint64_t end) {
|
||||
// A probe reads its descriptor from first + probe * step.
|
||||
const uint64_t bytes = view.profile.r128 ? 16u : 32u;
|
||||
auto probe = begin < view.first + bytes ? 0u : (begin - view.first - bytes) / view.step + 1u;
|
||||
for (; probe < view.count && view.first + probe * view.step < end; ++probe) {
|
||||
UpdateProbe(view, probe);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::UpdateProbe(View& view, uint64_t probe) {
|
||||
const auto& words = view.memory.words;
|
||||
const auto offset = (view.first + probe * view.step) / 4u;
|
||||
DescriptorValue value {.dword_count = 8u};
|
||||
std::copy_n(words.begin() + offset,
|
||||
std::min<size_t>(view.profile.r128 ? 4u : 8u, words.size() - offset),
|
||||
value.dwords.begin());
|
||||
// A buried word stays null in every view until its descriptor changes.
|
||||
auto& dead = view.memory.dead[offset];
|
||||
if (dead != 0u && dead != DescriptorHash {}(value)) dead = 0u;
|
||||
const auto kind = dead != 0u ? 0u : IR::ImageHeapKind(view.profile, value);
|
||||
Assign(view, offset, kind == 0u ? 0u : Intern(view, kind, value));
|
||||
}
|
||||
|
||||
void ImageHeap::Assign(View& view, size_t slot, uint32_t entry) {
|
||||
const auto previous = std::exchange(view.slots[slot], entry);
|
||||
Release(view, previous);
|
||||
if (previous != entry) Widen(view.dirty_begin, view.dirty_end, slot, slot + 1u);
|
||||
}
|
||||
|
||||
void ImageHeap::Bury() {
|
||||
if (m_unmapped.empty()) return;
|
||||
for (const auto& memory: m_memories) {
|
||||
for (const auto& view: memory->views) {
|
||||
bool buried = false;
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (binding.image_id && std::ranges::any_of(m_unmapped, [&](GuestRange range) {
|
||||
return ImageRangeOverlaps(binding.desc.info.data, range);
|
||||
})) {
|
||||
m_elements[index].stale = buried = true;
|
||||
}
|
||||
}
|
||||
if (!buried) continue;
|
||||
for (uint64_t probe = 0; probe < view->count; ++probe) {
|
||||
const auto slot = (view->first + probe * view->step) / 4u;
|
||||
const auto& element = m_elements[view->slots[slot] & ElementMask];
|
||||
if (!element.stale) continue;
|
||||
memory->dead[slot] = DescriptorHash {}(element.value);
|
||||
Assign(*view, slot, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_unmapped.clear();
|
||||
}
|
||||
|
||||
uint32_t ImageHeap::Intern(View& view, uint32_t kind, const DescriptorValue& value) {
|
||||
const auto arm = static_cast<uint32_t>(std::ranges::find(view.kind_keys, kind) -
|
||||
view.kind_keys.begin());
|
||||
if (arm == view.kind_keys.size()) {
|
||||
view.kind_keys.push_back(kind);
|
||||
view.kinds.push_back(value);
|
||||
}
|
||||
auto [found, inserted] = view.elements.try_emplace(value, 0u);
|
||||
if (inserted) {
|
||||
found->second = Allocate();
|
||||
auto& element = m_elements[found->second];
|
||||
element.value = value;
|
||||
element.kind = kind;
|
||||
view.pending.push_back(found->second);
|
||||
}
|
||||
++m_elements[found->second].refs;
|
||||
return arm << IR::ImageHeapKindShift | found->second;
|
||||
}
|
||||
|
||||
void ImageHeap::Release(View& view, uint32_t entry) {
|
||||
const auto index = entry & ElementMask;
|
||||
if (index == 0u || --m_elements[index].refs != 0u) return;
|
||||
auto& element = m_elements[index];
|
||||
if (const auto found = view.elements.find(element.value);
|
||||
found != view.elements.end() && found->second == index) {
|
||||
view.elements.erase(found);
|
||||
}
|
||||
Unreference(element);
|
||||
view.released.push_back(index);
|
||||
}
|
||||
|
||||
void ImageHeap::Unreference(Element& element) {
|
||||
if (auto* image = ImageOf(element)) {
|
||||
image->heap_refs.fetch_sub(1u, std::memory_order_relaxed);
|
||||
image->tick_accessed_last = m_context.GetCommandScheduler().CurrentTick();
|
||||
}
|
||||
}
|
||||
|
||||
Image* ImageHeap::ImageOf(const Element& element) const {
|
||||
auto* image = m_context.GetTextureCache().m_slot_images.try_get(element.binding.image_id);
|
||||
return image != nullptr && image->serial == element.serial ? image : nullptr;
|
||||
}
|
||||
|
||||
uint32_t ImageHeap::Allocate() {
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
while (!m_retired.empty() && scheduler.IsFree(m_retired.front().first)) {
|
||||
m_free.push_back(m_retired.front().second);
|
||||
m_retired.pop_front();
|
||||
}
|
||||
if (m_free.empty()) {
|
||||
if (m_elements.size() == IR::ImageHeapCapacity) {
|
||||
EXIT("image heaps exceed %u resident images\n", IR::ImageHeapCapacity);
|
||||
}
|
||||
m_elements.emplace_back();
|
||||
return static_cast<uint32_t>(m_elements.size() - 1u);
|
||||
}
|
||||
const auto index = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_elements[index] = {};
|
||||
return index;
|
||||
}
|
||||
|
||||
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();
|
||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||
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) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto& textures = m_context.GetTextureCache();
|
||||
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) {
|
||||
bool stale = false;
|
||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||
if (view->resolve_epoch != epoch) {
|
||||
view->resolve_epoch = epoch;
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (!binding.image_id) continue;
|
||||
const auto* image = ImageOf(m_elements[index]);
|
||||
if (image == nullptr || !image->registered || image->depth_id ||
|
||||
image->binding.needs_rebind) {
|
||||
mark(index);
|
||||
} else {
|
||||
(void)textures.FindTexture(binding.image_id, binding.desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Discovery applies the color metadata clears written since the last draw.
|
||||
std::erase_if(view->dcc, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
if (element.refs == 0u || element.stale) return true;
|
||||
if (executor.ResolveTexture(IR::ImageHeapResource(view->profile, element.kind),
|
||||
element.value).image_id == element.binding.image_id) {
|
||||
return false;
|
||||
}
|
||||
mark(index);
|
||||
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) {
|
||||
// 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.
|
||||
std::erase_if(view->elements,
|
||||
[&](const auto& item) { return m_elements[item.second].stale; });
|
||||
for (uint64_t probe = 0; probe < view->count; ++probe) {
|
||||
const auto slot = (view->first + probe * view->step) / 4u;
|
||||
const auto& element = m_elements[view->slots[slot] & ElementMask];
|
||||
if (!element.stale) continue;
|
||||
const auto value = element.value; // Intern may grow m_elements.
|
||||
Assign(*view, slot, Intern(*view, element.kind, value));
|
||||
}
|
||||
}
|
||||
for (const auto index: view->pending) {
|
||||
if (m_elements[index].refs == 0u) continue;
|
||||
auto binding = executor.ResolveTexture(
|
||||
IR::ImageHeapResource(view->profile, m_elements[index].kind),
|
||||
m_elements[index].value);
|
||||
if (binding.image_id) {
|
||||
auto& image = textures.GetImage(binding.image_id);
|
||||
image.heap_refs.fetch_add(1u, std::memory_order_relaxed);
|
||||
m_elements[index].serial = image.serial;
|
||||
binding.layout = image.ReadLayout();
|
||||
binding.image_view = textures.FindTexture(binding.image_id, binding.desc);
|
||||
if (binding.desc.info.metadata.kind == ImageMetadataKind::Dcc) {
|
||||
view->dcc.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_writes.push_back(index);
|
||||
}
|
||||
view->pending.clear();
|
||||
}
|
||||
WriteDescriptors();
|
||||
}
|
||||
|
||||
void ImageHeap::WriteDescriptors() {
|
||||
if (m_writes.empty()) return;
|
||||
std::vector<vk::DescriptorImageInfo> infos;
|
||||
std::vector<vk::WriteDescriptorSet> writes(m_writes.size());
|
||||
infos.reserve(m_writes.size());
|
||||
for (size_t write = 0; write < m_writes.size(); ++write) {
|
||||
const auto& binding = m_elements[m_writes[write]].binding;
|
||||
infos.push_back({nullptr, binding.image_view, binding.layout});
|
||||
writes[write].dstSet = m_set;
|
||||
writes[write].dstArrayElement = m_writes[write];
|
||||
writes[write].descriptorCount = 1;
|
||||
writes[write].descriptorType = vk::DescriptorType::eSampledImage;
|
||||
writes[write].pImageInfo = &infos.back();
|
||||
}
|
||||
m_context.GetGraphics().device.updateDescriptorSets(static_cast<uint32_t>(writes.size()),
|
||||
writes.data(), 0, nullptr);
|
||||
m_writes.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point,
|
||||
vk::PipelineLayout layout) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
if (view->dirty_begin != view->dirty_end) {
|
||||
const auto slots = std::span(view->slots).subspan(view->dirty_begin,
|
||||
view->dirty_end - view->dirty_begin);
|
||||
auto& stream = m_context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
|
||||
const auto offset = stream.Copy(slots.data(), slots.size_bytes(), sizeof(uint32_t));
|
||||
view->buffer->CopyFrom(buffer, stream, offset, view->dirty_begin * sizeof(uint32_t),
|
||||
slots.size_bytes());
|
||||
view->dirty_begin = view->dirty_end = 0;
|
||||
}
|
||||
Retire(*view);
|
||||
if (view->layout_epoch != epoch) {
|
||||
view->layout_epoch = epoch;
|
||||
view->away.clear();
|
||||
for (const auto& [value, index]: view->elements) view->away.push_back(index);
|
||||
}
|
||||
// This draw's own bindings own the layouts of its attachments and storage images. An image
|
||||
// this draw's discovery replaced lives until the tick ends, and its slots still select it.
|
||||
std::erase_if(view->away, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
auto* image = ImageOf(element);
|
||||
if (element.refs == 0u || image == nullptr) return true;
|
||||
if (image->binding.is_target || image->binding.shader_write ||
|
||||
image->binding.force_general) {
|
||||
return false;
|
||||
}
|
||||
const auto& view_info = element.binding.desc.view_info;
|
||||
image->Transit(image->ReadLayout(), vk::AccessFlagBits2::eShaderRead,
|
||||
ImageSubresourceRange {view_info.base_level, view_info.level_count,
|
||||
view_info.base_layer, view_info.layer_count},
|
||||
buffer.Handle());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
m_active.clear();
|
||||
buffer.Handle().bindDescriptorSets(bind_point, layout, IR::ImageHeapSet, 1, &m_set, 0,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
// Descriptors of textures in the range stay null even once it is mapped again.
|
||||
if (!m_unmapped.empty() && m_unmapped.back().End() == address) {
|
||||
m_unmapped.back().size += size;
|
||||
} else {
|
||||
m_unmapped.push_back({address, size});
|
||||
}
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||
return false;
|
||||
}
|
||||
Drop(*memory);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void ImageHeap::RunGarbageCollector() {
|
||||
// A smaller table rebuilt each frame moves to a new base. Larger ones keep their dead words.
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (memory->size >= MinTableBytes || memory->draw > m_suspend_draw) return false;
|
||||
Drop(*memory);
|
||||
return true;
|
||||
});
|
||||
m_suspend_draw = m_draw;
|
||||
m_sightings.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Drop(Memory& memory) {
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
Unwatch(memory);
|
||||
for (auto& view: memory.views) {
|
||||
std::erase(m_active, view.get());
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
Unreference(m_elements[index]);
|
||||
view->released.push_back(index);
|
||||
}
|
||||
Retire(*view);
|
||||
if (scheduler.Active()) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,150 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class Buffer;
|
||||
class CommandBuffer;
|
||||
class PageManager;
|
||||
class RenderContext;
|
||||
class RenderExecutor;
|
||||
struct ImageHeapTestAccess;
|
||||
|
||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
||||
// sampled-image array. A table is keyed by its base and follows its descriptor's size. Each view
|
||||
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||
// Image::heap_epoch. A smaller table leaves at a suspend point if the frame did not acquire it.
|
||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||
public:
|
||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||
~ImageHeap();
|
||||
KYTY_CLASS_NO_COPY(ImageHeap);
|
||||
|
||||
// Starts a draw or dispatch, before its programs acquire views.
|
||||
void BeginDraw() {
|
||||
++m_draw;
|
||||
Bury();
|
||||
}
|
||||
bool Acquire(const ShaderRecompiler::IR::ImageHeapRequest& request,
|
||||
ShaderRecompiler::IR::ImageHeapView& result) override;
|
||||
// Image discovery: resolves new elements and rediscovers changed images.
|
||||
void Rebind(RenderExecutor& executor);
|
||||
// Recording: uploads slots, moves resident images to their read layout and binds the array.
|
||||
void Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point, vk::PipelineLayout layout);
|
||||
// CPU write fault, on any thread.
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
// At a suspend point.
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||
|
||||
struct DescriptorHash {
|
||||
size_t operator()(const DescriptorValue& value) const noexcept;
|
||||
};
|
||||
|
||||
struct Element {
|
||||
DescriptorValue value;
|
||||
TextureBinding binding;
|
||||
uint32_t kind = 0;
|
||||
uint32_t refs = 0; // Slots of the owning view that select it.
|
||||
bool stale = false;
|
||||
uint64_t serial = 0; // Image::serial of binding.image_id.
|
||||
};
|
||||
|
||||
struct Memory;
|
||||
|
||||
struct View {
|
||||
Memory& memory;
|
||||
uint64_t step = 0;
|
||||
uint64_t first = 0;
|
||||
uint64_t count = 0;
|
||||
uint64_t limit = 0; // Probes a key domain allows, if fewer.
|
||||
ShaderRecompiler::IR::ImageResource profile;
|
||||
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
||||
std::vector<uint32_t> kind_keys;
|
||||
std::vector<DescriptorValue> kinds; // One representative per kind_keys entry.
|
||||
std::unordered_map<DescriptorValue, uint32_t, DescriptorHash> elements;
|
||||
std::vector<uint32_t> pending; // Not yet resolved.
|
||||
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> dcc; // Color metadata clears apply on each discovery.
|
||||
std::vector<uint32_t> held; // Another extent holds their address.
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
size_t dirty_begin = 0; // Slots to upload.
|
||||
size_t dirty_end = 0;
|
||||
size_t changed_begin = 0; // Table bytes changed since it was probed.
|
||||
size_t changed_end = 0;
|
||||
uint64_t resolve_epoch = 0;
|
||||
uint64_t layout_epoch = 0;
|
||||
uint64_t held_serial = 0; // Image::serials when held was last retried.
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
uint64_t base = 0;
|
||||
uint64_t size = 0;
|
||||
std::vector<uint32_t> words;
|
||||
std::vector<uint8_t> dirty_pages; // Unwatched, so unread since a write.
|
||||
std::vector<uint64_t> dead; // Per word, a buried descriptor's hash.
|
||||
uint64_t draw = 0; // Last draw that refreshed it.
|
||||
std::vector<std::unique_ptr<View>> views;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool Refresh(Memory& memory);
|
||||
void MarkDirty(Memory& memory, size_t begin, size_t end);
|
||||
template <bool track>
|
||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||
void Unwatch(const Memory& memory);
|
||||
void Drop(Memory& memory);
|
||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||
void UpdateProbe(View& view, uint64_t probe);
|
||||
void Assign(View& view, size_t slot, uint32_t entry);
|
||||
void Bury();
|
||||
uint32_t Intern(View& view, uint32_t kind, const DescriptorValue& value);
|
||||
void Release(View& view, uint32_t entry);
|
||||
void Unreference(Element& element);
|
||||
Image* ImageOf(const Element& element) const;
|
||||
uint32_t Allocate();
|
||||
void Retire(View& view);
|
||||
void Resize(Memory& memory, uint64_t size);
|
||||
void WriteDescriptors();
|
||||
|
||||
RenderContext& m_context;
|
||||
PageManager& m_page_manager;
|
||||
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||
std::vector<View*> m_active; // Acquired since the last commit.
|
||||
uint64_t m_draw = 1;
|
||||
uint64_t m_suspend_draw = 0; // m_draw at the last suspend point.
|
||||
std::vector<Element> m_elements;
|
||||
std::vector<uint32_t> m_free;
|
||||
std::unordered_map<uint64_t, uint64_t> m_sightings; // Smaller tables: base, first draw.
|
||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||
std::vector<uint32_t> m_scratch;
|
||||
std::vector<uint32_t> m_writes; // Elements whose descriptors changed.
|
||||
std::vector<GuestRange> m_unmapped; // Since the last Bury.
|
||||
vk::DescriptorSetLayout m_layout = nullptr;
|
||||
vk::DescriptorPool m_pool = nullptr;
|
||||
vk::DescriptorSet m_set = nullptr;
|
||||
|
||||
friend struct ImageHeapTestAccess;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,31 +4,48 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/file.h"
|
||||
#include "common/uniqueFunction.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct RenderColorInfo;
|
||||
struct RenderDepthInfo;
|
||||
struct ShaderParams;
|
||||
class CommandBuffer;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
class Shader;
|
||||
class UserConfig;
|
||||
struct ComputeShaderInfo;
|
||||
} // namespace HW
|
||||
namespace ShaderRecompiler {
|
||||
struct CompileOptions;
|
||||
struct ShaderSource;
|
||||
struct TranslateResult;
|
||||
} // namespace ShaderRecompiler
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
class ImageHeapSource;
|
||||
} // namespace ShaderRecompiler::IR
|
||||
|
||||
namespace ShaderRecompiler::Spirv {
|
||||
struct Profile;
|
||||
} // namespace ShaderRecompiler::Spirv
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
@@ -36,14 +53,13 @@ struct PipelineStaticParameters {
|
||||
bool negative_one_to_one = false;
|
||||
bool depth_clip_enable = true;
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
// 0 without tessellation, 3 for a rectangle list, else the input control points.
|
||||
uint32_t patch_control_points = 0;
|
||||
bool primitive_restart_enable = false;
|
||||
uint32_t samples = 1;
|
||||
bool sample_shading_enable = false;
|
||||
bool alpha_to_coverage_enable = false;
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool cull_front = false;
|
||||
bool cull_back = false;
|
||||
bool face = false;
|
||||
bool provoking_vtx_last = false;
|
||||
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
||||
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
@@ -54,57 +70,110 @@ struct PipelineStaticParameters {
|
||||
uint8_t alpha_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
|
||||
bool operator==(const PipelineStaticParameters& other) const noexcept;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<PipelineStaticParameters>);
|
||||
static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
|
||||
static_assert(alignof(PipelineStaticParameters) == 1);
|
||||
static_assert(sizeof(PipelineStaticParameters) == 117);
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
vk::Format depth_format = vk::Format::eUndefined;
|
||||
vk::Format stencil_format = vk::Format::eUndefined;
|
||||
uint32_t color_count = 0;
|
||||
|
||||
bool operator==(const PipelineRenderingState&) const = default;
|
||||
};
|
||||
|
||||
struct PipelineVertexInputState {
|
||||
struct Binding {
|
||||
uint32_t stride = 0;
|
||||
bool instance = false;
|
||||
bool operator==(const Binding&) const = default;
|
||||
uint32_t stride = 0;
|
||||
uint8_t instance = 0;
|
||||
};
|
||||
struct Attribute {
|
||||
uint32_t offset = 0;
|
||||
uint8_t binding = 0;
|
||||
bool operator==(const Attribute&) const = default;
|
||||
uint32_t offset = 0;
|
||||
uint8_t binding = 0;
|
||||
};
|
||||
|
||||
std::array<Binding, ShaderVertexInputInfo::RES_MAX> bindings {};
|
||||
std::array<Attribute, ShaderVertexInputInfo::RES_MAX> attributes {};
|
||||
uint8_t binding_count = 0;
|
||||
uint8_t attribute_count = 0;
|
||||
|
||||
bool operator==(const PipelineVertexInputState&) const = default;
|
||||
};
|
||||
|
||||
struct ShaderProgram {
|
||||
uint64_t id = 0;
|
||||
vk::ShaderModule module = nullptr;
|
||||
// Everything a graphics pipeline is made of besides the stage modules its ids name. Aligned so
|
||||
// that rendering.color_formats can be passed to Vulkan.
|
||||
struct alignas(4) GraphicsPipelineKey {
|
||||
// VS, LS or mesh; HS; TES; PS. 0 marks an absent stage.
|
||||
std::array<uint64_t, 4> stage_ids {};
|
||||
PipelineRenderingState rendering;
|
||||
PipelineVertexInputState vertex_input;
|
||||
PipelineStaticParameters static_params;
|
||||
|
||||
explicit operator bool() const { return id != 0 && module != nullptr; }
|
||||
bool operator==(const GraphicsPipelineKey& other) const;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
// Keys are compared and hashed as bytes.
|
||||
static_assert(std::has_unique_object_representations_v<GraphicsPipelineKey>);
|
||||
|
||||
// A compiled stage with what pipeline creation needs from it besides the key.
|
||||
struct ShaderModule {
|
||||
uint64_t id = 0;
|
||||
vk::ShaderModule module = nullptr;
|
||||
ShaderRecompiler::IR::CompiledShaderInfo info;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
std::vector<uint32_t> function_code;
|
||||
// VS or LS: the host format of each vertex input attribute.
|
||||
std::vector<vk::Format> vertex_formats;
|
||||
// Rectangle-list PS: the parameters it reads.
|
||||
std::vector<RectListParameter> rect_parameters;
|
||||
};
|
||||
|
||||
// A draw's topology, shader stages (from PipelineCache::GetGraphicsPrograms) and targets.
|
||||
struct DrawRenderState {
|
||||
RenderDepthInfo depth_info;
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
// Color targets the pixel program writes.
|
||||
uint32_t mrt_mask = 0;
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
bool primitive_restart_enable = false;
|
||||
bool ps_active = false;
|
||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
// Input: the user-data slots a GPU-read indirect draw patches.
|
||||
ShaderIndirectOffsets indirect_offsets;
|
||||
|
||||
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||
}
|
||||
};
|
||||
|
||||
// Writes the shader store on its own thread, running jobs in the order they were posted.
|
||||
class ShaderStoreWriter {
|
||||
public:
|
||||
// Keeps the first `kept` bytes of the store at `path`, or starts it over with `header`. The
|
||||
// thread runs only if that succeeded.
|
||||
void Open(const std::filesystem::path& path, std::span<const uint8_t> header, uint64_t kept);
|
||||
explicit operator bool() const { return m_thread.joinable(); }
|
||||
// Compresses `record` and appends it on the store thread, in call order.
|
||||
void Append(std::vector<uint8_t>&& record);
|
||||
|
||||
private:
|
||||
friend struct PipelineCacheTestAccess;
|
||||
|
||||
void Post(Common::UniqueFunction<void>&& job);
|
||||
|
||||
Common::File m_file;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable_any m_ready;
|
||||
std::queue<Common::UniqueFunction<void>> m_jobs;
|
||||
// Declared last, so it drains the queue and joins before the rest is destroyed.
|
||||
std::jthread m_thread;
|
||||
};
|
||||
|
||||
// The owning renderer serializes access, including saves while the GPU is running.
|
||||
class PipelineCache {
|
||||
public:
|
||||
explicit PipelineCache(GraphicContext& graphics);
|
||||
// Without a shader store path nothing is kept across runs.
|
||||
PipelineCache(GraphicContext& graphics, ShaderRecompiler::IR::ImageHeapSource* image_heap,
|
||||
const std::filesystem::path& shader_store = {});
|
||||
~PipelineCache();
|
||||
KYTY_CLASS_NO_COPY(PipelineCache);
|
||||
void Save();
|
||||
@@ -114,89 +183,109 @@ public:
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
|
||||
bool uses_push_descriptors = false;
|
||||
bool uses_image_heap = false;
|
||||
};
|
||||
|
||||
struct GraphicsPrograms {
|
||||
std::array<ShaderProgram, 3> vertex;
|
||||
ShaderProgram pixel;
|
||||
// Reads the shaders and registers bound in `command` into the state's shader stages.
|
||||
void GetGraphicsPrograms(const CommandBuffer& command, DrawRenderState& state);
|
||||
// Null skips the dispatch.
|
||||
const ShaderModule* GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
[[nodiscard]] uint32_t VertexStageCount() const { return vertex[1] ? 3u : 1u; }
|
||||
};
|
||||
|
||||
GraphicsPrograms
|
||||
GetGraphicsPrograms(const HW::VertexShaderInfo& vertex_regs,
|
||||
const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context, const HW::UserConfig& user_config,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
bool pixel_active, std::array<ShaderVertexInputInfo, 3>& vertex_info,
|
||||
ShaderPixelInputInfo& pixel_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
Pipeline& GetGraphicsPipeline(std::span<const RenderColorInfo> colors,
|
||||
const RenderDepthInfo& depth,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::PrimitiveTopology topology, bool primitive_restart_enable,
|
||||
const GraphicsPrograms& programs);
|
||||
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
Pipeline& GetGraphicsPipeline(const CommandBuffer& command, const DrawRenderState& state);
|
||||
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info);
|
||||
|
||||
private:
|
||||
struct ProgramCache;
|
||||
friend struct PipelineCacheTestAccess;
|
||||
|
||||
struct GraphicsPipelineKey {
|
||||
PipelineRenderingState rendering;
|
||||
std::array<uint64_t, 3> vertex_shader_ids {};
|
||||
uint64_t ps_shader_id = 0;
|
||||
PipelineVertexInputState vertex_input;
|
||||
PipelineStaticParameters static_params;
|
||||
struct Program {
|
||||
std::unique_ptr<ShaderRecompiler::ShaderSource> call_source;
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
std::vector<ShaderModule> modules;
|
||||
bool skip_dispatch = false;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
return rendering == other.rendering && vertex_shader_ids == other.vertex_shader_ids &&
|
||||
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
|
||||
static_params == other.static_params;
|
||||
}
|
||||
[[nodiscard]] std::span<const uint32_t> FunctionCode() const;
|
||||
bool RefreshCallSource(const ShaderRecompiler::IR::SrtRuntime& runtime);
|
||||
template <bool HasCalls>
|
||||
ShaderModule* FindModule(const ShaderRecompiler::IR::SrtRuntime& runtime,
|
||||
uint32_t push_data_cursor);
|
||||
};
|
||||
|
||||
struct PipelineKeyHash {
|
||||
static void Mix(std::size_t& hash, std::size_t value) {
|
||||
hash ^= value + static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (hash << 6u) +
|
||||
(hash >> 2u);
|
||||
}
|
||||
struct ProgramKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
uint32_t user_data_count = 0;
|
||||
std::vector<uint32_t> static_state;
|
||||
|
||||
bool operator==(const ProgramKey&) const = default;
|
||||
};
|
||||
|
||||
// Variants of one source share a bucket; ProgramKey equality compares the state.
|
||||
struct ProgramKeyHash {
|
||||
std::size_t operator()(const ProgramKey& key) const { return key.hash; }
|
||||
};
|
||||
|
||||
struct GraphicsPipelineKeyHash {
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
std::unique_ptr<ProgramCache> m_program_cache;
|
||||
using ModuleMap = std::unordered_map<uint64_t, const ShaderModule*>;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
ShaderRecompiler::IR::ImageHeapSource* m_image_heap;
|
||||
std::unordered_map<ProgramKey, Program, ProgramKeyHash> m_program_cache;
|
||||
// Modules compiled per stage.
|
||||
std::array<uint32_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> m_compiled {};
|
||||
ProgramKey m_lookup_key;
|
||||
vk::PipelineCache m_driver_cache = nullptr;
|
||||
std::filesystem::path m_driver_cache_path;
|
||||
std::chrono::steady_clock::time_point m_next_driver_cache_save;
|
||||
bool m_driver_cache_dirty = false;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
|
||||
std::unordered_map<GraphicsPipelineKey, Pipeline, GraphicsPipelineKeyHash> m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, Pipeline> m_compute_pipelines;
|
||||
std::unordered_map<uint64_t, ShaderTessellationInputInfo> m_tess_layouts;
|
||||
// Records everything first created; closed without a shader store.
|
||||
ShaderStoreWriter m_store;
|
||||
|
||||
template <typename InputInfo>
|
||||
const ShaderModule* GetProgram(const ShaderParams& params, InputInfo& input_info,
|
||||
uint32_t& push_data_cursor);
|
||||
ShaderModule& CompileModule(Program& program, const ProgramKey& key,
|
||||
ShaderRecompiler::CompileOptions options,
|
||||
ShaderRecompiler::TranslateResult translated,
|
||||
uint32_t push_data_cursor);
|
||||
void InitializeDriverCache();
|
||||
void CheckpointDriverCache();
|
||||
|
||||
// pipelineSerialization.cpp
|
||||
void WarmUp(const std::filesystem::path& path);
|
||||
bool LoadRecord(std::span<const uint8_t>& records, std::vector<GraphicsPipelineKey>& pipelines);
|
||||
void StoreModule(const ProgramKey& key, const Program& program, const ShaderModule& module,
|
||||
const std::vector<uint32_t>& spirv);
|
||||
void StorePipeline(const GraphicsPipelineKey& key);
|
||||
void StoreLayout(uint64_t key, const ShaderTessellationInputInfo& layout);
|
||||
// Every module by id, with id 0 naming an absent stage.
|
||||
[[nodiscard]] ModuleMap MapModules() const;
|
||||
// False when a stage id of the key is not in `modules`.
|
||||
bool CreateGraphicsPipeline(Pipeline& pipeline, const GraphicsPipelineKey& key,
|
||||
const ModuleMap& modules);
|
||||
};
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
// The shader store of the running title; empty unless --shader-cache is on in a Release build
|
||||
// without debug dumps, which change the SPIR-V.
|
||||
std::filesystem::path ShaderStorePath();
|
||||
// Host capabilities that change the emitted SPIR-V.
|
||||
ShaderRecompiler::Spirv::Profile SpirvProfile(const GraphicContext& graphics);
|
||||
|
||||
// Stages are indexed like GraphicsPipelineKey::stage_ids.
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const PipelineVertexInputState& vertex_input,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
const PipelineCache::GraphicsPrograms& programs,
|
||||
const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache);
|
||||
const GraphicsPipelineKey& key,
|
||||
const std::array<const ShaderModule*, 4>& stages,
|
||||
vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
const ShaderModule& module, vk::PipelineCache driver_cache);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/ir/Serialize.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <ranges>
|
||||
#include <xxhash.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
// The shader store: a header, then zstd-compressed records of everything a run created first.
|
||||
// Records are only appended, so a store cut short by a crash keeps every complete record.
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
using ShaderRecompiler::IR::IoAll;
|
||||
using ShaderRecompiler::IR::Reader;
|
||||
using ShaderRecompiler::IR::Writer;
|
||||
|
||||
enum class Record : uint8_t { Module, GraphicsPipeline, TessellationLayout };
|
||||
|
||||
// Precedes each record's zstd frame.
|
||||
struct Frame {
|
||||
uint32_t compressed_size = 0;
|
||||
uint32_t raw_size = 0;
|
||||
uint64_t checksum = 0; // XXH3 of the compressed bytes.
|
||||
};
|
||||
|
||||
// Changes with every build, so records never meet code that encodes them differently. Zero when
|
||||
// the executable cannot be found.
|
||||
uint64_t BuildStamp() {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
std::array<wchar_t, 32768> name {};
|
||||
GetModuleFileNameW(nullptr, name.data(), static_cast<DWORD>(name.size()));
|
||||
const std::filesystem::path path = name.data();
|
||||
#elif defined(__APPLE__)
|
||||
std::array<char, 4096> name {};
|
||||
auto size = static_cast<uint32_t>(name.size());
|
||||
_NSGetExecutablePath(name.data(), &size);
|
||||
const std::filesystem::path path = name.data();
|
||||
#else
|
||||
const std::filesystem::path path = "/proc/self/exe";
|
||||
#endif
|
||||
std::error_code error;
|
||||
const auto time = std::filesystem::last_write_time(path, error).time_since_epoch().count();
|
||||
return error ? 0 : static_cast<uint64_t>(time);
|
||||
}
|
||||
|
||||
// What every record depends on besides its own fields; empty when the build is unknown.
|
||||
std::vector<uint8_t> StoreHeader(const GraphicContext& graphics) {
|
||||
const auto stamp = BuildStamp();
|
||||
if (stamp == 0) {
|
||||
return {};
|
||||
}
|
||||
Writer header;
|
||||
IoAll(header, stamp, SpirvProfile(graphics),
|
||||
graphics.GetPhysicalDeviceProperties().limits.maxComputeSharedMemorySize);
|
||||
return std::move(header.bytes);
|
||||
}
|
||||
|
||||
// Record fields, shared by writing and reading.
|
||||
template <class S, class Key> void IoKey(S& io, Key& key) {
|
||||
auto& [stage, hash, user_data_count, static_state] = key;
|
||||
IoAll(io, stage, hash, user_data_count, static_state);
|
||||
}
|
||||
|
||||
template <class S, class Module> void IoModule(S& io, Module& stage) {
|
||||
// The handle comes from the SPIR-V; stored programs have no function code.
|
||||
auto& [id, handle, info, specialization, function_code, vertex_formats, rect_parameters] = stage;
|
||||
IoAll(io, id, info, specialization, vertex_formats, rect_parameters);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShaderStoreWriter::Open(const std::filesystem::path& path, std::span<const uint8_t> header,
|
||||
uint64_t kept) {
|
||||
Common::File::CreateDirectories(path.parent_path());
|
||||
if (kept == 0) {
|
||||
if (m_file.Create(path)) {
|
||||
m_file.Write(header.data(), static_cast<uint32_t>(header.size()));
|
||||
}
|
||||
} else if (m_file.Open(path, Common::File::Mode::ReadWrite)) {
|
||||
m_file.Truncate(kept);
|
||||
m_file.Seek(kept);
|
||||
}
|
||||
if (m_file.IsInvalid()) {
|
||||
return;
|
||||
}
|
||||
m_thread = std::jthread([this](std::stop_token stop) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
// Once stop is requested the wait returns false only when the queue is empty.
|
||||
while (m_ready.wait(lock, stop, [this] { return !m_jobs.empty(); })) {
|
||||
const auto job = std::move(m_jobs.front());
|
||||
m_jobs.pop();
|
||||
lock.unlock();
|
||||
job();
|
||||
lock.lock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ShaderStoreWriter::Append(std::vector<uint8_t>&& record) {
|
||||
Post([this, record = std::move(record)] {
|
||||
std::vector<uint8_t> bytes(sizeof(Frame) + ZSTD_compressBound(record.size()));
|
||||
const auto size = ZSTD_compress(bytes.data() + sizeof(Frame), bytes.size() - sizeof(Frame),
|
||||
record.data(), record.size(), 3);
|
||||
EXIT_IF(ZSTD_isError(size));
|
||||
const Frame frame {.compressed_size = static_cast<uint32_t>(size),
|
||||
.raw_size = static_cast<uint32_t>(record.size()),
|
||||
.checksum = XXH3_64bits(bytes.data() + sizeof(Frame), size)};
|
||||
std::memcpy(bytes.data(), &frame, sizeof(frame));
|
||||
// A failed write leaves a partial record; the next run drops it and everything after it.
|
||||
m_file.Write(bytes.data(), static_cast<uint32_t>(sizeof(Frame) + size));
|
||||
});
|
||||
}
|
||||
|
||||
void ShaderStoreWriter::Post(Common::UniqueFunction<void>&& job) {
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_jobs.push(std::move(job));
|
||||
}
|
||||
m_ready.notify_one();
|
||||
}
|
||||
|
||||
void PipelineCache::WarmUp(const std::filesystem::path& path) {
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto header = StoreHeader(m_graphics);
|
||||
if (header.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
std::vector<uint8_t> bytes;
|
||||
if (Common::File file;
|
||||
Common::File::IsFileExisting(path) && file.Open(path, Common::File::Mode::Read)) {
|
||||
bytes.resize(file.Size());
|
||||
uint32_t read = 0;
|
||||
file.Read(bytes.data(), static_cast<uint32_t>(bytes.size()), &read);
|
||||
bytes.resize(read);
|
||||
}
|
||||
Log::WriteToConsoleAndLog(
|
||||
fmt::format("Shader cache: compiling shaders and pipelines from {} ({:.1f} MB)\n",
|
||||
Common::PathToString(path), static_cast<double>(bytes.size()) / 1'000'000.0));
|
||||
std::vector<GraphicsPipelineKey> pipelines;
|
||||
auto records = std::span<const uint8_t>(bytes);
|
||||
const bool reusable =
|
||||
records.size() >= header.size() && std::ranges::equal(records.first(header.size()), header);
|
||||
if (reusable) {
|
||||
records = records.subspan(header.size());
|
||||
while (LoadRecord(records, pipelines)) {
|
||||
}
|
||||
}
|
||||
// A damaged record ends the store; the records before it stay.
|
||||
m_store.Open(path, header, reusable ? bytes.size() - records.size() : 0);
|
||||
|
||||
const auto modules = MapModules();
|
||||
for (const auto& key: pipelines) {
|
||||
Pipeline pipeline;
|
||||
if (CreateGraphicsPipeline(pipeline, key, modules)) {
|
||||
m_graphics_pipelines.emplace(key, pipeline);
|
||||
}
|
||||
}
|
||||
// A compute pipeline depends only on its module.
|
||||
for (const auto* module: modules | std::views::values) {
|
||||
if (module != nullptr && module->info.stage == ShaderType::Compute) {
|
||||
CreatePipelineInternal(m_graphics, m_compute_pipelines[module->id], *module,
|
||||
m_driver_cache);
|
||||
}
|
||||
}
|
||||
if (!m_graphics_pipelines.empty() || !m_compute_pipelines.empty()) {
|
||||
CheckpointDriverCache();
|
||||
}
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"Shader cache: loaded {} programs, {} modules, {} pipelines in {} ms\n",
|
||||
m_program_cache.size(), modules.size() - 1,
|
||||
m_graphics_pipelines.size() + m_compute_pipelines.size(),
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
|
||||
start)
|
||||
.count()));
|
||||
}
|
||||
|
||||
bool PipelineCache::LoadRecord(std::span<const uint8_t>& records,
|
||||
std::vector<GraphicsPipelineKey>& pipelines) {
|
||||
Frame frame;
|
||||
if (records.size() < sizeof(frame)) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(&frame, records.data(), sizeof(frame));
|
||||
if (records.size() - sizeof(frame) < frame.compressed_size) {
|
||||
return false;
|
||||
}
|
||||
const auto compressed = records.subspan(sizeof(frame), frame.compressed_size);
|
||||
if (XXH3_64bits(compressed.data(), compressed.size()) != frame.checksum ||
|
||||
ZSTD_getFrameContentSize(compressed.data(), compressed.size()) != frame.raw_size) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> raw(frame.raw_size);
|
||||
if (ZSTD_decompress(raw.data(), raw.size(), compressed.data(), compressed.size()) != raw.size()) {
|
||||
return false;
|
||||
}
|
||||
Reader reader {raw};
|
||||
auto type = Record::Module;
|
||||
const auto complete = [&] { return reader.ok && reader.bytes.empty(); };
|
||||
IoAll(reader, type);
|
||||
switch (type) {
|
||||
case Record::Module: {
|
||||
ProgramKey key;
|
||||
std::vector<uint8_t> plan;
|
||||
ShaderModule module;
|
||||
std::vector<uint32_t> spirv;
|
||||
IoKey(reader, key);
|
||||
IoAll(reader, plan);
|
||||
IoModule(reader, module);
|
||||
IoAll(reader, spirv);
|
||||
if (!complete()) {
|
||||
return false;
|
||||
}
|
||||
const auto [program, inserted] = m_program_cache.try_emplace(std::move(key));
|
||||
if (inserted &&
|
||||
!ShaderRecompiler::IR::LoadResourcePlan(plan, program->second.resource_plan)) {
|
||||
m_program_cache.erase(program);
|
||||
return false;
|
||||
}
|
||||
module.module = CompileSPV(spirv, m_graphics.device);
|
||||
if (module.module == nullptr) {
|
||||
return false;
|
||||
}
|
||||
program->second.modules.push_back(std::move(module));
|
||||
break;
|
||||
}
|
||||
case Record::GraphicsPipeline: {
|
||||
GraphicsPipelineKey key;
|
||||
reader.Raw(&key, sizeof(key));
|
||||
if (!complete()) {
|
||||
return false;
|
||||
}
|
||||
pipelines.push_back(key);
|
||||
break;
|
||||
}
|
||||
case Record::TessellationLayout: {
|
||||
uint64_t key = 0;
|
||||
ShaderTessellationInputInfo layout;
|
||||
IoAll(reader, key, layout);
|
||||
if (!complete()) {
|
||||
return false;
|
||||
}
|
||||
m_tess_layouts.try_emplace(key, layout);
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
records = records.subspan(sizeof(frame) + compressed.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Records are encoded here, since programs and modules change later.
|
||||
void PipelineCache::StoreModule(const ProgramKey& key, const Program& program,
|
||||
const ShaderModule& module, const std::vector<uint32_t>& spirv) {
|
||||
if (!m_store) {
|
||||
return;
|
||||
}
|
||||
Writer record;
|
||||
IoAll(record, Record::Module);
|
||||
IoKey(record, key);
|
||||
IoAll(record, ShaderRecompiler::IR::SaveResourcePlan(program.resource_plan));
|
||||
IoModule(record, module);
|
||||
IoAll(record, spirv);
|
||||
m_store.Append(std::move(record.bytes));
|
||||
}
|
||||
|
||||
void PipelineCache::StorePipeline(const GraphicsPipelineKey& key) {
|
||||
if (!m_store) {
|
||||
return;
|
||||
}
|
||||
Writer record;
|
||||
IoAll(record, Record::GraphicsPipeline);
|
||||
record.Raw(&key, sizeof(key));
|
||||
m_store.Append(std::move(record.bytes));
|
||||
}
|
||||
|
||||
void PipelineCache::StoreLayout(uint64_t key, const ShaderTessellationInputInfo& layout) {
|
||||
if (!m_store) {
|
||||
return;
|
||||
}
|
||||
Writer record;
|
||||
IoAll(record, Record::TessellationLayout, key, layout);
|
||||
m_store.Append(std::move(record.bytes));
|
||||
}
|
||||
|
||||
PipelineCache::ModuleMap PipelineCache::MapModules() const {
|
||||
ModuleMap modules {{0, nullptr}};
|
||||
for (const auto& program: m_program_cache | std::views::values) {
|
||||
for (const auto& module: program.modules) {
|
||||
modules.emplace(module.id, &module);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
bool PipelineCache::CreateGraphicsPipeline(Pipeline& pipeline, const GraphicsPipelineKey& key,
|
||||
const ModuleMap& modules) {
|
||||
std::array<const ShaderModule*, 4> stages {};
|
||||
for (size_t i = 0; i < stages.size(); i++) {
|
||||
const auto found = modules.find(key.stage_ids[i]);
|
||||
if (found == modules.end()) {
|
||||
return false;
|
||||
}
|
||||
stages[i] = found->second;
|
||||
}
|
||||
CreatePipelineInternal(m_graphics, pipeline, key, stages, m_driver_cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
@@ -52,6 +53,7 @@ vk::MemoryBarrier MakeShaderWriteDependency() {
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
|
||||
vk::AccessFlagBits::eVertexAttributeRead |
|
||||
vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead |
|
||||
vk::AccessFlagBits::eIndirectCommandRead |
|
||||
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
@@ -89,7 +91,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& program = runtime.module->info;
|
||||
const auto& resources = *runtime.resources;
|
||||
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
|
||||
// Address writes reach memory outside the bound buffer descriptors.
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
@@ -21,124 +20,11 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
// IDK: maybe we can remove it?
|
||||
constexpr uint8_t kTemporaryVertexAttribFormat113 =
|
||||
static_cast<uint8_t>(Prospero::VertexAttribFormat::k16_16SInt);
|
||||
constexpr uint32_t kTemporaryPs5BufferFormat121 = 121u;
|
||||
|
||||
static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_components) {
|
||||
if (used_components == 0 || used_components >= size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case vk::Format::eR32G32B32A32Sfloat:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR32Sfloat; break;
|
||||
case 2: format = vk::Format::eR32G32Sfloat; break;
|
||||
case 3: format = vk::Format::eR32G32B32Sfloat; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR32G32B32Sfloat:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR32Sfloat; break;
|
||||
case 2: format = vk::Format::eR32G32Sfloat; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR16G16B16A16Sfloat:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR16Sfloat; break;
|
||||
case 2: format = vk::Format::eR16G16Sfloat; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR8Unorm; break;
|
||||
case 2: format = vk::Format::eR8G8Unorm; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR8G8B8A8Snorm:
|
||||
if (used_components != 2) {
|
||||
return false;
|
||||
}
|
||||
format = vk::Format::eR8G8Snorm;
|
||||
size = 2;
|
||||
return true;
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR8Uint; break;
|
||||
case 2: format = vk::Format::eR8G8Uint; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format, uint32_t& size,
|
||||
uint32_t used_components) {
|
||||
const auto fmt = res.Format();
|
||||
const auto raw_format = res.RawFormat();
|
||||
if (raw_format == kTemporaryVertexAttribFormat113) {
|
||||
static bool logged_113 = false;
|
||||
if (!logged_113) {
|
||||
LOGF("InputFormat: temporary: accepting invalid PS5 buffer format 113 as "
|
||||
"vk::Format::eR32G32B32A32Sfloat\n");
|
||||
logged_113 = true;
|
||||
}
|
||||
format = vk::Format::eR32G32B32A32Sfloat;
|
||||
size = 4;
|
||||
if (NarrowInputFormat(format, size, used_components)) {
|
||||
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
|
||||
vk::to_string(format).c_str(), used_components);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (raw_format == kTemporaryPs5BufferFormat121) {
|
||||
static bool logged_121 = false;
|
||||
if (!logged_121) {
|
||||
LOGF("InputFormat: accepting PS5 buffer format 121 as vk::Format::eR16G16Sfloat\n");
|
||||
logged_121 = true;
|
||||
}
|
||||
format = vk::Format::eR16G16Sfloat;
|
||||
size = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
format = VulkanFormat(fmt);
|
||||
size = ShaderRecompiler::Format::GetFormatInfo(fmt).component_count;
|
||||
if (format == vk::Format::eUndefined || size == 0) {
|
||||
EXIT("unknown vertex format: fmt = %u\n", raw_format);
|
||||
}
|
||||
|
||||
if (NarrowInputFormat(format, size, used_components)) {
|
||||
static std::atomic<uint64_t> log_count = 0;
|
||||
auto log_id = log_count.fetch_add(1);
|
||||
if (log_id < 32) {
|
||||
LOGF("VertexInput: narrowed vertex format to %" PRIu32
|
||||
" component(s) for shader fetch\n",
|
||||
used_components);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static vk::BlendFactor GetBlendFactor(uint32_t factor) {
|
||||
switch (static_cast<Prospero::BlendFactor>(factor)) {
|
||||
case Prospero::BlendFactor::kZero: return vk::BlendFactor::eZero;
|
||||
@@ -242,33 +128,27 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const PipelineVertexInputState& vertex_input,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
const PipelineCache::GraphicsPrograms& programs,
|
||||
const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const auto& vs_input_info = vertex_info.front();
|
||||
const auto& vertex_program = programs.vertex[0];
|
||||
const auto& pixel_program = programs.pixel;
|
||||
const bool tessellation = vertex_info.size() == 3;
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(!vertex_program || (ps_active && !pixel_program));
|
||||
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
|
||||
rendering.stencil_format != vk::Format::eUndefined;
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh;
|
||||
const GraphicsPipelineKey& key,
|
||||
const std::array<const ShaderModule*, 4>& stages,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const auto& rendering = key.rendering;
|
||||
const auto& vertex_input = key.vertex_input;
|
||||
const auto& static_params = key.static_params;
|
||||
const auto& vertex = *stages[0];
|
||||
const auto* pixel = stages[3];
|
||||
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
|
||||
rendering.stencil_format != vk::Format::eUndefined;
|
||||
const bool mesh = vertex.info.stage == ShaderType::Mesh;
|
||||
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
|
||||
const bool rect_list =
|
||||
!mesh && !tessellation && static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
const bool rect_list = static_params.patch_control_points != 0 && stages[1] == nullptr;
|
||||
|
||||
vk::ShaderModule tess_control_shader_module = nullptr;
|
||||
vk::ShaderModule tess_eval_shader_module = nullptr;
|
||||
|
||||
if (rect_list) {
|
||||
const auto shaders =
|
||||
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
|
||||
std::span<const RectListParameter> parameters;
|
||||
if (pixel != nullptr) parameters = pixel->rect_parameters;
|
||||
const auto shaders = BuildRectListShaders(parameters, vertex.info.param_export_mask);
|
||||
tess_control_shader_module = CompileSPV(shaders.control, graphics.device);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done module=%p\n",
|
||||
@@ -287,11 +167,12 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[4] {};
|
||||
uint32_t shader_stage_count = 0;
|
||||
for (uint32_t i = 0; i < vertex_info.size(); i++) {
|
||||
shader_stages[shader_stage_count++] = {.stage =
|
||||
NativeShaderStage(vertex_info[i].logical_stage),
|
||||
.module = programs.vertex[i].module,
|
||||
.pName = "main"};
|
||||
for (const auto* stage: stages) {
|
||||
if (stage != nullptr) {
|
||||
shader_stages[shader_stage_count++] = {.stage = NativeShaderStage(stage->info.stage),
|
||||
.module = stage->module,
|
||||
.pName = "main"};
|
||||
}
|
||||
}
|
||||
if (rect_list) {
|
||||
shader_stages[shader_stage_count++] = {.stage =
|
||||
@@ -303,11 +184,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
.module = tess_eval_shader_module,
|
||||
.pName = "main"};
|
||||
}
|
||||
if (ps_active) {
|
||||
shader_stages[shader_stage_count++] = {.stage = vk::ShaderStageFlagBits::eFragment,
|
||||
.module = pixel_program.module,
|
||||
.pName = "main"};
|
||||
}
|
||||
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX] {};
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX] {};
|
||||
@@ -323,46 +199,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
input_attr[index].binding = vertex_input.attributes[index].binding;
|
||||
input_attr[index].location = index;
|
||||
input_attr[index].offset = vertex_input.attributes[index].offset;
|
||||
|
||||
uint32_t attr_size = 4;
|
||||
const auto registers_num = vs_input_info.resources_dst[index].registers_num;
|
||||
const auto compiled_components =
|
||||
vs_input_info.stage.program->info.vertex_fetch_components[index];
|
||||
const auto used_components =
|
||||
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
|
||||
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
|
||||
static_cast<uint32_t>(used_components));
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (log_id < 128) {
|
||||
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
|
||||
"src_fmt=%u dst=v%u regs=%u"
|
||||
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
|
||||
log_id, index, input_attr[index].binding, input_attr[index].offset,
|
||||
input_desc[input_attr[index].binding].stride,
|
||||
static_cast<int>(input_attr[index].format),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
|
||||
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
|
||||
static_cast<uint32_t>(registers_num), static_cast<uint32_t>(used_components),
|
||||
attr_size, static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
|
||||
}
|
||||
}
|
||||
|
||||
if (vs_input_info.resources[index].OutOfBounds() != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8 "\n",
|
||||
vs_input_info.resources[index].OutOfBounds());
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_IF(registers_num < 1 || registers_num > 4);
|
||||
input_attr[index].format = vertex.vertex_formats[index];
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_info {};
|
||||
@@ -382,17 +219,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
vk::PipelineViewportStateCreateInfo viewport_state {};
|
||||
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 {};
|
||||
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
@@ -413,8 +239,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
provoking_vertex.pNext = rasterizer.pNext;
|
||||
rasterizer.pNext = &provoking_vertex;
|
||||
}
|
||||
rasterizer.cullMode = cull_mode;
|
||||
rasterizer.frontFace = front_face;
|
||||
rasterizer.polygonMode = static_params.polygon_mode;
|
||||
rasterizer.lineWidth = 1.0f;
|
||||
|
||||
@@ -470,23 +294,22 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
vk::ShaderStageFlags graphics_stages = vk::ShaderStageFlagBits::eFragment;
|
||||
for (const auto& stage: vertex_info) {
|
||||
const auto native_stage = NativeShaderStage(stage.logical_stage);
|
||||
AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage);
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
for (const auto* stage: stages) {
|
||||
if (stage != nullptr) {
|
||||
const auto native_stage = NativeShaderStage(stage->info.stage);
|
||||
AddLayoutBindings(descriptor_bindings, stage->info, native_stage);
|
||||
pipeline.uses_image_heap |= stage->info.info.uses_image_heap;
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
}
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {graphics_stages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const std::array set_layouts {pipeline.descriptor_set_layout, graphics.image_heap_layout};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.setLayoutCount = pipeline.uses_image_heap ? 2u : 1u;
|
||||
pipeline_layout_info.pSetLayouts = set_layouts.data();
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
@@ -495,7 +318,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" set_layouts=1 push_constants=%" PRIu32 "\n",
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
|
||||
key.stage_ids[0], key.stage_ids[3], 1u);
|
||||
}
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
@@ -505,14 +328,14 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);
|
||||
|
||||
vk::PipelineDepthStencilStateCreateInfo depth_stencil_info {};
|
||||
|
||||
std::vector<vk::DynamicState> dynamic_states {
|
||||
vk::DynamicState::eViewportWithCount,
|
||||
vk::DynamicState::eScissorWithCount,
|
||||
vk::DynamicState::eLineWidth,
|
||||
vk::DynamicState::eCullMode,
|
||||
vk::DynamicState::eFrontFace,
|
||||
vk::DynamicState::eDepthTestEnable,
|
||||
vk::DynamicState::eDepthWriteEnable,
|
||||
vk::DynamicState::eDepthCompareOp,
|
||||
@@ -552,9 +375,9 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
|
||||
vk::PipelineTessellationStateCreateInfo tessellation_state {};
|
||||
tessellation_state.patchControlPoints =
|
||||
tessellation ? vs_input_info.tess.input_control_points : 3u;
|
||||
pipeline_info.pTessellationState = (rect_list || tessellation) ? &tessellation_state : nullptr;
|
||||
tessellation_state.patchControlPoints = static_params.patch_control_points;
|
||||
pipeline_info.pTessellationState =
|
||||
static_params.patch_control_points != 0 ? &tessellation_state : nullptr;
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
@@ -570,7 +393,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
|
||||
" depth=%s blend=%s dyn_states=%" PRIu32 "\n",
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0,
|
||||
key.stage_ids[0], key.stage_ids[3],
|
||||
static_cast<uint32_t>(static_params.topology), static_params.color_mask[0],
|
||||
(with_depth ? "true" : "false"), (static_params.blend_enable[0] ? "true" : "false"),
|
||||
dynamic_state.dynamicStateCount);
|
||||
@@ -583,29 +406,18 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
|
||||
if (tess_control_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
|
||||
}
|
||||
if (tess_eval_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
|
||||
}
|
||||
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
|
||||
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
|
||||
EXIT_IF(compute_module == nullptr);
|
||||
|
||||
const ShaderModule& module, vk::PipelineCache driver_cache) {
|
||||
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
|
||||
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
comp_shader_stage_info.module = compute_module;
|
||||
comp_shader_stage_info.module = module.module;
|
||||
comp_shader_stage_info.pName = "main";
|
||||
EXIT_IF(!input_info.stage);
|
||||
const auto wave_size = input_info.stage.program->wave_size;
|
||||
const auto wave_size = module.info.wave_size;
|
||||
if (graphics.compute_subgroup_size_control_enabled &&
|
||||
wave_size >= graphics.min_subgroup_size && wave_size <= graphics.max_subgroup_size) {
|
||||
comp_subgroup_size.requiredSubgroupSize = wave_size;
|
||||
@@ -613,32 +425,27 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
}
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
AddLayoutBindings(descriptor_bindings, module.info, vk::ShaderStageFlagBits::eCompute);
|
||||
pipeline.uses_image_heap = module.info.info.uses_image_heap;
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const std::array set_layouts {pipeline.descriptor_set_layout, graphics.image_heap_layout};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.setLayoutCount = pipeline.uses_image_heap ? 2u : 1u;
|
||||
pipeline_layout_info.pSetLayouts = set_layouts.data();
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
EXIT_IF(pipeline.pipeline_layout != nullptr);
|
||||
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
|
||||
1u);
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);
|
||||
|
||||
vk::ComputePipelineCreateInfo info {};
|
||||
if (input_info.stage.program->info.uses_gds) {
|
||||
if (module.info.info.uses_gds) {
|
||||
info.flags = vk::PipelineCreateFlagBits::eDispatchBase;
|
||||
}
|
||||
info.stage = comp_shader_stage_info;
|
||||
@@ -647,15 +454,9 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
EXIT_IF(pipeline.pipeline != nullptr);
|
||||
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines begin layout=%p\n",
|
||||
static_cast<void*>(pipeline.pipeline_layout));
|
||||
result = graphics.device.createComputePipelines(driver_cache, 1, &info, nullptr,
|
||||
&pipeline.pipeline);
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -23,14 +23,9 @@ class Shader;
|
||||
} // namespace HW
|
||||
|
||||
struct GraphicContext;
|
||||
struct ShaderBufferResource;
|
||||
struct ShaderComputeInputInfo;
|
||||
struct RenderDepthInfo;
|
||||
struct RenderColorInfo;
|
||||
struct DrawCallInfo;
|
||||
struct DrawEmitInfo;
|
||||
struct DrawIndexBufferSource;
|
||||
struct DrawRenderState;
|
||||
class RenderContext;
|
||||
class CommandScheduler;
|
||||
struct RenderExecutorTestAccess;
|
||||
@@ -48,6 +43,14 @@ enum class DrawOffsetSource : uint8_t {
|
||||
IndirectArgs,
|
||||
};
|
||||
|
||||
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
||||
struct DrawIndirectArgs {
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
ShaderIndirectOffsets offsets;
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
uint32_t index_count = 0;
|
||||
const void* index_addr = nullptr;
|
||||
@@ -57,6 +60,8 @@ struct DrawIndexArgs {
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
// With indirect records, index_count is INDEX_BUFFER_SIZE.
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct DrawAutoArgs {
|
||||
@@ -66,6 +71,7 @@ struct DrawAutoArgs {
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
@@ -161,18 +167,19 @@ public:
|
||||
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
|
||||
const PipelineCache::Pipeline& pipeline,
|
||||
std::span<PreparedBindings* const> bindings);
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
|
||||
private:
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
// False, with nothing drawn, when the draw's indirect records need CPU counts.
|
||||
bool DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
bool DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
|
||||
struct GraphicsBindings {
|
||||
std::array<PreparedBindings, 3> vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
};
|
||||
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors);
|
||||
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
|
||||
@@ -187,9 +194,8 @@ private:
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state);
|
||||
void ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
DrawRenderState& state, vk::PrimitiveTopology topology,
|
||||
const DrawEmitInfo& emit, const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable);
|
||||
DrawRenderState& state, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source);
|
||||
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth,
|
||||
vk::ImageAspectFlags& feedback_aspects,
|
||||
|
||||
@@ -53,7 +53,7 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
|
||||
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& program = input.stage.module->info;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
EXIT("compute runtime buffer count does not match shader metadata\n");
|
||||
@@ -135,7 +135,7 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& program = input.stage.module->info;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
@@ -208,7 +208,7 @@ bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: compute %s shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
image_clear ? "image clear" : "buffer fill", input.stage.program->shader_hash,
|
||||
image_clear ? "image clear" : "buffer fill", input.stage.module->info.shader_hash,
|
||||
descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
return true;
|
||||
@@ -216,7 +216,7 @@ bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
|
||||
static void BindSharedMemory(RenderContext& context, ShaderComputeInputInfo& input,
|
||||
PreparedBindings& bindings, uint64_t indirect_args = 0) {
|
||||
if (input.stage.program->bindings.descriptor_counts[
|
||||
if (input.stage.module->info.bindings.descriptor_counts[
|
||||
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::SharedMemory)] == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +248,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t thread_group_z, uint32_t mode, bool async_compute) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DispatchDirect), submit_id,
|
||||
@@ -293,7 +292,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
const auto& cs_regs = sh_ctx.GetCs();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
@@ -311,16 +309,15 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
input_info.workgroup_counts[axis] = threads / size + (threads % size != 0u);
|
||||
}
|
||||
}
|
||||
const auto compute_program =
|
||||
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
|
||||
if (!compute_program) return;
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
if (!m_context.GetPipelineCache().GetComputeProgram(cs_regs, input_info)) return;
|
||||
if (use_thread_dimensions) {
|
||||
input_info.dispatch_threads_num[0] = thread_group_x;
|
||||
input_info.dispatch_threads_num[1] = thread_group_y;
|
||||
input_info.dispatch_threads_num[2] = thread_group_z;
|
||||
}
|
||||
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& program = input_info.stage.module->info;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
if (resources.specialization_reads.empty() &&
|
||||
(TryConsumeComputeMetaClear(input_info, buffer) ||
|
||||
@@ -350,8 +347,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
buffer.EndRendering();
|
||||
auto& pipeline =
|
||||
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info);
|
||||
auto& bindings = m_compute_bindings;
|
||||
PrepareBindings(input_info.stage, bindings);
|
||||
if (program.bindings.dispatch_thread_dword != ShaderRecompiler::IR::PushData::NoStart) {
|
||||
@@ -363,6 +359,9 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
if (program.info.uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
RebindImages(bindings);
|
||||
BindSharedMemory(m_context, input_info, bindings);
|
||||
RebindBuffers(bindings);
|
||||
@@ -497,20 +496,22 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
input_info.indirect_args_address = args_addr;
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
if (!compute_program) return;
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
if (!m_context.GetPipelineCache().GetComputeProgram(cs_regs, input_info)) return;
|
||||
buffer.EndRendering();
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info);
|
||||
auto& bindings = m_compute_bindings;
|
||||
PrepareBindings(input_info.stage, bindings);
|
||||
PreparedBindings* descriptor_stage = &bindings;
|
||||
FindBuffers(std::span {&descriptor_stage, 1u});
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& program = input_info.stage.module->info;
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
BindSharedMemory(m_context, input_info, bindings, args_addr);
|
||||
if (program.info.uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
RebindImages(bindings);
|
||||
// Acquiring arguments can merge cache buffers; finalize shader bindings afterward.
|
||||
const auto [args_buffer, args_offset] = m_context.GetBufferCache().ObtainBuffer(
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
RenderContext::RenderContext(GraphicContext& graphics, const std::filesystem::path& shader_store)
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
|
||||
m_pipeline_cache(graphics), m_sampler_cache(graphics),
|
||||
m_pipeline_cache(graphics, &m_image_heap, shader_store), m_sampler_cache(graphics),
|
||||
m_buffer_cache(graphics, m_command_scheduler, m_page_manager, m_texture_cache),
|
||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache) {
|
||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache),
|
||||
m_image_heap(*this, m_page_manager), m_occlusion_queries(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -64,6 +65,7 @@ bool RenderContext::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) no
|
||||
if (access == PageFaultAccess::Write) {
|
||||
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
|
||||
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
|
||||
m_image_heap.InvalidateMemory(fault_vaddr, fault_size);
|
||||
} else {
|
||||
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
|
||||
}
|
||||
@@ -76,6 +78,7 @@ bool RenderContext::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.InvalidateMemory(vaddr, size);
|
||||
m_image_heap.InvalidateMemory(vaddr, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -112,6 +115,7 @@ void RenderContext::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_image_heap.UnmapMemory(vaddr, size);
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Subtract(vaddr, size);
|
||||
};
|
||||
@@ -130,7 +134,11 @@ void RenderContext::PrepareBda() {
|
||||
m_bda_logged = true;
|
||||
}
|
||||
std::shared_lock lock(m_mapped_ranges_mutex);
|
||||
m_buffer_cache.SynchronizeBda(m_mapped_ranges);
|
||||
m_buffer_cache.TakeBdaSync().ForEach([this](uint64_t begin, uint64_t end) {
|
||||
m_mapped_ranges.ForEachInRange(begin, end - begin, [this](uint64_t start, uint64_t finish) {
|
||||
m_buffer_cache.SynchronizeBuffersInRange(start, finish - start);
|
||||
});
|
||||
});
|
||||
m_fault_process_pending = true;
|
||||
}
|
||||
|
||||
@@ -139,6 +147,7 @@ void RenderContext::RunGarbageCollector() {
|
||||
m_fault_process_pending = false;
|
||||
m_buffer_cache.ProcessFaultBuffer();
|
||||
}
|
||||
m_image_heap.RunGarbageCollector();
|
||||
m_texture_cache.ProcessDownloadImages();
|
||||
m_texture_cache.RunGarbageCollector();
|
||||
m_buffer_cache.RunGarbageCollector();
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
@@ -29,7 +31,8 @@ class GuestGpu;
|
||||
|
||||
class RenderContext {
|
||||
public:
|
||||
explicit RenderContext(GraphicContext& graphics);
|
||||
explicit RenderContext(GraphicContext& graphics,
|
||||
const std::filesystem::path& shader_store = ShaderStorePath());
|
||||
~RenderContext();
|
||||
KYTY_CLASS_NO_COPY(RenderContext);
|
||||
|
||||
@@ -43,10 +46,12 @@ public:
|
||||
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
|
||||
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
|
||||
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
|
||||
ImageHeap& GetImageHeap() { return m_image_heap; }
|
||||
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
|
||||
BufferCache& GetBufferCache() { return m_buffer_cache; }
|
||||
TextureCache& GetTextureCache() { return m_texture_cache; }
|
||||
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
|
||||
OcclusionQueries& GetOcclusionQueries() { return m_occlusion_queries; }
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
@@ -76,6 +81,8 @@ private:
|
||||
PageManager m_page_manager;
|
||||
BufferCache m_buffer_cache;
|
||||
TextureCache m_texture_cache;
|
||||
ImageHeap m_image_heap;
|
||||
OcclusionQueries m_occlusion_queries;
|
||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||
RangeSet m_mapped_ranges;
|
||||
std::unique_ptr<GuestGpu> m_gpu;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
@@ -58,7 +57,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
||||
}
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const auto& program = *vs_input_info.stage.program;
|
||||
const auto& program = vs_input_info.stage.module->info;
|
||||
const auto& resources = *vs_input_info.stage.resources;
|
||||
if (index_offset == 0 &&
|
||||
program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
|
||||
@@ -198,7 +197,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
const auto& dc = ctx.GetDepthControl();
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
const auto& vp0 = vp.viewports[0];
|
||||
const auto& ps_resources = ps_input_info.stage.program->info;
|
||||
const auto& ps_resources = ps_input_info.stage.module->info.info;
|
||||
const auto sampled_images = std::count_if(
|
||||
ps_resources.images.begin(), ps_resources.images.end(), [](const auto& image) {
|
||||
return image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
@@ -321,7 +320,7 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
const vk::Extent2D framebuffer_extent {rendering.width, rendering.height};
|
||||
const auto& outputs = vs_input_info.stage.program->info.outputs;
|
||||
const auto& outputs = vs_input_info.stage.module->info.info.outputs;
|
||||
const bool indexed_viewports =
|
||||
std::any_of(outputs.begin(), outputs.end(), [](const auto& output) {
|
||||
return output.kind == ShaderRecompiler::IR::StageOutputKind::ViewportIndex;
|
||||
@@ -404,6 +403,20 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState
|
||||
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);
|
||||
if (depth.stencil_test_enable) {
|
||||
const auto& front = depth.stencil_front;
|
||||
@@ -433,22 +446,6 @@ static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
|
||||
return vs.es_regs.data_addr != 0;
|
||||
}
|
||||
|
||||
static bool PixelShaderHasDepthOrCoverageSideEffects(const HW::ShaderRegisters& sh_regs) {
|
||||
const auto& db = sh_regs.db_shader_control;
|
||||
return db.shader_kill_enable || db.shader_z_export_enable || db.shader_mask_export_enable ||
|
||||
db.shader_dual_export_enable || db.shader_execute_on_noop || sh_regs.AlphaToMask();
|
||||
}
|
||||
|
||||
struct DrawRenderState {
|
||||
RenderDepthInfo depth_info;
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
bool ps_active = true;
|
||||
std::array<ShaderVertexInputInfo, 3> vertex_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
PipelineCache::GraphicsPrograms programs;
|
||||
};
|
||||
|
||||
struct DrawCallInfo {
|
||||
CommandBufferDebugOp debug_op = CommandBufferDebugOp::DrawIndex;
|
||||
uint32_t index_count = 0;
|
||||
@@ -474,9 +471,8 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
auto& target = colors[i];
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto owner = cache.m_slot_images.try_get(target.image_id);
|
||||
if (owner == nullptr || (!owner->registered && !owner->info.data.Empty()) ||
|
||||
owner->binding.needs_rebind) {
|
||||
const auto& owner = cache.m_slot_images[target.image_id];
|
||||
if ((!owner.registered && !owner.info.data.Empty()) || owner.binding.needs_rebind) {
|
||||
EXIT("color target changed after render-state discovery\n");
|
||||
}
|
||||
const auto image_view = cache.FindRenderTarget(target.image_id, target.desc);
|
||||
@@ -502,8 +498,8 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
attachment.image_layout = layout;
|
||||
}
|
||||
if (depth.image_id) {
|
||||
const auto owner = cache.m_slot_images.try_get(depth.image_id);
|
||||
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
|
||||
const auto& owner = cache.m_slot_images[depth.image_id];
|
||||
if (!owner.registered || owner.binding.needs_rebind) {
|
||||
EXIT("depth target changed after render-state discovery\n");
|
||||
}
|
||||
const auto image_view = cache.FindDepthTarget(depth.image_id, depth.desc);
|
||||
@@ -589,23 +585,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
return state;
|
||||
}
|
||||
|
||||
static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
|
||||
uint32_t output_mask = ctx.HasObjectPrimitiveIdOutput() ? 1u << 7u : 0;
|
||||
for (uint32_t index = 0; index < RENDER_COLOR_ATTACHMENTS_MAX; index++) {
|
||||
const auto slot = ShaderPixelExportTarget(sh_regs.m_cbShaderMask, index);
|
||||
if (slot >= RENDER_COLOR_ATTACHMENTS_MAX) {
|
||||
break;
|
||||
}
|
||||
if (sh_regs.target_output_mode[index] != 0 &&
|
||||
render_target_mask_slot(write_mask, slot) != 0) {
|
||||
output_mask |= 1u << slot;
|
||||
}
|
||||
}
|
||||
return output_mask;
|
||||
}
|
||||
|
||||
enum class CbColorMode : uint8_t {
|
||||
Disable = 0,
|
||||
Normal = 1,
|
||||
@@ -631,10 +610,11 @@ bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
|
||||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
||||
}
|
||||
|
||||
// vertex_offset is the first vertex of non-indexed draws.
|
||||
struct DrawEmitInfo {
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct DrawIndexBufferSource {
|
||||
@@ -869,61 +849,24 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t color_output_mask, DrawRenderState& state) {
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
const auto& vertex_shader_info = sh_ctx.GetVs();
|
||||
const auto& pixel_shader_info = sh_ctx.GetPs();
|
||||
const auto& shader_regs = ctx.GetShaderRegisters();
|
||||
|
||||
state.programs = {};
|
||||
state.ps_input_info = {};
|
||||
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
|
||||
target_export_mapping {};
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
const auto& rt = ctx.GetRenderTarget(slot);
|
||||
if ((color_output_mask & (1u << slot)) != 0 && rt.base.addr != 0) {
|
||||
target_export_mapping[slot] =
|
||||
TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
|
||||
rt.info.channel_order)
|
||||
.export_mapping;
|
||||
}
|
||||
}
|
||||
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||
}
|
||||
state.programs = pipeline_cache.GetGraphicsPrograms(
|
||||
vertex_shader_info, pixel_shader_info, shader_regs, ctx, buffer.GetUserConfig(),
|
||||
target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info);
|
||||
}
|
||||
|
||||
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state) {
|
||||
const auto& shader_regs = buffer.GetRegisters().GetShaderRegisters();
|
||||
const auto color_output_mask = DrawColorOutputMask(buffer.GetRegisters());
|
||||
state.ps_active = buffer.GetShaders().GetPs().ps_regs.data_addr != 0 &&
|
||||
(color_output_mask != 0 ||
|
||||
PixelShaderHasDepthOrCoverageSideEffects(shader_regs));
|
||||
RefreshShaders(buffer, draw, color_output_mask, state);
|
||||
uint32_t mrt_mask = 0;
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt ||
|
||||
output.kind == ShaderRecompiler::IR::StageOutputKind::ObjectPrimitiveId) {
|
||||
mrt_mask |= 1u << output.location;
|
||||
}
|
||||
}
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||
}
|
||||
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;
|
||||
}
|
||||
mrt_mask &= color_output_mask;
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||
}
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if ((mrt_mask & (1u << slot)) != 0) {
|
||||
if ((state.mrt_mask & (1u << slot)) != 0) {
|
||||
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
|
||||
render_target_slice_offset, slot);
|
||||
if (state.color_info[state.color_count].image_id) {
|
||||
@@ -1043,8 +986,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
|
||||
emit.first_instance);
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset), emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
@@ -1054,7 +997,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
vk_buffer.drawIndexed(4, draw.instance_count, i, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(4, draw.instance_count, i + emit.first_vertex,
|
||||
vk_buffer.draw(4, draw.instance_count,
|
||||
i + static_cast<uint32_t>(emit.vertex_offset),
|
||||
emit.first_instance);
|
||||
}
|
||||
}
|
||||
@@ -1075,7 +1019,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
|
||||
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled ||
|
||||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
|
||||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
|
||||
emit.first_vertex != 0 || emit.first_instance != 0 ||
|
||||
emit.vertex_offset != 0 || emit.first_instance != 0 ||
|
||||
state.color_count != 1 || color.target_slot != 0 || !depth.image_id ||
|
||||
color.desc.info.samples != 1 ||
|
||||
depth.desc.info.extent.width != color.Extent().width ||
|
||||
@@ -1121,16 +1065,15 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
|
||||
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const bool primitive_restart_enable = state.primitive_restart_enable;
|
||||
if (CopyDepthToColor(buffer, draw, emit, state)) {
|
||||
return;
|
||||
}
|
||||
const auto vertex_stages =
|
||||
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
|
||||
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
|
||||
const auto vertex_stages = state.VertexStages();
|
||||
const bool mesh_active = state.vertex_info[0].stage.module->info.stage == ShaderType::Mesh;
|
||||
uint32_t mesh_groups = 0;
|
||||
if (mesh_active) {
|
||||
const auto& mesh = state.vertex_info[0].mesh;
|
||||
@@ -1189,13 +1132,18 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
|
||||
index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
}
|
||||
std::pair<Buffer*, uint64_t> args_buffer {};
|
||||
if (emit.indirect.address != 0) {
|
||||
const auto size = static_cast<uint64_t>(emit.indirect.count - 1) * emit.indirect.stride +
|
||||
(draw.IsIndexed() ? sizeof(vk::DrawIndexedIndirectCommand)
|
||||
: sizeof(vk::DrawIndirectCommand));
|
||||
args_buffer = m_context.GetBufferCache().ObtainBuffer(emit.indirect.address, size, false);
|
||||
EXIT_IF((args_buffer.second & 3u) != 0);
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "CreatePipeline");
|
||||
}
|
||||
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
|
||||
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
|
||||
state.programs);
|
||||
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(buffer, state);
|
||||
vk::ImageAspectFlags feedback_aspects;
|
||||
const auto rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
|
||||
@@ -1225,12 +1173,11 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (draw_dwords != 0) {
|
||||
std::array<uint32_t, PushData::MeshDrawDwordCount> draw_data {};
|
||||
if (mesh_active) {
|
||||
draw_data = {
|
||||
draw.index_count,
|
||||
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
|
||||
emit.first_instance, index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
draw_data = {draw.index_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset),
|
||||
index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
} else {
|
||||
draw_data[0] = buffer.GetUserConfig().GetObjectId();
|
||||
}
|
||||
@@ -1273,11 +1220,20 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (!draw.IsIndexed()) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||
}
|
||||
auto& occlusion_queries = m_context.GetOcclusionQueries();
|
||||
const int32_t occlusion_query = occlusion_queries.Begin(vk_buffer);
|
||||
if (mesh_active) {
|
||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||
} else {
|
||||
} else if (args_buffer.first == nullptr) {
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
|
||||
} else if (draw.IsIndexed()) {
|
||||
vk_buffer.drawIndexedIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||
emit.indirect.count, emit.indirect.stride);
|
||||
} else {
|
||||
vk_buffer.drawIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||
emit.indirect.count, emit.indirect.stride);
|
||||
}
|
||||
occlusion_queries.End(vk_buffer, occlusion_query);
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::Draws);
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::DrawInstances, draw.instance_count);
|
||||
|
||||
@@ -1294,7 +1250,32 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry
|
||||
// their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
int32_t first_vertex,
|
||||
uint32_t first_instance,
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy ||
|
||||
vs.indirect_offsets != indirect.offsets) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DrawEmitInfo {.indirect = indirect};
|
||||
}
|
||||
if (source == DrawOffsetSource::IndirectArgs) {
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
}
|
||||
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
|
||||
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
||||
}
|
||||
|
||||
bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawIndexArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -1309,19 +1290,19 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
reinterpret_cast<uint64_t>(args.index_addr));
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (args.index_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
if (args.index_count == 0 || (args.instance_count == 0 && args.indirect.address == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DrawHasValidVertexShader(sh_ctx)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -1345,9 +1326,9 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
return;
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
@@ -1367,7 +1348,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
default: EXIT("unknown index_type_and_size: %u\n", args.index_type_and_size);
|
||||
}
|
||||
index_source.size = static_cast<uint64_t>(args.index_count) * index_source.guest_element_size;
|
||||
const bool primitive_restart = ResolvePrimitiveRestart(buffer, index_source);
|
||||
state.primitive_restart_enable = ResolvePrimitiveRestart(buffer, index_source);
|
||||
|
||||
std::vector<uint16_t> expanded_indices;
|
||||
if (index_source.guest_element_size == 1) {
|
||||
@@ -1375,7 +1356,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const auto* src = static_cast<const uint8_t*>(args.index_addr);
|
||||
expanded_indices.resize(args.index_count);
|
||||
for (uint32_t i = 0; i < args.index_count; i++) {
|
||||
expanded_indices[i] = primitive_restart && src[i] == 0xffu ? 0xffffu : src[i];
|
||||
expanded_indices[i] =
|
||||
state.primitive_restart_enable && src[i] == 0xffu ? 0xffffu : src[i];
|
||||
}
|
||||
index_source.host_data = expanded_indices.data();
|
||||
index_source.size = expanded_indices.size() * sizeof(uint16_t);
|
||||
@@ -1383,33 +1365,24 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndex, args.index_count,
|
||||
args.instance_count, args.first_instance};
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
|
||||
args.index_addr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart);
|
||||
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
|
||||
args.base_vertex, args.first_instance, args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, *emit, index_source);
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
@@ -1423,19 +1396,19 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
args.first_instance);
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (args.vertex_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
if (args.indirect.address == 0 && (args.vertex_count == 0 || args.instance_count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DrawHasValidVertexShader(sh_ctx)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -1458,20 +1431,19 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
|
||||
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
|
||||
state.vertex_info[0].stage.program->param_export_mask == 0 &&
|
||||
state.vertex_info[0].stage.module->info.param_export_mask == 0 &&
|
||||
state.ps_input_info.input_num != 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
|
||||
@@ -1480,24 +1452,18 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
|
||||
}
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, 0, nullptr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.first_vertex = native_indirect ? 0 :
|
||||
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
|
||||
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
|
||||
static_cast<int32_t>(args.first_vertex), args.first_instance,
|
||||
args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, *emit, {});
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
}
|
||||
|
||||
bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_target_slice_offset) {
|
||||
@@ -1529,8 +1495,9 @@ bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_
|
||||
cache.MarkGpuWritten(dst.image_id);
|
||||
auto& source = cache.GetImage(src.image_id);
|
||||
auto& destination = cache.GetImage(dst.image_id);
|
||||
destination.Resolve(source, {src.guest_mip_level, 1, src.guest_array_layer, 1},
|
||||
{dst.guest_mip_level, 1, dst.guest_array_layer, 1});
|
||||
destination.Resolve(source,
|
||||
{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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#version 450
|
||||
|
||||
// One workgroup per metadata slice; see TileManager::CheckColorClear.
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
layout(set = 0, binding = 0, std430) buffer Metadata { uint data[]; } metadata;
|
||||
layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[]; } predicates;
|
||||
|
||||
layout(push_constant, std430) uniform Check {
|
||||
uint metadata_base;
|
||||
uint words;
|
||||
uint keys;
|
||||
uint expand;
|
||||
} params;
|
||||
|
||||
const uint Slots = 8u; // TileManager::ColorClearSlots
|
||||
|
||||
shared uint key;
|
||||
shared uint mismatch;
|
||||
|
||||
void main() {
|
||||
const uint slice = gl_WorkGroupID.x;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.words;
|
||||
// Each invocation reads, and may rewrite, only its own words.
|
||||
if (gl_LocalInvocationIndex == 0u) {
|
||||
key = metadata.data[base] & 0xffu;
|
||||
mismatch = 0u;
|
||||
}
|
||||
barrier();
|
||||
const uint pattern = key * 0x01010101u;
|
||||
bool same = true;
|
||||
for (uint i = gl_LocalInvocationIndex; same && i < params.words; i += gl_WorkGroupSize.x) {
|
||||
same = metadata.data[base + i] == pattern;
|
||||
}
|
||||
if (!same) {
|
||||
atomicOr(mismatch, 1u);
|
||||
}
|
||||
barrier();
|
||||
const uint slot = key >> 5u;
|
||||
const bool hit = mismatch == 0u && (key & 0x1fu) == 0u && (params.keys & (1u << slot)) != 0u;
|
||||
if (gl_LocalInvocationIndex < Slots) {
|
||||
predicates.data[slice * Slots + gl_LocalInvocationIndex] =
|
||||
uint(hit && slot == gl_LocalInvocationIndex);
|
||||
}
|
||||
if (hit && params.expand != 0u) {
|
||||
for (uint i = gl_LocalInvocationIndex; i < params.words; i += gl_WorkGroupSize.x) {
|
||||
metadata.data[base + i] = 0xffffffffu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
#version 450
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
|
||||
layout(constant_id = 0) const uint D32 = 0u;
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
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 {
|
||||
uint src_base;
|
||||
@@ -32,9 +33,5 @@ void main() {
|
||||
}
|
||||
uint src = params.src_base + y * params.source_pitch + x * 4u;
|
||||
uint dst = params.dst_base + y * params.target_pitch + x * 2u;
|
||||
uint shift = (dst & 2u) * 8u;
|
||||
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);
|
||||
output_buffer.data[dst >> 1u] = uint16_t(decode(input_buffer.data[src >> 2u]));
|
||||
}
|
||||
@@ -310,6 +310,10 @@ void GpuActivityRows(const Snapshot& shown) {
|
||||
Row("CPU waiting on GPU",
|
||||
Fmt("%.0f ms/s (%.0f waits/s)", Rate(shown, Counter::GpuWaitNs) / 1e6,
|
||||
Rate(shown, Counter::GpuWaits)));
|
||||
Row("DCC/CMASK readbacks",
|
||||
Fmt("%.0f ms/s (%.0f/s)", Rate(shown, Counter::ColorMetadataReadbackNs) / 1e6,
|
||||
Rate(shown, Counter::ColorMetadataReadbacks)));
|
||||
Row("DCC/CMASK GPU checks", Fmt("%.0f/s", Rate(shown, Counter::ColorMetadataChecks)));
|
||||
}
|
||||
|
||||
void CompilationRows(const Snapshot& shown) {
|
||||
@@ -529,16 +533,16 @@ void DrawMemoryAndIo(const Snapshot& shown) {
|
||||
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
||||
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
||||
Row("Extended", Bytes(guest.automatic_allocated));
|
||||
// Past the pool size the guest sees no page-table entries available.
|
||||
const auto entries = std::max(guest.cpu_page_entries, guest.gpu_page_entries);
|
||||
// Past its total a side sees no page-table entries available.
|
||||
const auto percent = std::max(guest.cpu_page_entries * 100 / guest.cpu_page_entries_total,
|
||||
guest.gpu_page_entries * 100 / guest.gpu_page_entries_total);
|
||||
Row("Page table (2 MB)",
|
||||
Fmt("CPU %llu GPU %llu / %llu",
|
||||
Fmt("CPU %llu / %llu GPU %llu / %llu",
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries_total),
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.page_entries_total)),
|
||||
entries > guest.page_entries_total ? BAD
|
||||
: entries * 10 > guest.page_entries_total * 9 ? MEDIUM
|
||||
: GOOD);
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries_total)),
|
||||
LevelColor(static_cast<double>(percent), 90, 100));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
if (BeginSection("Emulator process")) {
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||
|
||||
private:
|
||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||
@@ -240,6 +240,7 @@ private:
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
void SignalDoneLocked();
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
Common::Mutex m_mutex;
|
||||
@@ -250,6 +251,7 @@ private:
|
||||
std::list<Request> m_cpu_requests;
|
||||
std::list<Request> m_cancelled_requests;
|
||||
bool m_processing = false;
|
||||
bool m_gpu_waiting = false;
|
||||
uint64_t m_next_request_id = 1;
|
||||
};
|
||||
|
||||
@@ -953,7 +955,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
}
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||
@@ -1033,7 +1035,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
it = queue->erase(it);
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_submit_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
@@ -1205,19 +1207,26 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto has_request = [this, &cfg, index] {
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
};
|
||||
while (has_request()) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
void FlipQueue::SignalDoneLocked() {
|
||||
m_done_cond_var.SignalAll();
|
||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||
if (m_gpu_waiting) {
|
||||
m_gpu_waiting = false;
|
||||
m_presenter.Renderer().GetGpu().Wake();
|
||||
}
|
||||
}
|
||||
|
||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
||||
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 &&
|
||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
m_gpu_waiting = !safe;
|
||||
return safe;
|
||||
}
|
||||
|
||||
bool FlipQueue::Flip(uint32_t micros) {
|
||||
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
|
||||
|
||||
@@ -1311,7 +1320,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_processing = false;
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
@@ -1694,12 +1703,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
||||
auto* ctx = m_impl->Get(handle);
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot(int handle);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
@@ -50,6 +51,7 @@ struct VulkanExtensions {
|
||||
vk::PhysicalDeviceVulkan11Features WindowContext::RequiredVulkan11Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan11Features features {};
|
||||
features.storageBuffer16BitAccess = VK_TRUE;
|
||||
features.shaderDrawParameters = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -65,6 +67,9 @@ vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noe
|
||||
features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE;
|
||||
features.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
|
||||
features.descriptorBindingUpdateUnusedWhilePending = VK_TRUE;
|
||||
features.descriptorBindingPartiallyBound = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -274,6 +279,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
required_features12.samplerMirrorClampToEdge);
|
||||
check_feature(features11.storageBuffer16BitAccess, "storageBuffer16BitAccess",
|
||||
WindowContext::RequiredVulkan11Features().storageBuffer16BitAccess);
|
||||
check_feature(features11.shaderDrawParameters, "shaderDrawParameters",
|
||||
WindowContext::RequiredVulkan11Features().shaderDrawParameters);
|
||||
check_feature(features12.storageBuffer8BitAccess, "storageBuffer8BitAccess",
|
||||
required_features12.storageBuffer8BitAccess);
|
||||
check_feature(features12.timelineSemaphore, "timelineSemaphore",
|
||||
@@ -295,6 +302,14 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
check_feature(features12.shaderStorageBufferArrayNonUniformIndexing,
|
||||
"shaderStorageBufferArrayNonUniformIndexing",
|
||||
required_features12.shaderStorageBufferArrayNonUniformIndexing);
|
||||
check_feature(features12.descriptorBindingSampledImageUpdateAfterBind,
|
||||
"descriptorBindingSampledImageUpdateAfterBind",
|
||||
required_features12.descriptorBindingSampledImageUpdateAfterBind);
|
||||
check_feature(features12.descriptorBindingUpdateUnusedWhilePending,
|
||||
"descriptorBindingUpdateUnusedWhilePending",
|
||||
required_features12.descriptorBindingUpdateUnusedWhilePending);
|
||||
check_feature(features12.descriptorBindingPartiallyBound, "descriptorBindingPartiallyBound",
|
||||
required_features12.descriptorBindingPartiallyBound);
|
||||
check_feature(features13.robustImageAccess, "robustImageAccess");
|
||||
check_feature(robustness2.nullDescriptor, "nullDescriptor");
|
||||
check_feature(features13.dynamicRendering, "dynamicRendering",
|
||||
@@ -325,9 +340,13 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
check_feature(device_features2.features.shaderImageGatherExtended,
|
||||
"shaderImageGatherExtended");
|
||||
check_feature(device_features2.features.shaderResourceMinLod, "shaderResourceMinLod");
|
||||
|
||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||
check_feature(device_features2.features.drawIndirectFirstInstance,
|
||||
"drawIndirectFirstInstance");
|
||||
check_feature(device_features2.features.multiDrawIndirect, "multiDrawIndirect");
|
||||
|
||||
if (!skip_device) {
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
@@ -492,6 +511,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
maintenance8.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &maintenance8;
|
||||
}
|
||||
const bool shader_clock_extension =
|
||||
HasExtension(device_extensions, VK_KHR_SHADER_CLOCK_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderClockFeaturesKHR shader_clock {};
|
||||
if (shader_clock_extension) {
|
||||
shader_clock.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &shader_clock;
|
||||
}
|
||||
const bool image_atomic_int64_extension =
|
||||
HasExtension(device_extensions, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic_int64 {};
|
||||
@@ -499,6 +525,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
image_atomic_int64.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &image_atomic_int64;
|
||||
}
|
||||
const bool conditional_rendering_extension =
|
||||
HasExtension(device_extensions, VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering {};
|
||||
if (conditional_rendering_extension) {
|
||||
conditional_rendering.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &conditional_rendering;
|
||||
}
|
||||
const bool barycentric_extension =
|
||||
HasExtension(device_extensions, VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_barycentric {};
|
||||
@@ -572,6 +605,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
|
||||
// The device clock ticks at timestampPeriod nanoseconds.
|
||||
if (shader_clock_extension && shader_clock.shaderDeviceClock) {
|
||||
graphics.device_clock_period_ps = static_cast<uint32_t>(
|
||||
std::lround(graphics.physical_device_properties.limits.timestampPeriod * 1000.0f));
|
||||
}
|
||||
graphics.conditional_rendering_enabled =
|
||||
conditional_rendering_extension && conditional_rendering.conditionalRendering;
|
||||
LOGF("Vulkan maintenance8 support: %s\n",
|
||||
graphics.maintenance8_enabled ? "true" : "false");
|
||||
LOGF("Vulkan depth feedback support: %s\n",
|
||||
@@ -597,10 +637,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.depthClamp = VK_TRUE;
|
||||
#endif
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderStorageImageMultisample =
|
||||
supported_features2.features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.shaderResourceMinLod = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.drawIndirectFirstInstance = VK_TRUE;
|
||||
device_features.multiDrawIndirect = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
device_features.shaderClipDistance = VK_TRUE;
|
||||
@@ -609,7 +654,22 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.multiViewport = VK_TRUE;
|
||||
device_features.fillModeNonSolid = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
// Storage image sample counts beyond 1x require the feature.
|
||||
if (!device_features.shaderStorageImageMultisample) {
|
||||
graphics.physical_device_properties.limits.storageImageSampleCounts =
|
||||
vk::SampleCountFlagBits::e1;
|
||||
}
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
// Occlusion queries reset their queries from the host and count samples exactly where they can.
|
||||
graphics.occlusion_queries_enabled =
|
||||
Config::OcclusionQueriesEnabled() && supported_features12.hostQueryReset;
|
||||
graphics.occlusion_query_precise_enabled =
|
||||
graphics.occlusion_queries_enabled && supported_features2.features.occlusionQueryPrecise;
|
||||
features12.hostQueryReset = graphics.occlusion_queries_enabled;
|
||||
device_features.occlusionQueryPrecise = graphics.occlusion_query_precise_enabled;
|
||||
LOGF("Vulkan occlusion queries: %s, precise: %s\n",
|
||||
graphics.occlusion_queries_enabled ? "true" : "false",
|
||||
graphics.occlusion_query_precise_enabled ? "true" : "false");
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
device_features.shaderFloat64 =
|
||||
supported_features2.features.shaderFloat64 &&
|
||||
@@ -670,6 +730,23 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &maintenance8;
|
||||
}
|
||||
if (graphics.device_clock_period_ps != 0u) {
|
||||
shader_clock.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &shader_clock;
|
||||
}
|
||||
if (graphics.conditional_rendering_enabled) {
|
||||
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||
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.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -1041,12 +1118,15 @@ void WindowContext::CreateVulkan() {
|
||||
graphic_ctx.memory_budget_ext_enabled = true;
|
||||
}
|
||||
for (const auto* extension: {VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
|
||||
VK_KHR_SHADER_CLOCK_EXTENSION_NAME,
|
||||
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_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_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME}) {
|
||||
if (HasExtension(available_extensions, extension)) {
|
||||
device_extensions.push_back(extension);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_SIMD
|
||||
#include "stb_image.h"
|
||||
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
|
||||
@@ -30,10 +30,6 @@ namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
namespace {
|
||||
|
||||
const char* GetDumpLabel(const CompileOptions& options) {
|
||||
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
|
||||
}
|
||||
|
||||
std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
|
||||
std::string dump = "CFG:\n";
|
||||
dump += cfg;
|
||||
@@ -44,30 +40,17 @@ std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
|
||||
return dump;
|
||||
}
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Local: return "LS";
|
||||
case ShaderType::TessellationControl: return "HS";
|
||||
case ShaderType::TessellationEvaluation: return "TES";
|
||||
case ShaderType::Mesh: return "MS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void LogDispatcherFallback(const CompileOptions& options, const CFG::Graph& cfg, const char* phase) {
|
||||
const auto* block = cfg.FindBlock(cfg.failure_block);
|
||||
const auto start = block != nullptr ? block->start_pc : UINT32_MAX;
|
||||
const auto end = block != nullptr ? block->end_pc : UINT32_MAX;
|
||||
const auto predecessors = block != nullptr ? block->predecessors.size() : 0u;
|
||||
const auto successors = block != nullptr ? block->successors.size() : 0u;
|
||||
LOGF("%s CFG dispatcher fallback: stage=%s hash=0x%016" PRIx64
|
||||
LOGF("ShaderRecompiler %s CFG dispatcher fallback: hash=0x%016" PRIx64
|
||||
" phase=%s failure=%s block=%" PRIu32 " pc=0x%08" PRIx32 "..0x%08" PRIx32 " preds=%" PRIu64
|
||||
" succs=%" PRIu64 " blocks=%" PRIu64 " loops=%" PRIu64 " back_edges=%" PRIu64
|
||||
" reason=%s\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash, phase,
|
||||
StageName(options.stage), options.shader_hash, phase,
|
||||
CFG::FailureKindToString(cfg.failure_kind).c_str(), cfg.failure_block, start, end,
|
||||
static_cast<uint64_t>(predecessors), static_cast<uint64_t>(successors),
|
||||
static_cast<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
|
||||
@@ -484,6 +467,7 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
||||
void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
using namespace IR;
|
||||
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
||||
source.wave_size = options.wave_size;
|
||||
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
||||
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
||||
source.calls.push_back({.instruction = i});
|
||||
@@ -520,7 +504,8 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
};
|
||||
Register = [&](uint32_t before, uint32_t code) -> Value {
|
||||
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) {
|
||||
std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code);
|
||||
}
|
||||
@@ -584,14 +569,30 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
for (auto& inst: block) {
|
||||
inst.SetParent(nullptr);
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 || inst.GetOpcode() == ValueOpcode::ReadConstBuffer)
|
||||
inst.SetFlags(SrtReadFlags {.index = inst.Flags<MemoryFlags>().index});
|
||||
inst.SetFlags(MemoryFlags {.index = inst.Flags<MemoryFlags>().index});
|
||||
}
|
||||
query.value_storage.splice(query.value_storage.end(), block.Instructions());
|
||||
source.call_targets = std::move(static_cast<ResourcePlan&>(query));
|
||||
std::vector<Value> targets;
|
||||
for (const auto& site: source.calls) targets.insert(targets.end(), site.target.begin(), site.target.end());
|
||||
CompileSrtWalker(source.call_targets, targets);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Local: return "LS";
|
||||
case ShaderType::TessellationControl: return "HS";
|
||||
case ShaderType::TessellationEvaluation: return "TES";
|
||||
case ShaderType::Mesh: return "MS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ShaderSource PrepareShaderSource(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
EXIT_IF(code.empty());
|
||||
ShaderSource source;
|
||||
@@ -617,12 +618,18 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
EXIT_IF(source.unsupported_call_pc != UINT32_MAX);
|
||||
auto& reads = source.reads;
|
||||
reads.clear();
|
||||
IR::SrtReadCapture capture(runtime, reads);
|
||||
const auto clean = IR::CleanRuntime(capture.ObservedRuntime());
|
||||
IR::SrtWalker walker(source.call_targets, clean);
|
||||
std::vector<uint32_t> flat;
|
||||
const bool walked = IR::WalkSrt(source.call_targets, runtime, flat, &reads);
|
||||
const auto read_code = [&](uint64_t address, std::span<uint32_t> words) {
|
||||
if (runtime.read_specialization_memory == nullptr ||
|
||||
!runtime.read_specialization_memory(runtime.userdata, address, words)) return false;
|
||||
reads.emplace_back(address, words.size_bytes());
|
||||
return true;
|
||||
};
|
||||
for (auto& site: source.calls) {
|
||||
uint32_t low = 0, high = 0;
|
||||
if (!walker.Evaluate(site.target[0], low) || !walker.Evaluate(site.target[1], high))
|
||||
if (!walked || !IR::WalkedValue(source.call_targets, site.target[0], low) ||
|
||||
!IR::WalkedValue(source.call_targets, site.target[1], high))
|
||||
EXIT("shader call at pc 0x%08x has an unavailable scalar target",
|
||||
source.decoded.instructions[site.instruction].pc);
|
||||
// SWAPPC reads the old aliased pair and ignores the target's low two bits.
|
||||
@@ -646,7 +653,7 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
break;
|
||||
}
|
||||
linked.observed_function.resize(function.size);
|
||||
if (!clean.read_memory(clean.userdata, source.calls[i].address, linked.observed_function) ||
|
||||
if (!read_code(source.calls[i].address, linked.observed_function) ||
|
||||
!std::ranges::equal(expected, linked.observed_function)) {
|
||||
unchanged = false;
|
||||
break;
|
||||
@@ -676,8 +683,7 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
uint32_t count = 1;
|
||||
Decoder::Instruction inst;
|
||||
for (;;) {
|
||||
if (!clean.read_memory(clean.userdata, address + uint64_t {index} * sizeof(uint32_t),
|
||||
std::span(words).first(count)))
|
||||
if (!read_code(address + uint64_t {index} * sizeof(uint32_t), std::span(words).first(count)))
|
||||
EXIT("shader call at pc 0x%08x cannot read leaf code", call.pc);
|
||||
inst = {};
|
||||
Decoder::DecodeInstruction(words, 0u, inst);
|
||||
@@ -698,10 +704,11 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
EXIT_IF(function_pc > UINT32_MAX - function_size);
|
||||
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
||||
for (auto inst: function) {
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) ||
|
||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u));
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg, source.wave_size) ||
|
||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u, source.wave_size));
|
||||
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);
|
||||
}
|
||||
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
||||
@@ -756,6 +763,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
static_cast<unsigned>(options.stage));
|
||||
}
|
||||
|
||||
const auto* stage = StageName(options.stage);
|
||||
const auto compile_begin = std::chrono::steady_clock::now();
|
||||
const auto phase_ms = [&compile_begin]() {
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
@@ -765,27 +773,25 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
std::string decoded_dump;
|
||||
if (options.dump_ir) {
|
||||
decoded_dump = Decoder::ProgramToString(decoded);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s decoded RDNA2 (early):\n%s", GetDumpLabel(options), decoded_dump.c_str());
|
||||
}
|
||||
LOGF("ShaderRecompiler %s decoded RDNA2:\n%s", stage, decoded_dump.c_str());
|
||||
}
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " CFG BuildGraph\n", stage,
|
||||
options.shader_hash);
|
||||
auto native_cfg = CFG::BuildGraph(decoded);
|
||||
CFG::Graph structured_cfg;
|
||||
auto* selected_cfg = &native_cfg;
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
stage, options.shader_hash,
|
||||
static_cast<uint64_t>(native_cfg.blocks.size()),
|
||||
static_cast<uint64_t>(native_cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(native_cfg.back_edges.size()), phase_ms());
|
||||
if (native_cfg.irreducible) {
|
||||
LogDispatcherFallback(options, native_cfg, "build");
|
||||
} else {
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " CFG Structurize\n", stage,
|
||||
options.shader_hash);
|
||||
structured_cfg = CFG::Structurize(native_cfg);
|
||||
if (structured_cfg.unsupported) {
|
||||
native_cfg.unsupported = true;
|
||||
@@ -795,12 +801,12 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
LogDispatcherFallback(options, native_cfg, "structurize");
|
||||
} else {
|
||||
selected_cfg = &structured_cfg;
|
||||
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
LOGF("ShaderRecompiler %s structured CFG success: blocks=%" PRIu64 "\n", stage,
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()));
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
stage, options.shader_hash,
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()),
|
||||
static_cast<uint64_t>(selected_cfg->natural_loops.size()), phase_ms());
|
||||
}
|
||||
@@ -813,7 +819,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
decoded, *options.input_info.vertex, options.user_data_base,
|
||||
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
|
||||
if (!embedded_fetch.loads.empty()) {
|
||||
LOGF("%s embedded vertex fetch plan: detected=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
LOGF("ShaderRecompiler %s embedded vertex fetch plan: detected=%" PRIu64 "\n", stage,
|
||||
static_cast<uint64_t>(embedded_fetch.loads.size()));
|
||||
}
|
||||
}
|
||||
@@ -826,12 +832,12 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
.input_info = options.input_info,
|
||||
.embedded_fetch = embedded_fetch.loads.empty() ? nullptr : &embedded_fetch,
|
||||
};
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " IR TranslateProgram\n", stage,
|
||||
options.shader_hash);
|
||||
auto ir = Frontend::TranslateProgram(decoded, cfg, translate_options);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
stage, options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
IR::RewriteToSsa(ir.blocks);
|
||||
const auto simplify = [&ir] {
|
||||
@@ -842,8 +848,9 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
simplify();
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0 || read_lane_stats.rewritten_reductions != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 " reductions=%" PRIu32 "\n",
|
||||
GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.rewritten_reductions);
|
||||
LOGF("ShaderRecompiler %s read-lane elimination: reads=%" PRIu32
|
||||
" reductions=%" PRIu32 "\n",
|
||||
stage, read_lane_stats.rewritten_reads, read_lane_stats.rewritten_reductions);
|
||||
}
|
||||
// Cleanup also exposes profitable folds by removing dead predicate consumers.
|
||||
simplify();
|
||||
@@ -857,10 +864,8 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
std::string cfg_dump;
|
||||
if (options.dump_ir) {
|
||||
cfg_dump = CFG::GraphToString(cfg);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR before resource tracking:\n%s", GetDumpLabel(options),
|
||||
MakeIrDump(cfg_dump, ir).c_str());
|
||||
}
|
||||
LOGF("ShaderRecompiler %s native IR before resource tracking:\n%s", stage,
|
||||
MakeIrDump(cfg_dump, ir).c_str());
|
||||
}
|
||||
IR::TrackResources(ir, decoded, native_cfg, options.input_info);
|
||||
TranslateResult result;
|
||||
@@ -877,6 +882,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
uint32_t push_data_start_dword) {
|
||||
const auto emit_begin = std::chrono::steady_clock::now();
|
||||
auto& ir = translated.program;
|
||||
const auto* stage = StageName(ir.stage);
|
||||
IR::ApplyResourceSpecialization(ir, specialization);
|
||||
// The resource plan owns host descriptor evaluation now. Keep only dependencies consumed
|
||||
// by GPU memory operations; bound descriptor dwords must not retain shader instructions.
|
||||
@@ -923,19 +929,16 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(translated.cfg_dump, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
LOGF("ShaderRecompiler %s native IR:\n%s", stage, ir_dump.c_str());
|
||||
}
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", stage,
|
||||
ir.shader_hash);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword, *options.profile,
|
||||
options.emit_debug_info);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
|
||||
static_cast<uint64_t>(spirv.size()),
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" wave_size=%u elapsed_ms=%" PRIu64 "\n",
|
||||
stage, ir.shader_hash, static_cast<uint64_t>(spirv.size()), ir.wave_size,
|
||||
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - emit_begin)
|
||||
.count()));
|
||||
|
||||
@@ -22,10 +22,8 @@ struct CompileOptions {
|
||||
uint32_t user_data_base = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
bool emit_debug_info = false;
|
||||
const Spirv::Profile* profile = &Spirv::DefaultProfile;
|
||||
const char* dump_label = nullptr;
|
||||
std::span<const uint32_t> user_data;
|
||||
std::span<const uint32_t> back_code;
|
||||
ShaderStageInputInfo input_info;
|
||||
@@ -64,11 +62,13 @@ struct ShaderSource {
|
||||
IR::ResourcePlan call_targets;
|
||||
std::vector<Call> calls;
|
||||
uint32_t unsupported_call_pc = UINT32_MAX;
|
||||
uint32_t wave_size = 64;
|
||||
std::optional<Linked> linked;
|
||||
uint64_t revision = 0;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
||||
};
|
||||
|
||||
[[nodiscard]] const char* StageName(ShaderType stage);
|
||||
[[nodiscard]] ShaderSource PrepareShaderSource(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtRuntime& runtime);
|
||||
|
||||
@@ -169,6 +169,9 @@ uint32_t Builder::AddString(const char* text) {
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
if (!m_emit_names) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(operands, name);
|
||||
AppendInstruction(m_debug, spv::OpName, operands);
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t AddString(const char* text);
|
||||
void EnableNames() { m_emit_names = true; }
|
||||
void AddName(uint32_t target, const char* name);
|
||||
template <typename... Args>
|
||||
void AddAnnotation(spv::Op opcode, const Args&... operands) {
|
||||
@@ -159,6 +160,7 @@ private:
|
||||
std::map<std::string, uint32_t> m_import_ids;
|
||||
std::map<std::vector<uint32_t>, uint32_t> m_declaration_ids;
|
||||
size_t m_unpatched_phi_incomings = 0;
|
||||
bool m_emit_names = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -161,6 +161,7 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
if (image.heap) continue;
|
||||
if (image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
@@ -226,6 +227,7 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
|
||||
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
|
||||
program.shader_hash);
|
||||
state.debug_source = state.builder.AddString(name);
|
||||
state.builder.EnableNames();
|
||||
}
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
struct Profile {
|
||||
bool maintenance8 = false; // Dynamic texel offsets on image samples.
|
||||
bool native_float_min_max = false; // NMin/NMax give the guest's NaN and signed-zero results.
|
||||
// OpReadClockKHR Device tick in picoseconds; 0 without shaderDeviceClock.
|
||||
uint32_t device_clock_period_ps = 0;
|
||||
};
|
||||
|
||||
inline constexpr Profile DefaultProfile {};
|
||||
|
||||
@@ -54,9 +54,30 @@ uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t value) {
|
||||
uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
// Clamping first keeps the truncating conversion defined; NaN and values past the largest
|
||||
// float below the integer limit are selected afterwards.
|
||||
const auto clamped =
|
||||
EmitExt(state, TypeF32(state), GLSLstd450NClamp,
|
||||
{src, ConstantF32(state, signed_value ? 0xcf000000u : 0u),
|
||||
ConstantF32(state, signed_value ? 0x4effffffu : 0x4f7fffffu)});
|
||||
const auto converted = signed_value
|
||||
? Unary(state, spv::OpBitcast, TypeU32(state),
|
||||
Unary(state, spv::OpConvertFToS, TypeI32(state), clamped))
|
||||
: Unary(state, spv::OpConvertFToU, TypeU32(state), clamped);
|
||||
const auto above = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, signed_value ? 0x4f000000u : 0x4f800000u));
|
||||
const auto high =
|
||||
Select(state, TypeU32(state), above,
|
||||
ConstantU32(state, signed_value ? 0x7fffffffu : 0xffffffffu), converted);
|
||||
return Select(state, TypeU32(state), Unary(state, spv::OpIsNan, TypeBool(state), src),
|
||||
ConstantU32(state, 0), high);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits) {
|
||||
// Native half conversion causes regressions; use compact integer RTZ conversion.
|
||||
const auto bits = EmitBitcastF32ToU32(state, value);
|
||||
const auto abs = EmitAndConstant(state, bits, 0x7fffffffu);
|
||||
const auto sign = EmitAndConstant(
|
||||
state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), bits, ConstantU32(state, 16)),
|
||||
@@ -92,28 +113,6 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t value) {
|
||||
return EmitOrU32(state, sign, magnitude);
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
// Clamping first keeps the truncating conversion defined; NaN and values past the largest
|
||||
// float below the integer limit are selected afterwards.
|
||||
const auto clamped =
|
||||
EmitExt(state, TypeF32(state), GLSLstd450NClamp,
|
||||
{src, ConstantF32(state, signed_value ? 0xcf000000u : 0u),
|
||||
ConstantF32(state, signed_value ? 0x4effffffu : 0x4f7fffffu)});
|
||||
const auto converted = signed_value
|
||||
? Unary(state, spv::OpBitcast, TypeU32(state),
|
||||
Unary(state, spv::OpConvertFToS, TypeI32(state), clamped))
|
||||
: Unary(state, spv::OpConvertFToU, TypeU32(state), clamped);
|
||||
const auto above = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, signed_value ? 0x4f000000u : 0x4f800000u));
|
||||
const auto high =
|
||||
Select(state, TypeU32(state), above,
|
||||
ConstantU32(state, signed_value ? 0x7fffffffu : 0xffffffffu), converted);
|
||||
return Select(state, TypeU32(state), Unary(state, spv::OpIsNan, TypeBool(state), src),
|
||||
ConstantU32(state, 0), high);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t EmitFPFma32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
const auto result = EmitGlsl<GLSLstd450Fma, IR::Type::F32>(state, a, b, c);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction);
|
||||
@@ -199,7 +198,8 @@ uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value
|
||||
|
||||
uint32_t EmitPackFloat2x16Rtz(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(
|
||||
state, EmitF32ToF16RtzBits(state, arg0), EmitF32ToF16RtzBits(state, arg1),
|
||||
state, EmitF32BitsToF16RtzBits(state, EmitBitcastF32ToU32(state, arg0)),
|
||||
EmitF32BitsToF16RtzBits(state, EmitBitcastF32ToU32(state, arg1)),
|
||||
ConstantU32(state, 16), ConstantU32(state, 16));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t 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) {
|
||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
||||
return state.per_vertex_variable;
|
||||
@@ -120,7 +127,14 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
} else {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
if (image.dimension == ImageDimension::Dim1D || image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(sampled == 1 ? spv::CapabilitySampled1D
|
||||
: spv::CapabilityImage1D);
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
if (sampled == 2 && info.multisampled != 0u) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageMultisample);
|
||||
}
|
||||
const auto scalar_type =
|
||||
image.atomic64 ? TypeU64(state) : ImageScalarType(state, image.numeric_class);
|
||||
return state.builder.Type(spv::OpTypeImage, scalar_type,
|
||||
@@ -144,14 +158,20 @@ void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id, spv::Capabil
|
||||
state.builder.RequireCapability(capability);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
|
||||
}
|
||||
|
||||
ImageDefinition& ImageDescriptorDefinition(EmitterState& state, const IR::ImageResource& image,
|
||||
IR::DescriptorBindingKind kind) {
|
||||
return (image.heap ? state.heap_images : state.images)[IR::ImageBindingIndex(kind)];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip, array_index);
|
||||
const auto image = state.builder.AllocateId();
|
||||
const auto kind = *IR::DescriptorBindingForImage(state.program.info.images[resource]);
|
||||
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
|
||||
const auto& resource_info = state.program.info.images[resource];
|
||||
const auto kind = *IR::DescriptorBindingForImage(resource_info);
|
||||
state.builder.AddFunction(spv::OpLoad, ImageDescriptorDefinition(state, resource_info, kind).type,
|
||||
image, pointer);
|
||||
if (array_index != 0u) {
|
||||
DecorateNonUniformDescriptor(state, image,
|
||||
@@ -184,13 +204,13 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto kind = *IR::DescriptorBindingForImage(image_resource);
|
||||
auto& sampled_type = state.images[IR::ImageBindingIndex(kind)].sampled_type;
|
||||
if (sampled_type == 0) {
|
||||
sampled_type = state.builder.Type(
|
||||
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
|
||||
auto& definition = ImageDescriptorDefinition(state, image_resource,
|
||||
*IR::DescriptorBindingForImage(image_resource));
|
||||
if (definition.sampled_type == 0) {
|
||||
definition.sampled_type = state.builder.Type(spv::OpTypeSampledImage, definition.type);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
state.builder.AddFunction(spv::OpSampledImage, definition.sampled_type, sampled_image, image,
|
||||
sampler_id);
|
||||
if (array_index != 0u || sampler_dynamic) {
|
||||
DecorateNonUniformDescriptor(state, sampled_image, spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
}
|
||||
@@ -202,26 +222,31 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(mip >= image.mip_count);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
EXIT_IF(!kind.has_value() || (image.heap && array_index == 0u));
|
||||
if (array_index == 0u) {
|
||||
array_index = ConstantU32(state, image.descriptor_index + mip);
|
||||
}
|
||||
const auto& definition = state.images[IR::ImageBindingIndex(*kind)];
|
||||
const auto& definition = ImageDescriptorDefinition(state, image, *kind);
|
||||
return DescriptorElementPointer(state, definition.pointer_type, definition.variable,
|
||||
array_index, *kind, resource,
|
||||
"image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index) {
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip, array_index),
|
||||
coord, texel);
|
||||
const auto descriptor = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
if (sample == 0u) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel);
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel,
|
||||
spv::ImageOperandsSampleMask, sample);
|
||||
}
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
EmitWrite(0);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <optional>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -61,7 +63,8 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
}
|
||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||
kind == IR::StageInputKind::InvocationId || kind == IR::StageInputKind::PrimitiveId ||
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId ||
|
||||
kind == IR::StageInputKind::BaseVertex || kind == IR::StageInputKind::BaseInstance) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeI32(state), value, variable);
|
||||
@@ -112,6 +115,18 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c
|
||||
return EmitInputComponentU32(state, kind, component);
|
||||
}
|
||||
|
||||
// Whether EXEC enables a source lane within the caller's 32-lane half.
|
||||
uint32_t EmitSourceLaneActive(ValueEmitContext& ctx, uint32_t exec, uint32_t lane) {
|
||||
auto& state = ctx.state;
|
||||
const auto scope = ConstantU32(state, spv::ScopeSubgroup);
|
||||
const auto ballot = EmitNative<spv::OpGroupNonUniformBallot, IR::Type::U32x4>(state, scope, exec);
|
||||
if (ctx.half != 0) {
|
||||
lane = EmitAndConstant(state, lane, 31);
|
||||
}
|
||||
return EmitNative<spv::OpGroupNonUniformBallotBitExtract, IR::Type::U1>(state, scope, ballot,
|
||||
lane);
|
||||
}
|
||||
|
||||
uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& flags,
|
||||
uint32_t exec) {
|
||||
auto& state = ctx.state;
|
||||
@@ -131,15 +146,98 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
|
||||
enable(flags.bank_mask, 2u);
|
||||
enable(flags.row_mask, 4u);
|
||||
// Only row shifts (0x101-0x10f, 0x111-0x11f) can vacate a lane; other controls always read one.
|
||||
const bool row_shift = !flags.dpp8 && flags.control > 0x100u && flags.control < 0x120u &&
|
||||
const bool row_shift = flags.control > 0x100u && flags.control < 0x120u &&
|
||||
flags.control != 0x110u;
|
||||
if (!flags.bound_control && row_shift) {
|
||||
if (flags.bound_control || (flags.fetch_inactive && !row_shift)) {
|
||||
return writable;
|
||||
}
|
||||
// Without BOUND_CTRL, a vacated source or, without FI, an EXEC-disabled one disables the write.
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
if (row_shift) {
|
||||
writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, target.valid);
|
||||
}
|
||||
if (!flags.fetch_inactive) {
|
||||
// Vacated lanes target outside their row; wrapping keeps the ballot index in range.
|
||||
auto source = target.lane;
|
||||
if (row_shift) {
|
||||
lane = lane != 0 ? lane : EmitSubgroupLocalInvocationId(state);
|
||||
source = EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(
|
||||
state, lane, source, ConstantU32(state, 0), ConstantU32(state, 4));
|
||||
}
|
||||
writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable,
|
||||
EmitDppTargetLane(state, flags).valid);
|
||||
EmitSourceLaneActive(ctx, exec, source));
|
||||
}
|
||||
return writable;
|
||||
}
|
||||
|
||||
uint32_t LoadVertexParameter(EmitterState& state, const InputBinding& input, uint32_t vertex,
|
||||
uint32_t chan) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
|
||||
input.variable_id, ConstantU32(state, vertex),
|
||||
ConstantU32(state, chan & 3u));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Interpolates a pixel input channel to f32. A half (0 low, 1 high) selects one f16 of a packed
|
||||
// FP16_INTERP_MODE dword before blending; smooth inputs read by halves are always per-vertex.
|
||||
uint32_t EmitPixelAttribute(EmitterState& state, const InputBinding& input, uint32_t attr,
|
||||
uint32_t chan, std::optional<uint32_t> half) {
|
||||
const auto select = [&](uint32_t value) {
|
||||
if (!half) {
|
||||
return value;
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
const auto unpacked = state.builder.AllocateId();
|
||||
const auto word = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 2), unpacked,
|
||||
GlslStd450(state), GLSLstd450UnpackHalf2x16, bits);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), word, unpacked, *half);
|
||||
return word;
|
||||
};
|
||||
if (!input.per_vertex) {
|
||||
const auto vector = state.builder.AllocateId();
|
||||
const auto component = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input.variable_id);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector,
|
||||
chan & 3u);
|
||||
return select(component);
|
||||
}
|
||||
if (PixelParameterIsFlat(state, attr)) {
|
||||
const auto vertex =
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
|
||||
return select(LoadVertexParameter(state, input, vertex, chan));
|
||||
}
|
||||
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
|
||||
? IR::StageInputKind::BaryCoordNoPerspective
|
||||
: IR::StageInputKind::BaryCoordSmooth;
|
||||
const auto barycentric = InputVariableForKind(state, barycentric_kind);
|
||||
uint32_t sum = 0;
|
||||
for (uint32_t vertex = 0; vertex < 3u; vertex++) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto weight = state.builder.AllocateId();
|
||||
const auto product = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, barycentric, ConstantU32(state, vertex));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer);
|
||||
state.builder.AddFunction(spv::OpFMul, TypeF32(state), product,
|
||||
select(LoadVertexParameter(state, input, vertex, chan)), weight);
|
||||
if (vertex == 0u) {
|
||||
sum = product;
|
||||
} else {
|
||||
const auto next = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product);
|
||||
sum = next;
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
|
||||
const auto* input = InputBindingForParameter(state, attr);
|
||||
if (input == nullptr || input->variable_id == 0) {
|
||||
@@ -148,57 +246,9 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
|
||||
if (state.program.stage == ShaderType::Vertex || state.program.stage == ShaderType::Local) {
|
||||
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
|
||||
}
|
||||
const auto load_per_vertex = [&](uint32_t vertex) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
|
||||
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
if (input->per_vertex) {
|
||||
if (PixelParameterIsFlat(state, attr)) {
|
||||
const auto value = load_per_vertex(
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u);
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
|
||||
? IR::StageInputKind::BaryCoordNoPerspective
|
||||
: IR::StageInputKind::BaryCoordSmooth;
|
||||
const auto barycentric = InputVariableForKind(state, barycentric_kind);
|
||||
uint32_t sum = 0;
|
||||
for (uint32_t vertex = 0; vertex < 3u; vertex++) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto weight = state.builder.AllocateId();
|
||||
const auto product = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain,
|
||||
TypePointer(state, spv::StorageClassInput, TypeF32(state)),
|
||||
pointer, barycentric, ConstantU32(state, vertex));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), weight, pointer);
|
||||
state.builder.AddFunction(spv::OpFMul, TypeF32(state), product, load_per_vertex(vertex),
|
||||
weight);
|
||||
if (vertex == 0u) {
|
||||
sum = product;
|
||||
} else {
|
||||
const auto next = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFAdd, TypeF32(state), next, sum, product);
|
||||
sum = next;
|
||||
}
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, sum);
|
||||
return bits;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
const auto component = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32Vector(state, 4), vector, input->variable_id);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), component, vector,
|
||||
chan & 3u);
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, component);
|
||||
const auto value = EmitPixelAttribute(state, *input, attr, chan, std::nullopt);
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
|
||||
return bits;
|
||||
}
|
||||
|
||||
@@ -209,25 +259,16 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
|
||||
if (!input->per_vertex) {
|
||||
return EmitAttribute(ctx.state, attr, chan);
|
||||
}
|
||||
const auto load_vertex = [&](uint32_t vertex) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeF32(state)), pointer,
|
||||
input->variable_id, ConstantU32(state, vertex), ConstantU32(state, chan & 3u));
|
||||
state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
|
||||
uint32_t selected_vertex = (mode + 1u) % 3u;
|
||||
if (mode == 2u && PixelParameterIsFlat(state, attr)) {
|
||||
selected_vertex =
|
||||
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
|
||||
}
|
||||
uint32_t value = load_vertex(selected_vertex);
|
||||
uint32_t value = LoadVertexParameter(state, *input, selected_vertex, chan);
|
||||
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
|
||||
const auto delta = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
|
||||
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value,
|
||||
LoadVertexParameter(state, *input, 0, chan));
|
||||
value = delta;
|
||||
}
|
||||
const auto bits = state.builder.AllocateId();
|
||||
@@ -251,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,
|
||||
bool uint_output) {
|
||||
auto& state = ctx.state;
|
||||
if (exp.compr && !uint_output) {
|
||||
uint32_t vector_type) {
|
||||
auto& state = ctx.state;
|
||||
const bool integer_output = vector_type != TypeF32Vector(state, 4);
|
||||
if (exp.compr && !integer_output) {
|
||||
auto unpack = GLSLstd450UnpackHalf2x16;
|
||||
switch (MrtOutputMode(state, exp)) {
|
||||
case 5: unpack = GLSLstd450UnpackUnorm2x16; break;
|
||||
@@ -290,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, uint_output ? 1u : 0x3f800000u),
|
||||
ConstantU32(state, integer_output ? 1u : 0x3f800000u),
|
||||
};
|
||||
if (exp.compr) {
|
||||
const auto extract = vector_type == TypeU32Vector(state, 4) ? spv::OpBitFieldUExtract
|
||||
: spv::OpBitFieldSExtract;
|
||||
for (uint32_t pair = 0; pair < 2u; pair++) {
|
||||
if ((exp.en & (3u << (pair * 2u))) == 0u) {
|
||||
continue;
|
||||
@@ -304,8 +348,8 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
continue;
|
||||
}
|
||||
raw[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), raw[component],
|
||||
packed, ConstantU32(state, lane * 16u),
|
||||
state.builder.AddFunction(extract, TypeU32(state), raw[component], packed,
|
||||
ConstantU32(state, lane * 16u),
|
||||
ConstantU32(state, 16));
|
||||
}
|
||||
}
|
||||
@@ -320,12 +364,10 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), data,
|
||||
raw[0], raw[1], raw[2], raw[3]);
|
||||
}
|
||||
if (uint_output) {
|
||||
if (vector_type == TypeU32Vector(state, 4)) {
|
||||
return data;
|
||||
}
|
||||
const auto vector = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitcast, TypeF32Vector(state, 4), vector, data);
|
||||
return vector;
|
||||
return Unary(state, spv::OpBitcast, vector_type, data);
|
||||
}
|
||||
|
||||
uint32_t DistancePointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
||||
@@ -523,11 +565,11 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (state.program.stage != ShaderType::Mesh && variable == 0) {
|
||||
return;
|
||||
}
|
||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
||||
const auto vector_type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto value = ExportVector(ctx, data, exp, uint_output);
|
||||
const auto vector_type = MrtOutputMode(state, exp) != 0u ? MrtOutputType(state, exp.index)
|
||||
: TypeF32Vector(state, 4);
|
||||
auto value = ExportVector(ctx, data, exp, vector_type);
|
||||
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) {
|
||||
// Broadcast logical alpha before swizzling the primary output.
|
||||
const auto blend_output =
|
||||
@@ -671,9 +713,16 @@ void EmitBarrier(EmitterState& state) {
|
||||
return;
|
||||
}
|
||||
const auto memory_scope = tessellation ? spv::ScopeInvocation : spv::ScopeWorkgroup;
|
||||
const auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
||||
? spv::MemorySemanticsUniformMemoryMask
|
||||
: spv::MemorySemanticsWorkgroupMemoryMask;
|
||||
auto memory = state.lds_storage_class == spv::StorageClassStorageBuffer
|
||||
? spv::MemorySemanticsUniformMemoryMask
|
||||
: 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
|
||||
: spv::MemorySemanticsAcquireReleaseMask | memory;
|
||||
state.builder.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup),
|
||||
@@ -771,6 +820,13 @@ uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value 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) {
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto lane = ctx.FirstLane(ballot);
|
||||
@@ -781,18 +837,28 @@ uint32_t EmitReadLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
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 value = ctx.Arg(inst, 0);
|
||||
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();
|
||||
state.builder.AddFunction(spv::OpGroupNonUniformUMin, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce, value);
|
||||
state.builder.AddFunction(Reduce, TypeU32(state), result,
|
||||
ConstantU32(state, spv::ScopeSubgroup), spv::GroupOperationReduce,
|
||||
value);
|
||||
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) {
|
||||
auto& state = ctx.state;
|
||||
const auto hit = state.builder.AllocateId();
|
||||
@@ -803,27 +869,23 @@ uint32_t EmitWriteLane(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
|
||||
uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto flags = inst.Flags<IR::PermlaneFlags>();
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto row = state.builder.AllocateId();
|
||||
const auto row_value = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto lane8 = state.builder.AllocateId();
|
||||
const auto shift = state.builder.AllocateId();
|
||||
const auto upper = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto shifted = state.builder.AllocateId();
|
||||
const auto index = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
auto& state = ctx.state;
|
||||
const auto flags = inst.Flags<IR::PermlaneFlags>();
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto row = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto lane8 = state.builder.AllocateId();
|
||||
const auto shift = state.builder.AllocateId();
|
||||
const auto upper = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto shifted = state.builder.AllocateId();
|
||||
const auto index = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), row, subid,
|
||||
ConstantU32(state, 0xfffffff0u));
|
||||
if (flags.x16) {
|
||||
state.builder.AddFunction(spv::OpBitwiseXor, TypeU32(state), row_value, row,
|
||||
ConstantU32(state, 16));
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpCopyObject, TypeU32(state), row_value, row);
|
||||
}
|
||||
const auto row_value =
|
||||
flags.x16 ? Binary(state, spv::OpBitwiseXor, TypeU32(state), row, ConstantU32(state, 16))
|
||||
: row;
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 15));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane8, lane,
|
||||
@@ -839,20 +901,23 @@ uint32_t EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ConstantU32(state, 15));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, row_value, index);
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target);
|
||||
uint32_t result = shuffled;
|
||||
if (!flags.fetch_inactive) {
|
||||
const auto source_exec = ctx.Shuffle(inst, 3, target);
|
||||
result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpSelect, TypeU32(state), result, source_exec, shuffled,
|
||||
ConstantU32(state, 0));
|
||||
if (flags.fetch_inactive) {
|
||||
return shuffled;
|
||||
}
|
||||
return result;
|
||||
return Select(state, TypeU32(state), EmitSourceLaneActive(ctx, ctx.Arg(inst, 3), target),
|
||||
shuffled, ctx.Arg(inst, 4));
|
||||
}
|
||||
|
||||
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitAttribute(ctx.state, inst.Arg(0).U32(), inst.Arg(1).U32());
|
||||
}
|
||||
|
||||
uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto attr = inst.Arg(0).U32();
|
||||
return EmitPixelAttribute(ctx.state, *InputBindingForParameter(ctx.state, attr), attr,
|
||||
inst.Arg(1).U32(), inst.Arg(2).U32());
|
||||
}
|
||||
|
||||
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return EmitInterpolationParameter(ctx, inst.Arg(0).U32(), inst.Arg(1).U32(), inst.Arg(2).U32());
|
||||
}
|
||||
@@ -863,6 +928,22 @@ uint32_t EmitGetShaderBase(ValueEmitContext& ctx) {
|
||||
return ctx.Def(IR::Value(uint64_t {0}));
|
||||
}
|
||||
|
||||
uint32_t EmitReadRealtimeClock(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto period = state.profile.device_clock_period_ps;
|
||||
if (period == 0u) {
|
||||
ctx.Fail(inst, "requires shaderDeviceClock");
|
||||
}
|
||||
state.builder.RequireExtension("SPV_KHR_shader_clock");
|
||||
state.builder.RequireCapability(spv::CapabilityShaderClockKHR);
|
||||
const auto clock =
|
||||
Unary(state, spv::OpReadClockKHR, TypeU64(state), ConstantU32(state, spv::ScopeDevice));
|
||||
// S_MEMREALTIME counts at 100 MHz. Shift the host clock by the most bits that keep its tick at
|
||||
// most 15000 ps (2/3 of 100 MHz): 1 GHz shifts by 3 to 125 MHz; 100 MHz and slower stay.
|
||||
const uint32_t shift = std::bit_width(7500u / period);
|
||||
return EmitShiftRightLogical64(ctx, clock, IR::Value(shift));
|
||||
}
|
||||
|
||||
void EmitUnreachable(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Fail(inst, "must be lowered before SPIR-V emission");
|
||||
}
|
||||
|
||||
@@ -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 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;
|
||||
if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0);
|
||||
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 component) {
|
||||
const auto value = AddressU32(ctx, mem, address, component);
|
||||
return Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component).bit_width == 16u
|
||||
? EmitF16BitsToF32(ctx.state, value)
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
||||
const auto value = AddressU32(ctx, mem, address, component);
|
||||
const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags,
|
||||
mem.image_dimension, component);
|
||||
return layout.bit_width == 16u ? EmitF16BitsToF32(ctx.state, value)
|
||||
: Unary(ctx.state, spv::OpBitcast, TypeF32(ctx.state), value);
|
||||
}
|
||||
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
@@ -77,6 +79,7 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
layout.coord = cursor;
|
||||
cursor += info.coordinate_components;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLodClamp)) layout.clamp = cursor++;
|
||||
return layout;
|
||||
}
|
||||
|
||||
@@ -389,6 +392,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
||||
const auto format = image.conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
if (IR::ReadsDepthBits(image)) return info;
|
||||
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
|
||||
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
|
||||
info.component_count > 4u);
|
||||
@@ -424,6 +428,7 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
|
||||
const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class;
|
||||
const auto scalar_type = ImageScalarType(ctx.state, numeric_class);
|
||||
const bool integer = info.packed_bitfield || info.type == Format::ComponentType::Uint;
|
||||
uint32_t packed = 0;
|
||||
if (info.packed_bitfield) {
|
||||
packed = ctx.state.builder.AllocateId();
|
||||
@@ -441,8 +446,16 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
|
||||
components[component], texel, component);
|
||||
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
|
||||
const auto unorm_max = static_cast<float>((1u << info.component_bits[component]) - 1u);
|
||||
components[component] = Binary(ctx.state, spv::OpFMul, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 255.0f));
|
||||
ConstantF32Value(ctx.state, unorm_max));
|
||||
if (integer) {
|
||||
// Depth bits: round back to the stored UNorm code.
|
||||
components[component] =
|
||||
Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
|
||||
Binary(ctx.state, spv::OpFAdd, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 0.5f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t component = info.component_count; component < 4u; component++) {
|
||||
@@ -454,20 +467,19 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
const auto selector = (swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = info.packed_bitfield
|
||||
selected[component] = integer
|
||||
? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One))
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
selected[component] = components[selector - 4u];
|
||||
} else {
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 0u)
|
||||
: ZeroF32(ctx.state);
|
||||
selected[component] = integer ? ConstantU32(ctx.state, 0u) : ZeroF32(ctx.state);
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
|
||||
selected[1], selected[2], selected[3]);
|
||||
integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4),
|
||||
result, selected[0], selected[1], selected[2], selected[3]);
|
||||
return info.type == Format::ComponentType::Unorm
|
||||
? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result;
|
||||
}
|
||||
@@ -691,6 +703,21 @@ auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t resul
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
}
|
||||
if (image.heap) {
|
||||
const auto entry = EmitImageHeapEntry(state, key, image.indirect_mapping_offset);
|
||||
const auto kinds = static_cast<uint32_t>(image.indirect_resources.size()) - 1u;
|
||||
// One kind leaves every slot word a bare element.
|
||||
if (kinds == 1u) return emit(image.indirect_resources[1], entry);
|
||||
const auto element = Binary(state, spv::OpBitwiseAnd, TypeU32(state), entry,
|
||||
ConstantU32(state, (1u << IR::ImageHeapKindShift) - 1u));
|
||||
const auto EmitKind = [&](uint32_t kind) {
|
||||
return emit(image.indirect_resources[kind + 1u], element);
|
||||
};
|
||||
return EmitIndexSwitch(state,
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), entry,
|
||||
ConstantU32(state, IR::ImageHeapKindShift)),
|
||||
kinds, result_type, EmitKind);
|
||||
}
|
||||
const auto selected = EmitIndirectResourceIndex(
|
||||
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
|
||||
const auto SameType = [&](uint32_t lhs, uint32_t rhs) {
|
||||
@@ -786,6 +813,25 @@ uint32_t EmitImageResult(ValueEmitContext& ctx, const IR::Inst& inst, const Emit
|
||||
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 {
|
||||
uint32_t id = 0;
|
||||
bool dynamic = false;
|
||||
@@ -821,9 +867,7 @@ std::array<ImageSampler, 2> LoadImageSamplers(ValueEmitContext& ctx, const IR::I
|
||||
selected = EmitIndirectResourceIndex(state, selected,
|
||||
samplers[index].indirect_mapping_offset,
|
||||
samplers[index].indirect_search_iterations, 0u);
|
||||
array_index = EmitIndexSwitch(
|
||||
state, selected, static_cast<uint32_t>(candidates.size()),
|
||||
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, candidates[ordinal]); });
|
||||
array_index = SamplerArrayIndex(state, selected, candidates);
|
||||
}
|
||||
sampler = {LoadSamplerDescriptor(state, index, array_index), array_index != 0u};
|
||||
};
|
||||
@@ -870,14 +914,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto dimension = image.dimension;
|
||||
const auto sampled = MakeSampledImage(state, mem.resource,
|
||||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
|
||||
image.cube));
|
||||
const auto sampler = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto lod = EmitImageAccess(ctx, inst, TypeF32Vector(state, 2),
|
||||
[&](uint32_t resource, uint32_t array_index) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto query = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), query,
|
||||
MakeSampledImage(state, resource, sampler, 0u, array_index),
|
||||
CoordF32(ctx, mem, *address, 0,
|
||||
ImageDimensionInfoFor(candidate.dimension).spatial_components,
|
||||
candidate.cube));
|
||||
return query;
|
||||
});
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
@@ -932,9 +981,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto mip_lod = candidate.mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, mem.image_dimension) : 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, candidate.dimension);
|
||||
const auto& dimension_info = ImageDimensionInfoFor(candidate.dimension);
|
||||
const auto sample = dimension_info.multisampled != 0u
|
||||
? AddressU32(ctx, mem, *address, dimension_info.coordinate_components) : 0u;
|
||||
const auto texel = StoreTexel(ctx, selected, ctx.Arg(inst, 2),
|
||||
candidate.numeric_class == Prospero::TextureNumericClass::Uint);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index, sample);
|
||||
});
|
||||
});
|
||||
return;
|
||||
@@ -1037,7 +1089,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
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;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
operands[0] = spv::ImageOperandsGradMask;
|
||||
@@ -1049,6 +1106,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||
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[1] = lod;
|
||||
operand_count = 2;
|
||||
@@ -1072,6 +1134,13 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
sample_operands[sample_operand_count++] = PackedOffset(
|
||||
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 =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
|
||||
@@ -56,6 +56,7 @@ EMIT_NATIVE(BitFieldSExtract, OpBitFieldSExtract, U32, uint32_t, uint32_t, uint3
|
||||
EMIT_NATIVE(SelectU1, OpSelect, U1, uint32_t, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(SelectU32, OpSelect, U32, uint32_t, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(SelectF32, OpSelect, F32, uint32_t, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(SelectF64, OpSelect, F64, uint32_t, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(ISub32, OpISub, U32, uint32_t, uint32_t)
|
||||
EMIT_NATIVE(IMul32, OpIMul, U32, uint32_t, uint32_t)
|
||||
@@ -229,16 +230,20 @@ uint32_t EmitDppUpdateU32(ValueEmitContext& ctx, const IR::Inst& in
|
||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value);
|
||||
uint32_t EmitLaneId(EmitterState& state);
|
||||
uint32_t EmitBallot(ValueEmitContext& ctx, IR::Value predicate);
|
||||
uint32_t EmitLiveMask(EmitterState& state);
|
||||
uint32_t EmitConditionRef(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 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 EmitPermlane16U32(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetInterpolationParameter(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t EmitGetShaderBase(ValueEmitContext& ctx);
|
||||
uint32_t EmitReadRealtimeClock(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
inline constexpr auto EmitGetSrtResource = EmitVoid;
|
||||
inline constexpr auto EmitGetBufferResource = EmitGetSrtResource;
|
||||
uint32_t EmitGetBufferResourceIndex(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
@@ -286,6 +291,7 @@ inline constexpr auto EmitBufferAtomicIAdd32 = EmitAtomic32;
|
||||
inline constexpr auto EmitBufferAtomicISub32 = EmitAtomic32;
|
||||
inline constexpr auto EmitBufferAtomicSMin32 = EmitAtomic32;
|
||||
inline constexpr auto EmitBufferAtomicUMin32 = EmitAtomic32;
|
||||
inline constexpr auto EmitBufferAtomicUMin64 = EmitBufferAtomic64;
|
||||
inline constexpr auto EmitBufferAtomicSMax32 = EmitAtomic32;
|
||||
inline constexpr auto EmitBufferAtomicUMax32 = EmitAtomic32;
|
||||
inline constexpr auto EmitBufferAtomicAnd32 = EmitAtomic32;
|
||||
|
||||
@@ -112,6 +112,7 @@ struct EmitterState {
|
||||
uint32_t lane_half = 0;
|
||||
std::array<BufferDefinition, 4> storage_buffers {};
|
||||
std::vector<uint32_t> memory_byte_offsets;
|
||||
std::vector<uint32_t> memory_byte_sizes;
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
@@ -130,6 +131,7 @@ struct EmitterState {
|
||||
uint32_t lds_u64_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> images {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> heap_images {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t sampler_type = 0;
|
||||
uint32_t sampler_pointer_type = 0;
|
||||
@@ -144,6 +146,7 @@ struct EmitterState {
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t pixel_valid_mask_variable = 0;
|
||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||
uint32_t helper_invocation_variable = 0;
|
||||
uint32_t per_vertex_variable = 0;
|
||||
uint32_t point_size_variable = 0;
|
||||
uint32_t clip_distance_variable = 0;
|
||||
@@ -281,6 +284,7 @@ struct ImageSampleLayout {
|
||||
uint32_t bias = NoImageComponent;
|
||||
uint32_t coord = 0;
|
||||
uint32_t lod = NoImageComponent;
|
||||
uint32_t clamp = NoImageComponent;
|
||||
uint32_t grad_x = NoImageComponent;
|
||||
uint32_t grad_y = NoImageComponent;
|
||||
};
|
||||
@@ -304,6 +308,7 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
|
||||
|
||||
|
||||
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);
|
||||
|
||||
@@ -339,8 +344,9 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
uint32_t mip = 0, uint32_t array_index = 0,
|
||||
bool sampler_dynamic = false);
|
||||
|
||||
// sample is 0 (no id) for single-sampled images.
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index);
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample);
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage);
|
||||
|
||||
@@ -412,7 +418,7 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
||||
|
||||
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state);
|
||||
void EmitMemoryBindings(EmitterState& state);
|
||||
|
||||
uint32_t LdsDwordCount(const EmitterState& state);
|
||||
void EnsureLdsStorage(EmitterState& state);
|
||||
@@ -430,6 +436,8 @@ struct MemoryResourceAccess {
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource);
|
||||
// Slot word of an image heap byte offset; offsets past the heap select the null element.
|
||||
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset);
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t array_index = 0u);
|
||||
|
||||
@@ -527,6 +535,7 @@ inline constexpr auto EmitFNegateValue = EmitNative<spv::OpFNegate, IR::Type::F3
|
||||
inline constexpr auto EmitFAbsValue = EmitGlsl<GLSLstd450FAbs, IR::Type::F32, uint32_t>;
|
||||
|
||||
uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
|
||||
uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
|
||||
|
||||
void EmitProgram(EmitterState& state);
|
||||
|
||||
|
||||
@@ -1541,16 +1541,16 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto index =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto low = state.builder.AllocateId();
|
||||
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);
|
||||
// Elect and count from the guest EXEC words, which V_MBCNT also ranks lanes by.
|
||||
const auto low = ctx.Arg(inst, 1);
|
||||
const auto high = ctx.Arg(inst, 2);
|
||||
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
||||
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 =
|
||||
state.lane_count == 2
|
||||
? 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(
|
||||
state, is_first,
|
||||
AndCondition(state,
|
||||
state.lane_count == 2 ? Binary(state, spv::OpINotEqual, TypeBool(state), count,
|
||||
ConstantU32(state, 0))
|
||||
: exec,
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), count, ConstantU32(state, 0)),
|
||||
bounds));
|
||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||
const auto value = state.builder.AllocateId();
|
||||
|
||||
@@ -38,19 +38,39 @@ uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t LoadFlattenedSrt(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.flattened_srt_variable, ConstantU32(state, 0), index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset) {
|
||||
const auto Mapping = [&](uint32_t word) {
|
||||
return LoadFlattenedSrt(state, ConstantU32(state, mapping_offset + word));
|
||||
};
|
||||
// Keys past the table select its trailing null slot.
|
||||
const auto index = EmitUMin32(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
|
||||
ConstantU32(state, 2u)),
|
||||
Mapping(0u));
|
||||
const auto offset = Unary(state, spv::OpUConvert, TypeU64(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index,
|
||||
ConstantU32(state, 2u)));
|
||||
const auto pointer = Unary(state, spv::OpConvertUToPtr, TypePhysicalU32Pointer(state),
|
||||
Binary(state, spv::OpIAdd, TypeU64(state),
|
||||
PackU64(state, Mapping(1u), Mapping(2u)), offset));
|
||||
const auto entry = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), entry, pointer,
|
||||
spv::MemoryAccessAlignedMask, 4u);
|
||||
return entry;
|
||||
}
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource) {
|
||||
const auto LoadMapping = [&](uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
const auto mapping = ConstantU32(state, mapping_offset);
|
||||
const auto count = LoadMapping(mapping);
|
||||
const auto count = LoadFlattenedSrt(state, mapping);
|
||||
if (search_iterations == 0u) {
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
|
||||
ConstantU32(state, 2u));
|
||||
@@ -58,7 +78,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
const auto safe = Select(state, TypeU32(state), valid, index, ConstantU32(state, 0u));
|
||||
const auto entry = Binary(state, spv::OpIAdd, TypeU32(state), safe,
|
||||
ConstantU32(state, mapping_offset + 1u));
|
||||
return Select(state, TypeU32(state), valid, LoadMapping(entry),
|
||||
return Select(state, TypeU32(state), valid, LoadFlattenedSrt(state, entry),
|
||||
ConstantU32(state, default_resource));
|
||||
}
|
||||
const auto entry_at = [&](uint32_t index) {
|
||||
@@ -76,7 +96,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
ConstantU32(state, 1u));
|
||||
const auto probe = Select(state, TypeU32(state), active, mid, ConstantU32(state, 0u));
|
||||
const auto less = Binary(state, spv::OpULessThan, TypeBool(state),
|
||||
LoadMapping(entry_at(probe)), key);
|
||||
LoadFlattenedSrt(state, entry_at(probe)), key);
|
||||
low = Select(state, TypeU32(state),
|
||||
Binary(state, spv::OpLogicalAnd, TypeBool(state), active, less),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low);
|
||||
@@ -87,9 +107,10 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
const auto in_range = Binary(state, spv::OpULessThan, TypeBool(state), low, count);
|
||||
const auto entry = entry_at(Select(state, TypeU32(state), in_range, low, ConstantU32(state, 0u)));
|
||||
const auto match = Binary(state, spv::OpLogicalAnd, TypeBool(state), in_range,
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), LoadMapping(entry), key));
|
||||
const auto resource = LoadMapping(Binary(state, spv::OpIAdd, TypeU32(state), entry,
|
||||
ConstantU32(state, 1u)));
|
||||
Binary(state, spv::OpIEqual, TypeBool(state),
|
||||
LoadFlattenedSrt(state, entry), key));
|
||||
const auto resource = LoadFlattenedSrt(
|
||||
state, Binary(state, spv::OpIAdd, TypeU32(state), entry, ConstantU32(state, 1u)));
|
||||
return Select(state, TypeU32(state), match, resource, ConstantU32(state, default_resource));
|
||||
}
|
||||
|
||||
@@ -133,9 +154,10 @@ uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resour
|
||||
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)];
|
||||
state.memory_byte_offsets.resize(count);
|
||||
state.memory_byte_sizes.resize(count);
|
||||
std::vector<bool> dynamic_offsets(count);
|
||||
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
||||
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
||||
@@ -153,6 +175,8 @@ void EmitMemoryOffsets(EmitterState& state) {
|
||||
if (dynamic_offsets[i + lane]) continue;
|
||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
||||
state.memory_byte_sizes[i + lane] =
|
||||
EmitShaderDataDwordLoad(state, state.program.bindings.MemorySizeDword() + i + lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,22 +260,28 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
||||
const auto offsets_start = state.program.bindings.memory_offset_dword +
|
||||
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
|
||||
const auto& bindings = state.program.bindings;
|
||||
const auto data_start = bindings.UsesPushData() ? bindings.push_data_start_dword : 0u;
|
||||
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))));
|
||||
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
||||
ConstantU32(state, 3));
|
||||
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, shift, ConstantU32(state, 8));
|
||||
access.length = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||
state, ConstantU32(state, bindings.MemorySizeDword() + data_start), array_index));
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -451,6 +481,17 @@ uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::Buffer
|
||||
UnormRounding rounding) {
|
||||
EXIT_IF(rounding != UnormRounding::RoundEven && info.type != Format::ComponentType::Unorm);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.type == Format::ComponentType::Float && bits < 16u) {
|
||||
// Truncate to f16, whose bias the unsigned 11/10-bit floats share, and drop mantissa
|
||||
// bits; negatives and NaN, whose f16 bits lie above +inf, store zero.
|
||||
const auto half = data_bits == 16u ? EmitAndConstant(state, data, 0xffffu)
|
||||
: EmitF32BitsToF16RtzBits(state, data);
|
||||
return Select(state, TypeU32(state),
|
||||
EmitCompareU32Constant(state, spv::OpUGreaterThan, half, 0x7c00u),
|
||||
ConstantU32(state, 0),
|
||||
EmitBinaryU32(state, spv::OpShiftRightLogical, half,
|
||||
ConstantU32(state, 15u - bits)));
|
||||
}
|
||||
if (data_bits == 16u) {
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
@@ -544,6 +585,7 @@ spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::ImageAtomicUMin32:
|
||||
case IR::ValueOpcode::ImageAtomicUMin64:
|
||||
case IR::ValueOpcode::BufferAtomicUMin32:
|
||||
case IR::ValueOpcode::BufferAtomicUMin64:
|
||||
case IR::ValueOpcode::SharedAtomicUMin32: return spv::OpAtomicUMin;
|
||||
case IR::ValueOpcode::ImageAtomicSMax32:
|
||||
case IR::ValueOpcode::BufferAtomicSMax32:
|
||||
|
||||
@@ -187,7 +187,7 @@ void EmitMeshEntryPoint(EmitterState& state) {
|
||||
vertex[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpBitFieldUExtract, TypeU32(state), vertex[component], packed,
|
||||
ConstantU32(state, component * 10u), ConstantU32(state, 10));
|
||||
ConstantU32(state, component * 10u), ConstantU32(state, 9));
|
||||
}
|
||||
const auto triangle = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
|
||||
|
||||
@@ -206,6 +206,17 @@ uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
return state.builder.Type(spv::OpTypeArray, TypeF32(state), ConstantU32(state, count));
|
||||
}
|
||||
|
||||
void DefineImageArray(EmitterState& state, ImageDefinition& definition, const IR::ImageResource& image,
|
||||
uint32_t count, uint32_t set, uint32_t binding) {
|
||||
definition.type = ImageType(state, image);
|
||||
const auto array = state.builder.Type(spv::OpTypeArray, definition.type, ConstantU32(state, count));
|
||||
definition.variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, spv::StorageClassUniformConstant, array), spv::StorageClassUniformConstant);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationDescriptorSet, set);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationBinding, binding);
|
||||
definition.pointer_type = TypePointer(state, spv::StorageClassUniformConstant, definition.type);
|
||||
}
|
||||
|
||||
void DefineDescriptors(EmitterState& state) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
auto& info = state.program.info;
|
||||
@@ -222,6 +233,14 @@ void DefineDescriptors(EmitterState& state) {
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
EXIT("shader image has an invalid descriptor type or mip count");
|
||||
}
|
||||
if (image.heap) {
|
||||
// Heap kinds alias one array; the root itself is never accessed.
|
||||
auto& definition = state.heap_images[IR::ImageBindingIndex(*kind)];
|
||||
if (image.indirect_resources.empty() && definition.variable == 0) {
|
||||
DefineImageArray(state, definition, image, IR::ImageHeapCapacity, IR::ImageHeapSet, 0u);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto& count = counts[static_cast<size_t>(*kind)];
|
||||
image.descriptor_index = count;
|
||||
if (count == 0) image_types[IR::ImageBindingIndex(*kind)] = ℑ
|
||||
@@ -331,21 +350,11 @@ void DefineDescriptors(EmitterState& state) {
|
||||
default: {
|
||||
EXIT_IF(IR::ImageBindingResourceClass(kind) ==
|
||||
IR::ImageResourceClass::None);
|
||||
const auto& image = *image_types[IR::ImageBindingIndex(kind)];
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(kind));
|
||||
auto& definition = state.images[IR::ImageBindingIndex(kind)];
|
||||
definition.type = ImageType(state, image);
|
||||
definition.variable = Define(ArrayType(definition.type), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
definition.pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, definition.type);
|
||||
if (image.dimension == ImageDimension::Dim1D ||
|
||||
image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(image.resource_class ==
|
||||
IR::ImageResourceClass::Sampled
|
||||
? spv::CapabilitySampled1D
|
||||
: spv::CapabilityImage1D);
|
||||
}
|
||||
DefineImageArray(state, definition, *image_types[IR::ImageBindingIndex(kind)],
|
||||
counts[index], 0u, IR::NativeBinding(state.program.stage, kind));
|
||||
state.builder.AddName(definition.variable,
|
||||
("image_" + std::to_string(static_cast<uint32_t>(kind))).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -467,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
||||
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
||||
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
||||
case IR::StageInputKind::BaseVertex: return spv::BuiltInBaseVertex;
|
||||
case IR::StageInputKind::BaseInstance: return spv::BuiltInBaseInstance;
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
@@ -517,7 +528,9 @@ void DefineInputs(EmitterState& state) {
|
||||
case IR::StageInputKind::PrimitiveId:
|
||||
case IR::StageInputKind::InstanceIndex:
|
||||
case IR::StageInputKind::Layer:
|
||||
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::SampleId:
|
||||
case IR::StageInputKind::BaseVertex:
|
||||
case IR::StageInputKind::BaseInstance: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::WorkgroupId:
|
||||
case IR::StageInputKind::NumWorkgroups:
|
||||
case IR::StageInputKind::LocalInvocationId:
|
||||
@@ -586,6 +599,13 @@ void DefineInputs(EmitterState& state) {
|
||||
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) {
|
||||
@@ -682,13 +702,15 @@ void DefineOutputs(EmitterState& state) {
|
||||
break;
|
||||
case IR::StageOutputKind::Parameter:
|
||||
case IR::StageOutputKind::Mrt: {
|
||||
const bool uint_output =
|
||||
binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel &&
|
||||
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
|
||||
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
|
||||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
|
||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto type = TypeF32Vector(state, 4);
|
||||
if (binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel && binding.index < 8u) {
|
||||
// Packed UNorm stores its encoded bits.
|
||||
const auto bit = 1u << binding.index;
|
||||
type = (state.input_info.pixel->target_packed_unorm_mask & bit) != 0u
|
||||
? TypeU32Vector(state, 4)
|
||||
: MrtOutputType(state, binding.index);
|
||||
}
|
||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||
binding.debug_name.c_str());
|
||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
||||
@@ -752,7 +774,7 @@ void DefineModule(EmitterState& state) {
|
||||
if (state.program.info.buffer_u16) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer16BitAccess);
|
||||
}
|
||||
if (state.program.info.uses_dma) {
|
||||
if (state.program.info.uses_dma || state.program.info.uses_image_heap) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
@@ -784,6 +806,10 @@ void DefineModule(EmitterState& state) {
|
||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilitySampleRateShading);
|
||||
}
|
||||
if (InputVariableForKind(state, IR::StageInputKind::BaseVertex) != 0 ||
|
||||
InputVariableForKind(state, IR::StageInputKind::BaseInstance) != 0) {
|
||||
state.builder.RequireCapability(spv::CapabilityDrawParameters);
|
||||
}
|
||||
if (state.program.info.image_gather_extended) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageGatherExtended);
|
||||
}
|
||||
@@ -820,7 +846,7 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
|
||||
}
|
||||
state.builder.RequireExtension("SPV_KHR_float_controls");
|
||||
state.builder.AddMemoryModel(state.program.info.uses_dma
|
||||
state.builder.AddMemoryModel(state.program.info.uses_dma || state.program.info.uses_image_heap
|
||||
? spv::AddressingModelPhysicalStorageBuffer64
|
||||
: spv::AddressingModelLogical,
|
||||
spv::MemoryModelGLSL450);
|
||||
|
||||
@@ -235,9 +235,9 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (ctx.half != 0 &&
|
||||
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
||||
op == Op::ReadConstBuffer ||
|
||||
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
||||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
|
||||
// Whole-wave results and SMEM loads (SGPR operands, no EXEC) are identical 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));
|
||||
return;
|
||||
}
|
||||
@@ -746,7 +746,7 @@ void EmitProgram(EmitterState& state) {
|
||||
state.lds_base_dwords = EmitBinaryU32(state, spv::OpIMul, index,
|
||||
ConstantU32(state, LdsDwordCount(state)));
|
||||
}
|
||||
EmitMemoryOffsets(state);
|
||||
EmitMemoryBindings(state);
|
||||
if (program.blocks.empty()) {
|
||||
EmitReturn(ctx);
|
||||
} else if (state.program.dispatcher_fallback) {
|
||||
|
||||
@@ -1710,21 +1710,25 @@ Graph Structurize(const Graph& graph) {
|
||||
return GotoStructurizer(graph).Run();
|
||||
}
|
||||
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) {
|
||||
// Vector comparisons and carry results can write a scalar pair in wave64.
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size) {
|
||||
// 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 ||
|
||||
inst.family == Decoder::Family::VOPC || inst.family == Decoder::Family::VOP3 ||
|
||||
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;
|
||||
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 InstructionWritesScalarCode(inst, code);
|
||||
}
|
||||
|
||||
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());
|
||||
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
||||
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;) {
|
||||
const auto& inst = program.instructions[--i];
|
||||
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);
|
||||
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) {
|
||||
EXIT_IF(definition && *definition != index);
|
||||
|
||||
@@ -113,9 +113,10 @@ struct Graph {
|
||||
};
|
||||
|
||||
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 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.
|
||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||
Graph Structurize(const Graph& graph);
|
||||
|
||||
@@ -14,12 +14,6 @@ struct MimgSampleInfo {
|
||||
uint32_t flags = 0;
|
||||
};
|
||||
|
||||
struct MimgGatherInfo {
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::UNSUPPORTED;
|
||||
uint32_t flags = 0;
|
||||
};
|
||||
|
||||
struct MimgAtomicInfo {
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::UNSUPPORTED;
|
||||
@@ -73,7 +67,7 @@ constexpr uint32_t ImageSampleAddressComponents(uint32_t flags, ImageDimension d
|
||||
if ((flags & ImageSampleFlagBias) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagLod) != 0) {
|
||||
if ((flags & (ImageSampleFlagLod | ImageSampleFlagLodClamp)) != 0) {
|
||||
components++;
|
||||
}
|
||||
if ((flags & ImageSampleFlagDerivative) != 0) {
|
||||
@@ -181,23 +175,72 @@ constexpr MimgSampleInfo MIMG_SAMPLE_OPCODE_LIST[] = {
|
||||
ImageSampleFlagOffset | ImageSampleFlagAdjust},
|
||||
};
|
||||
|
||||
constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = {
|
||||
{0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod},
|
||||
{0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero},
|
||||
{0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare},
|
||||
{0x4cu, Opcode::IMAGE_GATHER4_C_L, ImageSampleFlagCompare | ImageSampleFlagLod},
|
||||
{0x4fu, Opcode::IMAGE_GATHER4_C_LZ,
|
||||
ImageSampleFlagCompare | ImageSampleFlagLevelZero},
|
||||
{0x57u, Opcode::IMAGE_GATHER4_LZ_O,
|
||||
ImageSampleFlagLevelZero | ImageSampleFlagOffset},
|
||||
{0x58u, Opcode::IMAGE_GATHER4_C_O,
|
||||
ImageSampleFlagCompare | ImageSampleFlagOffset},
|
||||
{0x5fu, Opcode::IMAGE_GATHER4_C_LZ_O,
|
||||
constexpr MimgSampleInfo MIMG_GATHER_OPCODE_LIST[] = {
|
||||
{0x40u, "image_gather4", 0},
|
||||
{0x41u, "image_gather4_cl", ImageSampleFlagLodClamp},
|
||||
{0x44u, "image_gather4_l", ImageSampleFlagLod},
|
||||
{0x45u, "image_gather4_b", ImageSampleFlagBias},
|
||||
{0x46u, "image_gather4_b_cl", ImageSampleFlagBias | ImageSampleFlagLodClamp},
|
||||
{0x47u, "image_gather4_lz", ImageSampleFlagLevelZero},
|
||||
{0x48u, "image_gather4_c", ImageSampleFlagCompare},
|
||||
{0x49u, "image_gather4_c_cl", ImageSampleFlagCompare | ImageSampleFlagLodClamp},
|
||||
{0x4cu, "image_gather4_c_l", ImageSampleFlagCompare | ImageSampleFlagLod},
|
||||
{0x4du, "image_gather4_c_b", ImageSampleFlagCompare | ImageSampleFlagBias},
|
||||
{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},
|
||||
{0x61u, Opcode::IMAGE_GATHER4H, ImageSampleFlagGatherHorizontal},
|
||||
{0xc8u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare | ImageSampleFlagAdjust},
|
||||
{0xd8u, Opcode::IMAGE_GATHER4_C_O,
|
||||
{0x61u, "image_gather4h", ImageSampleFlagGatherHorizontal},
|
||||
{0xc0u, "image_gather4_a", ImageSampleFlagAdjust},
|
||||
{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},
|
||||
{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[] = {
|
||||
@@ -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_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) {
|
||||
if (sample != nullptr) {
|
||||
return Opcode::IMAGE_SAMPLE;
|
||||
}
|
||||
if (gather != nullptr) {
|
||||
return gather->decoded;
|
||||
return Opcode::IMAGE_GATHER4;
|
||||
}
|
||||
if (atomic != nullptr) {
|
||||
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,
|
||||
const MimgSampleInfo* sample, const MimgGatherInfo* gather,
|
||||
const MimgAtomicInfo* atomic) {
|
||||
if (sample != nullptr) {
|
||||
return ImageSampleAddressComponents(sample->flags, dimension);
|
||||
}
|
||||
if (gather != nullptr) {
|
||||
return ImageSampleAddressComponents(gather->flags, dimension);
|
||||
const MimgSampleInfo* sampled, const MimgAtomicInfo* atomic) {
|
||||
if (sampled != nullptr) {
|
||||
return ImageSampleAddressComponents(sampled->flags, dimension);
|
||||
}
|
||||
if (atomic != nullptr) {
|
||||
return ImageCoordComponents(dimension);
|
||||
@@ -290,33 +319,31 @@ uint32_t CountDmaskComponents(uint32_t dmask) {
|
||||
|
||||
} // namespace
|
||||
|
||||
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component) {
|
||||
const auto width = [flags](uint32_t index) {
|
||||
if ((flags & ImageSampleFlagA16) == 0u) return 32u;
|
||||
uint32_t cursor = 0;
|
||||
if ((flags & ImageSampleFlagOffset) != 0u) {
|
||||
if (index == cursor++) return 32u;
|
||||
}
|
||||
if ((flags & ImageSampleFlagBias) != 0u) {
|
||||
if (index == cursor++) return 16u;
|
||||
}
|
||||
if ((flags & ImageSampleFlagCompare) != 0u && index == cursor) return 32u;
|
||||
return 16u;
|
||||
};
|
||||
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;
|
||||
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, ImageDimension dimension,
|
||||
uint32_t component) {
|
||||
if ((flags & ImageSampleFlagA16) == 0u) return {component * 32u, 32u};
|
||||
// A16 offsets, bias and PCF reference each take a whole VGPR, the bias as a half in its low
|
||||
// 16 bits. The other components pack two per VGPR, except that each 1D derivative and the last
|
||||
// two 3D ones take the low half of their own VGPR.
|
||||
const bool has_bias = (flags & ImageSampleFlagBias) != 0u;
|
||||
const uint32_t bias_index = (flags & ImageSampleFlagOffset) != 0u ? 1u : 0u;
|
||||
const uint32_t whole_vgprs = std::popcount(
|
||||
flags & (ImageSampleFlagOffset | ImageSampleFlagBias | ImageSampleFlagCompare));
|
||||
if (component < whole_vgprs) {
|
||||
return {component * 32u, has_bias && component == bias_index ? 16u : 32u};
|
||||
}
|
||||
const auto bits = width(component);
|
||||
if (bits == 32u) offset = (offset + 31u) & ~31u;
|
||||
return {offset, bits};
|
||||
const uint32_t axes =
|
||||
(flags & ImageSampleFlagDerivative) != 0u ? ImageGradientComponents(dimension) : 0u;
|
||||
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;
|
||||
const auto last = ImageAddressComponentLayout(flags, components - 1u);
|
||||
const auto last = ImageAddressComponentLayout(flags, dimension, components - 1u);
|
||||
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 uint32_t word_count = 2u + nsa_dwords;
|
||||
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t ssamp = (word1 >> 21u) & 0x1fu;
|
||||
const bool r128 = ((word0 >> 15u) & 0x1u) != 0u;
|
||||
const bool a16 = ((word1 >> 30u) & 0x1u) != 0u;
|
||||
const bool d16 = ((word1 >> 31u) & 0x1u) != 0u;
|
||||
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
||||
const auto* gather = Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
|
||||
const auto* atomic = Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode);
|
||||
const uint32_t vdata = (word1 >> 8u) & 0xffu;
|
||||
const uint32_t vaddr = word1 & 0xffu;
|
||||
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
|
||||
const uint32_t ssamp = (word1 >> 21u) & 0x1fu;
|
||||
const bool r128 = ((word0 >> 15u) & 0x1u) != 0u;
|
||||
const bool a16 = ((word1 >> 30u) & 0x1u) != 0u;
|
||||
const bool d16 = ((word1 >> 31u) & 0x1u) != 0u;
|
||||
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
||||
const auto* gather = Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
|
||||
const auto* atomic = Detail::FindOpcode(MIMG_ATOMIC_OPS, opcode);
|
||||
const auto* sampled = sample != nullptr ? sample : gather;
|
||||
|
||||
inst.pc = pc;
|
||||
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.glc = ((word0 >> 13u) & 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) {
|
||||
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_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++) {
|
||||
inst.image_nsa_addr[i] = (code[word_index + 2u + i / 4u] >> ((i % 4u) * 8u)) & 0xffu;
|
||||
}
|
||||
inst.image_address_components =
|
||||
DecodeMimgAddressComponents(opcode, dimension, sample, gather, atomic);
|
||||
inst.image_address_components = DecodeMimgAddressComponents(opcode, dimension, sampled, atomic);
|
||||
SetRawWords(inst, code, word_index, word_count);
|
||||
|
||||
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 ||
|
||||
opcode == 0x01u || opcode == 0x08u || opcode == 0x09u;
|
||||
const bool supports_d16 = sampled != nullptr || opcode == 0x00u || opcode == 0x01u ||
|
||||
opcode == 0x08u || opcode == 0x09u;
|
||||
if (d16 && !supports_d16) {
|
||||
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;
|
||||
}
|
||||
|
||||
const char* MimgSampleOpcodeName(uint32_t opcode) {
|
||||
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
||||
return sample != nullptr ? sample->name : nullptr;
|
||||
const char* MimgSampledOpcodeName(uint32_t opcode) {
|
||||
const auto* sample = Detail::FindOpcode(MIMG_SAMPLE_OPS, opcode);
|
||||
const auto* sampled = sample != nullptr ? sample : Detail::FindOpcode(MIMG_GATHER_OPS, opcode);
|
||||
return sampled != nullptr ? sampled->name : nullptr;
|
||||
}
|
||||
|
||||
} // 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,
|
||||
Instruction& inst);
|
||||
|
||||
const char* MimgSampleOpcodeName(uint32_t opcode);
|
||||
const char* MimgSampledOpcodeName(uint32_t opcode);
|
||||
|
||||
struct ImageAddressComponent {
|
||||
uint32_t bit_offset;
|
||||
uint32_t bit_width;
|
||||
};
|
||||
|
||||
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, uint32_t component);
|
||||
uint32_t ImageAddressDwordCount(uint32_t flags, uint32_t components);
|
||||
ImageAddressComponent ImageAddressComponentLayout(uint32_t flags, ImageDimension dimension,
|
||||
uint32_t component);
|
||||
uint32_t ImageAddressDwordCount(uint32_t flags, ImageDimension dimension, uint32_t components);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ namespace Libs::Graphics::ShaderRecompiler::Decoder {
|
||||
namespace {
|
||||
|
||||
struct MemoryOpcodeInfo {
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::UNSUPPORTED;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
uint32_t encoding = 0;
|
||||
Opcode decoded = Opcode::UNSUPPORTED;
|
||||
uint32_t data_dwords = 1;
|
||||
uint32_t data_bits = 32;
|
||||
bool data_signed = false;
|
||||
bool typed = false;
|
||||
bool formatted = false;
|
||||
uint32_t data_components = 0; // D16 formats pack two components per dword
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
|
||||
@@ -21,7 +22,7 @@ constexpr MemoryOpcodeInfo SMEM_OPCODE_LIST[] = {
|
||||
{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},
|
||||
{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[] = {
|
||||
@@ -68,14 +69,19 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
|
||||
{0x40u, Opcode::BUFFER_ATOMIC_FMAX, 1, 32},
|
||||
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_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},
|
||||
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
|
||||
{0x71u, Opcode::BUFFER_GL0_INV, 0, 0},
|
||||
{0x72u, Opcode::BUFFER_GL1_INV, 0, 0},
|
||||
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true},
|
||||
{0x81u, Opcode::BUFFER_LOAD_FORMAT_D16_XY, 1, 16, false, false, true, 2},
|
||||
{0x82u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZ, 2, 16, false, false, true, 3},
|
||||
{0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true, 4},
|
||||
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
|
||||
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true},
|
||||
{0x85u, Opcode::BUFFER_STORE_FORMAT_D16_XY, 1, 16, false, false, true, 2},
|
||||
{0x86u, Opcode::BUFFER_STORE_FORMAT_D16_XYZ, 2, 16, false, false, true, 3},
|
||||
{0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true, 4},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
@@ -90,14 +96,14 @@ constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo FLAT_OPCODE_LIST[] = {
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{0x1eu, Opcode::FLAT_STORE_DWORDX4, 4, 32}, {0x1fu, Opcode::FLAT_STORE_DWORDX3, 3, 32},
|
||||
{0x24u, Opcode::FLAT_LOAD_SHORT_D16, 1, 16},
|
||||
{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},
|
||||
{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},
|
||||
{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},
|
||||
{0x1eu, Opcode::FLAT_STORE_DWORDX4, 4, 32}, {0x1fu, Opcode::FLAT_STORE_DWORDX3, 3, 32},
|
||||
{0x24u, Opcode::FLAT_LOAD_SHORT_D16, 1, 16}, {0x25u, Opcode::FLAT_LOAD_SHORT_D16_HI, 1, 16},
|
||||
};
|
||||
|
||||
constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
|
||||
@@ -158,12 +164,13 @@ void ApplyMemoryInfo(Instruction& inst, const MemoryOpcodeInfo* info) {
|
||||
inst.opcode = Opcode::UNSUPPORTED;
|
||||
return;
|
||||
}
|
||||
inst.opcode = info->decoded;
|
||||
inst.data_dwords = info->data_dwords;
|
||||
inst.data_bits = info->data_bits;
|
||||
inst.data_signed = info->data_signed;
|
||||
inst.typed = info->typed;
|
||||
inst.formatted = info->formatted;
|
||||
inst.opcode = info->decoded;
|
||||
inst.data_dwords = info->data_dwords;
|
||||
inst.data_bits = info->data_bits;
|
||||
inst.data_signed = info->data_signed;
|
||||
inst.typed = info->typed;
|
||||
inst.formatted = info->formatted;
|
||||
inst.data_components = info->data_components;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -279,6 +286,9 @@ void DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
if (inst.opcode == Opcode::UNSUPPORTED) {
|
||||
SetUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
|
||||
}
|
||||
if (inst.opcode == Opcode::S_DCACHE_INV) {
|
||||
return;
|
||||
}
|
||||
|
||||
DecodeScalarDestination(sdst, pc, inst.dst);
|
||||
if (inst.opcode == Opcode::S_MEMREALTIME) {
|
||||
@@ -323,12 +333,7 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
|
||||
}
|
||||
|
||||
DecodeVectorGpr(vdata, inst.dst);
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_XYZW ||
|
||||
inst.opcode == Opcode::BUFFER_STORE_FORMAT_D16_XYZW) {
|
||||
inst.data_components = 4u;
|
||||
}
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
|
||||
if (inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16 ||
|
||||
inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI) {
|
||||
inst.dst.sdwa_sel = inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI ? 5u : 4u;
|
||||
@@ -418,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);
|
||||
if (inst.opcode == Opcode::FLAT_LOAD_SHORT_D16) {
|
||||
// D16 loads reuse partial destinations to preserve the untouched high half.
|
||||
inst.dst.sdwa_sel = 4u;
|
||||
if (inst.opcode == Opcode::FLAT_LOAD_SHORT_D16 ||
|
||||
inst.opcode == Opcode::FLAT_LOAD_SHORT_D16_HI) {
|
||||
// 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);
|
||||
inst.src_count = 1;
|
||||
|
||||
@@ -57,6 +57,8 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
|
||||
{0x14u, Opcode::S_FF1_I32_B64},
|
||||
{0x15u, Opcode::S_FLBIT_I32_B32},
|
||||
{0x16u, Opcode::S_FLBIT_I32_B64},
|
||||
{0x17u, Opcode::S_FLBIT_I32},
|
||||
{0x18u, Opcode::S_FLBIT_I32_I64},
|
||||
{0x19u, Opcode::S_SEXT_I32_I8},
|
||||
{0x1au, Opcode::S_SEXT_I32_I16},
|
||||
{0x1bu, Opcode::S_BITSET0_B32},
|
||||
|
||||
@@ -123,10 +123,9 @@ std::string ImageSampleFlagsToString(uint32_t flags) {
|
||||
}
|
||||
|
||||
std::string FormatMimg(const Instruction& inst) {
|
||||
const char* sample_name =
|
||||
inst.opcode == Opcode::IMAGE_SAMPLE ? MimgSampleOpcodeName(inst.opcode_id) : nullptr;
|
||||
const char* sampled_name = MimgSampledOpcodeName(inst.opcode_id);
|
||||
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 =
|
||||
fmt::format("0x{:08x}: {} {}, {}, {}, {} ; dmask=0x{:x} image_dim={}", inst.pc, name,
|
||||
OperandToString(inst.dst).c_str(), OperandToString(inst.src0).c_str(),
|
||||
@@ -138,22 +137,10 @@ std::string FormatMimg(const Instruction& inst) {
|
||||
if (inst.image_r128) {
|
||||
text += " r128=1";
|
||||
}
|
||||
switch (inst.opcode) {
|
||||
case Opcode::IMAGE_SAMPLE:
|
||||
case Opcode::IMAGE_GATHER4_L:
|
||||
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:
|
||||
text += fmt::format(" sample_flags={} addr_components={}",
|
||||
ImageSampleFlagsToString(inst.image_sample_flags).c_str(),
|
||||
inst.image_address_components);
|
||||
break;
|
||||
default: break;
|
||||
if (sampled_name != nullptr) {
|
||||
text += fmt::format(" sample_flags={} addr_components={}",
|
||||
ImageSampleFlagsToString(inst.image_sample_flags).c_str(),
|
||||
inst.image_address_components);
|
||||
}
|
||||
if (inst.image_nsa_dwords != 0) {
|
||||
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 127u: operand.kind = OperandKind::ExecHi; return;
|
||||
case 235u: operand.kind = OperandKind::SharedBase; return;
|
||||
case 236u: operand.kind = OperandKind::SharedLimit; return;
|
||||
case 237u: operand.kind = OperandKind::PrivateBase; return;
|
||||
case 238u: operand.kind = OperandKind::PrivateLimit; return;
|
||||
case 239u: operand.kind = OperandKind::PopsExitingWaveId; return;
|
||||
case 248u:
|
||||
operand.kind = OperandKind::FloatInlineConstant;
|
||||
@@ -459,7 +448,9 @@ std::string OperandToString(const Operand& operand) {
|
||||
case OperandKind::M0: text = "m0"; break;
|
||||
case OperandKind::PopsExitingWaveId: text = "pops_exiting_wave_id"; break;
|
||||
case OperandKind::SharedBase: text = "shared_base"; break;
|
||||
case OperandKind::SharedLimit: text = "shared_limit"; break;
|
||||
case OperandKind::PrivateBase: text = "private_base"; break;
|
||||
case OperandKind::PrivateLimit: text = "private_limit"; break;
|
||||
case OperandKind::Null: text = "null"; break;
|
||||
default: text = "unknown"; break;
|
||||
}
|
||||
@@ -566,6 +557,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
OperandToString(inst.src0).c_str()));
|
||||
case Opcode::S_BARRIER:
|
||||
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: s_barrier", inst.pc));
|
||||
case Opcode::S_DCACHE_INV:
|
||||
case Opcode::BUFFER_GL0_INV:
|
||||
case Opcode::BUFFER_GL1_INV:
|
||||
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_GET_RESINFO:
|
||||
case Opcode::IMAGE_GET_LOD:
|
||||
case Opcode::IMAGE_GATHER4_L:
|
||||
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::IMAGE_GATHER4: return WithUnsupportedReason(inst, FormatMimg(inst));
|
||||
case Opcode::S_LOAD_DWORD:
|
||||
case Opcode::S_LOAD_DWORDX2:
|
||||
case Opcode::S_LOAD_DWORDX4:
|
||||
@@ -636,6 +620,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XY:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZ:
|
||||
case Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_XY:
|
||||
@@ -643,6 +629,8 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XY:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XYZ:
|
||||
case Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
|
||||
case Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Opcode::BUFFER_LOAD_UBYTE_D16:
|
||||
@@ -677,6 +665,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::BUFFER_ATOMIC_SUB:
|
||||
case Opcode::BUFFER_ATOMIC_SMIN:
|
||||
case Opcode::BUFFER_ATOMIC_UMIN:
|
||||
case Opcode::BUFFER_ATOMIC_UMIN_X2:
|
||||
case Opcode::BUFFER_ATOMIC_SMAX:
|
||||
case Opcode::BUFFER_ATOMIC_UMAX:
|
||||
case Opcode::BUFFER_ATOMIC_AND:
|
||||
@@ -693,6 +682,7 @@ std::string InstructionToString(const Instruction& inst) {
|
||||
case Opcode::FLAT_LOAD_USHORT:
|
||||
case Opcode::FLAT_LOAD_SSHORT:
|
||||
case Opcode::FLAT_LOAD_SHORT_D16:
|
||||
case Opcode::FLAT_LOAD_SHORT_D16_HI:
|
||||
case Opcode::FLAT_LOAD_DWORD:
|
||||
case Opcode::FLAT_LOAD_DWORDX2:
|
||||
case Opcode::FLAT_LOAD_DWORDX3:
|
||||
|
||||
@@ -60,6 +60,8 @@ enum class Opcode {
|
||||
S_FF1_I32_B64,
|
||||
S_FLBIT_I32_B32,
|
||||
S_FLBIT_I32_B64,
|
||||
S_FLBIT_I32,
|
||||
S_FLBIT_I32_I64,
|
||||
S_BITREPLICATE_B64_B32,
|
||||
S_QUADMASK_B64,
|
||||
S_GETPC_B64,
|
||||
@@ -542,11 +544,14 @@ enum class Opcode {
|
||||
S_BUFFER_LOAD_DWORDX8,
|
||||
S_BUFFER_LOAD_DWORDX16,
|
||||
S_MEMREALTIME,
|
||||
S_DCACHE_INV,
|
||||
BUFFER_LOAD_FORMAT_X,
|
||||
BUFFER_LOAD_FORMAT_XY,
|
||||
BUFFER_LOAD_FORMAT_XYZ,
|
||||
BUFFER_LOAD_FORMAT_XYZW,
|
||||
BUFFER_LOAD_FORMAT_D16_X,
|
||||
BUFFER_LOAD_FORMAT_D16_XY,
|
||||
BUFFER_LOAD_FORMAT_D16_XYZ,
|
||||
BUFFER_LOAD_FORMAT_D16_XYZW,
|
||||
BUFFER_STORE_FORMAT_X,
|
||||
BUFFER_STORE_FORMAT_XY,
|
||||
@@ -554,6 +559,8 @@ enum class Opcode {
|
||||
BUFFER_STORE_FORMAT_XYZW,
|
||||
BUFFER_STORE_FORMAT_D16_X,
|
||||
BUFFER_STORE_FORMAT_D16_HI_X,
|
||||
BUFFER_STORE_FORMAT_D16_XY,
|
||||
BUFFER_STORE_FORMAT_D16_XYZ,
|
||||
BUFFER_STORE_FORMAT_D16_XYZW,
|
||||
BUFFER_LOAD_UBYTE,
|
||||
BUFFER_LOAD_UBYTE_D16,
|
||||
@@ -590,6 +597,7 @@ enum class Opcode {
|
||||
BUFFER_ATOMIC_SUB,
|
||||
BUFFER_ATOMIC_SMIN,
|
||||
BUFFER_ATOMIC_UMIN,
|
||||
BUFFER_ATOMIC_UMIN_X2,
|
||||
BUFFER_ATOMIC_SMAX,
|
||||
BUFFER_ATOMIC_UMAX,
|
||||
BUFFER_ATOMIC_AND,
|
||||
@@ -606,6 +614,7 @@ enum class Opcode {
|
||||
FLAT_LOAD_USHORT,
|
||||
FLAT_LOAD_SSHORT,
|
||||
FLAT_LOAD_SHORT_D16,
|
||||
FLAT_LOAD_SHORT_D16_HI,
|
||||
FLAT_LOAD_DWORD,
|
||||
FLAT_LOAD_DWORDX2,
|
||||
FLAT_LOAD_DWORDX3,
|
||||
@@ -701,18 +710,12 @@ enum class Opcode {
|
||||
IMAGE_ATOMIC_FMIN,
|
||||
IMAGE_ATOMIC_FMAX,
|
||||
IMAGE_SAMPLE,
|
||||
IMAGE_GATHER4_L,
|
||||
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,
|
||||
IMAGE_GATHER4,
|
||||
V_INTERP_P1_F32,
|
||||
V_INTERP_P2_F32,
|
||||
V_INTERP_MOV_F32,
|
||||
V_INTERP_P1LL_F16,
|
||||
V_INTERP_P2_F16,
|
||||
|
||||
S_NOP,
|
||||
S_WAITCNT,
|
||||
@@ -758,7 +761,9 @@ enum class OperandKind {
|
||||
M0,
|
||||
PopsExitingWaveId,
|
||||
SharedBase,
|
||||
SharedLimit,
|
||||
PrivateBase,
|
||||
PrivateLimit,
|
||||
Null,
|
||||
Vgpr,
|
||||
};
|
||||
|
||||
@@ -391,6 +391,8 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x377u, Opcode::V_PERMLANE16_B32},
|
||||
{0x378u, Opcode::V_PERMLANEX16_B32},
|
||||
{0x34bu, Opcode::V_FMA_F16},
|
||||
{0x342u, Opcode::V_INTERP_P1LL_F16},
|
||||
{0x35au, Opcode::V_INTERP_P2_F16},
|
||||
{0x36du, Opcode::V_ADD3_U32},
|
||||
{0x14fu, Opcode::V_ALIGNBYTE_B32},
|
||||
};
|
||||
@@ -1553,6 +1555,9 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
|
||||
}
|
||||
switch (opcode) {
|
||||
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
|
||||
case Opcode::V_INTERP_P1LL_F16:
|
||||
case Opcode::V_INTERP_P2_F16:
|
||||
return abs != 0u || (op_sel & 0x7u) != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
||||
case Opcode::V_CNDMASK_B32:
|
||||
return (abs & ~0x3u) != 0u || op_sel != 0u || clamp != 0u || omod != 0u ||
|
||||
(neg & ~0x3u) != 0u;
|
||||
@@ -1664,6 +1669,17 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
}
|
||||
}
|
||||
|
||||
// VINTRP and VOP3 interpolation share one layout: src0 = I/J VGPR, src1 = attr, src2 = chan.
|
||||
void SetInterpolationAttribute(Instruction& inst, uint32_t attr, uint32_t chan) {
|
||||
inst.src1.kind = OperandKind::IntegerInlineConstant;
|
||||
inst.src1.value = attr;
|
||||
inst.src1.signed_val = static_cast<int32_t>(attr);
|
||||
inst.src2.kind = OperandKind::IntegerInlineConstant;
|
||||
inst.src2.value = chan;
|
||||
inst.src2.signed_val = static_cast<int32_t>(chan);
|
||||
inst.src_count = 3;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
|
||||
@@ -1830,6 +1846,15 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
inst.src_count = 0;
|
||||
return;
|
||||
}
|
||||
if (inst.opcode == Opcode::V_INTERP_P1LL_F16 || inst.opcode == Opcode::V_INTERP_P2_F16) {
|
||||
// SRC0 packs {attr_word, attr_chan, attr}; attr_word selects the high f16 of the dword.
|
||||
DecodeVectorGpr(vdst, inst.dst);
|
||||
DecodeScalarSource(src1, pc, inst.src0);
|
||||
SetInterpolationAttribute(inst, src0 & 0x3fu, (src0 >> 6u) & 0x3u);
|
||||
inst.src1.op_sel = (src0 & 0x100u) != 0u;
|
||||
inst.dst.sdwa_sel = (op_sel & 0x8u) != 0u ? 5u : 4u;
|
||||
return;
|
||||
}
|
||||
if (compare_exec) {
|
||||
inst.dst.kind = OperandKind::ExecLo;
|
||||
} else if (scalar_dst) {
|
||||
@@ -2004,13 +2029,7 @@ void DecodeVintrp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_ind
|
||||
} else {
|
||||
DecodeVectorGpr(vsrc, inst.src0);
|
||||
}
|
||||
inst.src1.kind = OperandKind::IntegerInlineConstant;
|
||||
inst.src1.value = attr;
|
||||
inst.src1.signed_val = static_cast<int32_t>(attr);
|
||||
inst.src2.kind = OperandKind::IntegerInlineConstant;
|
||||
inst.src2.value = chan;
|
||||
inst.src2.signed_val = static_cast<int32_t>(chan);
|
||||
inst.src_count = 3;
|
||||
SetInterpolationAttribute(inst, attr, chan);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
|
||||
@@ -73,14 +73,19 @@ void Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::V_INTERP_P1_F32() {}
|
||||
|
||||
void Translator::V_INTERP_P2_F32(const Decoder::Instruction& inst) {
|
||||
const auto value = ir.Emit(IR::ValueOpcode::GetAttribute,
|
||||
{IR::Value(inst.src1.value), IR::Value(inst.src2.value)});
|
||||
WriteOperand(inst.dst, value);
|
||||
}
|
||||
|
||||
void Translator::V_INTERP_P2_F16(const Decoder::Instruction& inst) {
|
||||
const auto value = ir.Emit(IR::ValueOpcode::GetAttributeF16,
|
||||
{IR::Value(inst.src1.value), IR::Value(inst.src2.value),
|
||||
IR::Value(uint32_t {inst.src1.op_sel})});
|
||||
WriteF16(inst.dst, IR::F32(value));
|
||||
}
|
||||
|
||||
void Translator::V_INTERP_MOV_F32(const Decoder::Instruction& inst) {
|
||||
if (inst.src0.value >= 3u) {
|
||||
EXIT("v_interp_mov_f32 mode %u is reserved at pc 0x%08x", inst.src0.value, inst.pc);
|
||||
@@ -108,8 +113,11 @@ void Translator::EXP(const Decoder::Instruction& inst) {
|
||||
|
||||
void Translator::EmitInterpolation(const Decoder::Instruction& inst) {
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::V_INTERP_P1_F32: V_INTERP_P1_F32(); return;
|
||||
// P2 interpolates the whole attribute, so the P1 halves have nothing to compute.
|
||||
case Decoder::Opcode::V_INTERP_P1_F32:
|
||||
case Decoder::Opcode::V_INTERP_P1LL_F16: return;
|
||||
case Decoder::Opcode::V_INTERP_P2_F32: V_INTERP_P2_F32(inst); return;
|
||||
case Decoder::Opcode::V_INTERP_P2_F16: V_INTERP_P2_F16(inst); return;
|
||||
case Decoder::Opcode::V_INTERP_MOV_F32: V_INTERP_MOV_F32(inst); return;
|
||||
default: return FailMissingTranslation(inst);
|
||||
}
|
||||
|
||||
@@ -5,30 +5,20 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar,
|
||||
bool cmpx) {
|
||||
void Translator::EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar) {
|
||||
if (scalar) {
|
||||
ir.SetScc(value);
|
||||
return;
|
||||
}
|
||||
const auto masked = ir.LogicalAnd(ir.GetExec(), value);
|
||||
if (cmpx) {
|
||||
const auto mask = BallotMask(masked);
|
||||
ir.SetExec(masked);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
WriteMask(inst.dst, masked);
|
||||
WriteVectorMask(inst.dst, value);
|
||||
}
|
||||
|
||||
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar,
|
||||
bool cmpx) {
|
||||
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar, cmpx);
|
||||
void Translator::EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar) {
|
||||
EmitCompareResult(inst, IR::U1(IR::Value(value)), scalar);
|
||||
}
|
||||
|
||||
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.
|
||||
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
|
||||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
|
||||
@@ -49,18 +39,18 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
|
||||
if (swap_operands) {
|
||||
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,
|
||||
bool signed_value, bool cmpx) {
|
||||
bool signed_value) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, 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,
|
||||
bool half, bool cmpx) {
|
||||
bool half) {
|
||||
const auto type = IR::ArgTypeOf(opcode, 0);
|
||||
const auto lhs =
|
||||
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);
|
||||
const IR::FPCompareFlags flags{
|
||||
.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) {
|
||||
const auto type = is64 ? IR::Type::F64 : IR::Type::F32;
|
||||
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 unordered = ir.LogicalOr(IR::U1(ir.Emit(is_nan_op, {lhs})),
|
||||
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))
|
||||
: ReadOperand(inst.src0, IR::Type::F32);
|
||||
const auto mask = half ? IR::Value(Read16LaneBits(inst.src1, false))
|
||||
: ReadOperand(inst.src1, IR::Type::U32);
|
||||
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
|
||||
@@ -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,
|
||||
bool negate_exec, bool negate_source, bool write_64) {
|
||||
if (!write_64) {
|
||||
// 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 combine = [&](IR::U32 old, IR::U32 src) {
|
||||
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
|
||||
IR::U32 result;
|
||||
switch (operation) {
|
||||
case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break;
|
||||
case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break;
|
||||
default: EXIT("unsupported SAVEEXEC operation");
|
||||
}
|
||||
return operation == IR::ValueOpcode::LogicalAnd ? ir.BitwiseAnd(lhs, rhs)
|
||||
: ir.BitwiseOr(lhs, rhs);
|
||||
};
|
||||
if (!write_64) {
|
||||
// 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(ConditionOperand(Decoder::OperandKind::ExecLo), result);
|
||||
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
|
||||
return;
|
||||
}
|
||||
const auto old = ir.GetExec();
|
||||
const auto src = ReadMask(inst.src0);
|
||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
|
||||
WriteMask(inst.dst, old, true);
|
||||
const auto mask = BallotMask(result);
|
||||
ir.SetExec(result);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
// EXEC words combine bitwise; the per-lane EXEC combines the per-lane masks, so helper lanes
|
||||
// stay enabled.
|
||||
const auto old = ir.GetExec();
|
||||
const auto src = ReadMask(inst.src0);
|
||||
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
|
||||
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
|
||||
const auto src_mask = ReadU32Pair(inst.src0);
|
||||
const std::array old_mask {ir.GetExecLo(), ir.GetExecHi()};
|
||||
const std::array mask {combine(old_mask[0], src_mask[0]), combine(old_mask[1], src_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))));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ void Translator::ADD_U32(const Decoder::Instruction& inst, bool vector, bool use
|
||||
}
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (vector) {
|
||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), carry));
|
||||
WriteVectorMask(inst.dst2, carry);
|
||||
} else {
|
||||
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);
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (vector) {
|
||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
||||
WriteVectorMask(inst.dst2, borrow);
|
||||
} else {
|
||||
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);
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
if (vector) {
|
||||
WriteMask(inst.dst2, ir.LogicalAnd(ir.GetExec(), borrow));
|
||||
WriteVectorMask(inst.dst2, borrow);
|
||||
} else {
|
||||
ir.SetScc(borrow);
|
||||
}
|
||||
@@ -465,9 +465,13 @@ void Translator::V_PERMLANE16_B32(const Decoder::Instruction& inst, bool x16) {
|
||||
.fetch_inactive = inst.dst.op_sel,
|
||||
.bound_control = inst.dst.op_sel_hi,
|
||||
};
|
||||
const auto result =
|
||||
ir.Emit(IR::ValueOpcode::Permlane16U32,
|
||||
{ReadU32(inst.src0), ReadU32(inst.src1), ReadU32(inst.src2), ir.GetExec()}, flags);
|
||||
// An EXEC-disabled source lane without FI disables the write unless BOUND_CTRL reads zero.
|
||||
const auto inactive = flags.fetch_inactive || flags.bound_control
|
||||
? IR::U32(IR::Value(0u))
|
||||
: ir.GetVectorReg(static_cast<IR::VectorReg>(inst.dst.reg));
|
||||
const auto result = ir.Emit(
|
||||
IR::ValueOpcode::Permlane16U32,
|
||||
{ReadU32(inst.src0), ReadU32(inst.src1), ReadU32(inst.src2), ir.GetExec(), inactive}, flags);
|
||||
auto dst = DestinationOperand(inst);
|
||||
dst.op_sel = false;
|
||||
dst.op_sel_hi = false;
|
||||
|
||||
@@ -49,18 +49,11 @@ void Translator::V_CVT_F16_16(const Decoder::Instruction& inst, bool signed_valu
|
||||
}
|
||||
|
||||
void Translator::V_CVT_16_F16(const Decoder::Instruction& inst, bool signed_value) {
|
||||
const auto value = ReadF16AsF32(inst.src0);
|
||||
// The 32-bit conversion already saturates and maps NaN to zero.
|
||||
if (signed_value) {
|
||||
const auto converted = ir.Emit(IR::ValueOpcode::ConvertS32F32, {value});
|
||||
const auto low = ir.Emit(IR::ValueOpcode::SMax32, {converted, IR::Value(0xffff8000u)});
|
||||
Write16Bits(DestinationOperand(inst),
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {low, IR::Value(0x7fffu)})));
|
||||
return;
|
||||
}
|
||||
const auto converted = ir.Emit(IR::ValueOpcode::ConvertU32F32, {value});
|
||||
Write16Bits(DestinationOperand(inst),
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {converted, IR::Value(0xffffu)})));
|
||||
const auto converted = IR::U32(
|
||||
ir.Emit(signed_value ? IR::ValueOpcode::ConvertS32F32 : IR::ValueOpcode::ConvertU32F32,
|
||||
{ReadF16AsF32(inst.src0)}));
|
||||
Write16Bits(DestinationOperand(inst), Saturate16(converted, signed_value));
|
||||
}
|
||||
|
||||
void Translator::V_CVT_RPI_I32_F32(const Decoder::Instruction& inst) {
|
||||
@@ -130,14 +123,30 @@ void Translator::V_CVT_PK_U8_F32(const Decoder::Instruction& inst) {
|
||||
{ReadU32(SourceAt(inst, 2)), byte, shift, IR::Value(8u)}));
|
||||
}
|
||||
|
||||
void Translator::V_CVT_PK_16_32(const Decoder::Instruction& inst, bool signed_value) {
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
PackU16Lanes(Saturate16(ReadU32(inst.src0), signed_value),
|
||||
Saturate16(ReadU32(inst.src1), signed_value)));
|
||||
}
|
||||
|
||||
void Translator::V_PACK_B32_F16(const Decoder::Instruction& inst) {
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
PackU16Lanes(Read16LaneBits(inst.src0, false), Read16LaneBits(inst.src1, false)));
|
||||
}
|
||||
|
||||
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(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldInsert, {low, high, IR::Value(16u), IR::Value(16u)}));
|
||||
}
|
||||
|
||||
IR::U32 Translator::Saturate16(IR::U32 value, bool signed_value) {
|
||||
if (!signed_value) {
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {value, IR::Value(0xffffu)}));
|
||||
}
|
||||
const auto low = ir.Emit(IR::ValueOpcode::SMax32, {value, IR::Value(0xffff8000u)});
|
||||
return IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {low, IR::Value(0x7fffu)}));
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Frontend
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
@@ -118,6 +119,33 @@ void Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode op
|
||||
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)}));
|
||||
}
|
||||
|
||||
void Translator::V_FRACT(const Decoder::Instruction& inst, IR::Type type) {
|
||||
// x - floor(x) rounds to 1.0 for a tiny negative x; v_fract returns the largest value below
|
||||
// one instead. The ordered compare lets NaN through.
|
||||
const auto destination = DestinationOperand(inst);
|
||||
if (type == IR::Type::F64) {
|
||||
const auto result = ir.Emit(IR::ValueOpcode::FPFract64, {ReadOperand(inst.src0, type)});
|
||||
const auto below_one = ir.Emit(IR::ValueOpcode::BitCastF64U64,
|
||||
{IR::Value(uint64_t {0x3fefffffffffffff})});
|
||||
const auto above = ir.Emit(IR::ValueOpcode::FPOrdGreaterThan64, {result, below_one});
|
||||
WriteOperand(destination, ir.Emit(IR::ValueOpcode::SelectF64, {above, below_one, result}));
|
||||
return;
|
||||
}
|
||||
const bool half = type == IR::Type::F16;
|
||||
const auto source = half ? ReadF16AsF32(inst.src0) : IR::F32(ReadOperand(inst.src0, type));
|
||||
const auto result = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {source}));
|
||||
// Clamp in the destination precision: 0x3f7fe000 is the half 0x3bff.
|
||||
const auto below_one =
|
||||
IR::F32(IR::Value::F32(std::bit_cast<float>(half ? 0x3f7fe000u : 0x3f7fffffu)));
|
||||
const auto above = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdGreaterThan32, {result, below_one}));
|
||||
const auto clamped = SelectF32(above, below_one, result);
|
||||
if (half) {
|
||||
WriteF16(destination, clamped);
|
||||
} else {
|
||||
WriteOperand(destination, clamped);
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool reverse) {
|
||||
std::array<IR::Value, 2> args;
|
||||
|
||||
@@ -25,8 +25,7 @@ void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
const auto rhs = ReadU16AsU32(inst.src1, true);
|
||||
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
|
||||
if (inst.dst.clamp) {
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMax32, {result, IR::Value(0xffff8000u)}));
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)}));
|
||||
result = Saturate16(result, true);
|
||||
}
|
||||
Write16Bits(DestinationOperand(inst), result);
|
||||
}
|
||||
@@ -108,19 +107,7 @@ void Translator::S_U64_MASK(const Decoder::Instruction& inst, IR::ValueOpcode lo
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(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));
|
||||
}
|
||||
const auto lhs = ReadU32Pair(inst.src0);
|
||||
std::array<IR::U32, 2> result;
|
||||
if (unary) {
|
||||
result = {ir.BitwiseNot(lhs[0]), ir.BitwiseNot(lhs[1])};
|
||||
@@ -133,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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (is_exec_or_vcc(inst.dst) || is_exec_or_vcc(inst.src0) ||
|
||||
(inst.src_count > 1u && is_exec_or_vcc(inst.src1))) {
|
||||
WriteMask(inst.dst, invocation_result, result, true);
|
||||
} else {
|
||||
// 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))));
|
||||
}
|
||||
@@ -221,8 +218,17 @@ void Translator::V_XOR3_B32(const Decoder::Instruction& inst) {
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
}
|
||||
|
||||
void Translator::S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb) {
|
||||
void Translator::S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb,
|
||||
bool sign) {
|
||||
auto source = ReadU32Pair(inst.src0);
|
||||
if (sign) {
|
||||
// A signed source sign-extends a literal; then bits equal to the sign become zero.
|
||||
if (inst.src0.kind == Decoder::OperandKind::LiteralConstant) {
|
||||
source[1] = ir.ShiftRightArithmetic(source[0], IR::U32(IR::Value(31u)));
|
||||
}
|
||||
const auto mask = ir.ShiftRightArithmetic(source[1], IR::U32(IR::Value(31u)));
|
||||
source = {ir.BitwiseXor(source[0], mask), ir.BitwiseXor(source[1], mask)};
|
||||
}
|
||||
for (auto& word: source) {
|
||||
word = find_zero ? ir.BitwiseNot(word) : word;
|
||||
}
|
||||
@@ -240,12 +246,19 @@ void Translator::S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero,
|
||||
|
||||
void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
|
||||
const auto source = ReadU32(inst.src0);
|
||||
const auto value = sign ? ir.BitwiseXor(
|
||||
source, ir.ShiftRightArithmetic(source, IR::U32(IR::Value(31u))))
|
||||
: source;
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {value}));
|
||||
if (sign) {
|
||||
// Bits equal to the sign become zero; FindILsb of the reversed bits is ~0 when none.
|
||||
const auto bits =
|
||||
ir.BitwiseXor(source, ir.ShiftRightArithmetic(source, IR::U32(IR::Value(31u))));
|
||||
const auto reversed = ir.Emit(IR::ValueOpcode::BitReverse32, {bits});
|
||||
WriteOperand(DestinationOperand(inst),
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {reversed})));
|
||||
return;
|
||||
}
|
||||
// ResourceTracking matches this 31 - FindUMsb32 form.
|
||||
const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {source}));
|
||||
const auto position = ir.ISub(IR::U32(IR::Value(31u)), msb);
|
||||
const auto result = ir.Select(ir.INotEqual(value, IR::U32(IR::Value(0u))), position,
|
||||
const auto result = ir.Select(ir.INotEqual(source, IR::U32(IR::Value(0u))), position,
|
||||
IR::U32(IR::Value(0xffffffffu)));
|
||||
WriteOperand(DestinationOperand(inst), result);
|
||||
}
|
||||
@@ -277,7 +290,8 @@ void Translator::V_MAD_U64_U32(const Decoder::Instruction& inst) {
|
||||
if (inst.dst2.kind != Decoder::OperandKind::Null &&
|
||||
inst.dst2.kind != Decoder::OperandKind::Unknown) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,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 bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {value, offset, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
ir.SetScc(ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
}
|
||||
|
||||
void Translator::S_BITCMP_B64(const Decoder::Instruction& inst, bool expected) {
|
||||
@@ -468,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]);
|
||||
const auto bit =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::BitFieldUExtract, {word, word_bit, IR::Value(1u)}));
|
||||
WriteCompareResult(inst.dst, ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
ir.SetScc(ir.IEqual(bit, IR::U32(IR::Value(expected ? 1u : 0u))));
|
||||
}
|
||||
|
||||
void Translator::V_ALIGN_B32(const Decoder::Instruction& inst, bool byte_offset) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/shader/recompiler/frontend/translate/Translator.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
@@ -324,8 +322,8 @@ IR::Value Translator::MakeImageAddress(const Decoder::Instruction& inst,
|
||||
const Decoder::Operand& base) {
|
||||
std::array<IR::Value, 13> components {};
|
||||
components.fill(IR::Value(0u));
|
||||
const auto count =
|
||||
Decoder::ImageAddressDwordCount(inst.image_sample_flags, inst.image_address_components);
|
||||
const auto count = Decoder::ImageAddressDwordCount(
|
||||
inst.image_sample_flags, inst.image_dimension, inst.image_address_components);
|
||||
EXIT_IF(count > components.size());
|
||||
const auto nsa_components =
|
||||
std::min(inst.image_nsa_dwords * 4u, Decoder::MaxImageNsaAddressComponents);
|
||||
@@ -455,12 +453,17 @@ void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
|
||||
AddMemoryInfo(memory, inst.pc));
|
||||
if (bits != 32u) {
|
||||
WriteOperand(inst.dst, WidenSubdword(loaded, bits, sign));
|
||||
} else if (memory.data_dwords == 1u) {
|
||||
WriteOperand(inst.dst, loaded);
|
||||
} else {
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
WriteOperand(OffsetOperand(inst.dst, component),
|
||||
ir.CompositeExtract(loaded, component));
|
||||
return;
|
||||
}
|
||||
for (uint32_t component = 0; component < memory.data_dwords; component++) {
|
||||
const auto dst = OffsetOperand(inst.dst, component);
|
||||
const auto value =
|
||||
memory.data_dwords == 1u ? IR::U32(loaded) : ir.CompositeExtract(loaded, component);
|
||||
// D16 formats with an odd component count keep the high half of their final VGPR.
|
||||
if (memory.data_bits == 16u && component * 2u + 1u == memory.component_count) {
|
||||
Write16Bits(dst, value);
|
||||
} else {
|
||||
WriteOperand(dst, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -860,9 +863,10 @@ void Translator::DS_WRITE2(const Decoder::Instruction& inst) {
|
||||
|
||||
void Translator::DS_APPEND_CONSUME(const Decoder::Instruction& inst, IR::ValueOpcode opcode) {
|
||||
const auto memory = MemoryInfoFromDecoded(inst);
|
||||
WriteOperand(inst.dst, ir.Emit(opcode,
|
||||
{ReadU32(MemorySourceAt(inst, 0)), ir.GetExec()},
|
||||
AddMemoryInfo(memory, inst.pc)));
|
||||
const auto exec = ExecMask();
|
||||
WriteOperand(inst.dst,
|
||||
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) {
|
||||
@@ -898,10 +902,7 @@ void Translator::DS_ORDERED_COUNT(const Decoder::Instruction& inst) {
|
||||
const auto packer = ir.BitwiseAnd(m0, IR::U32(IR::Value(0xfu)));
|
||||
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
|
||||
}
|
||||
const auto exec = ir.GetExec();
|
||||
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 [low, high] = ExecMask();
|
||||
const auto active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
|
||||
const auto first = ir.Select(
|
||||
ir.INotEqual(low, IR::U32(IR::Value(0u))),
|
||||
@@ -940,6 +941,8 @@ void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
|
||||
|
||||
void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
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_GL1_INV:
|
||||
ir.Emit(IR::ValueOpcode::DataCacheInvalidate);
|
||||
@@ -964,17 +967,9 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX4:
|
||||
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX8:
|
||||
case Decoder::Opcode::S_BUFFER_LOAD_DWORDX16: return S_LOAD(inst, false);
|
||||
case Decoder::Opcode::S_MEMREALTIME: {
|
||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
||||
Log::WriteToConsoleAndLog(
|
||||
"Warning: S_MEMREALTIME uses placeholder UINT64_MAX; real-time clock not implemented.\n");
|
||||
}
|
||||
for (uint32_t component = 0; component < 2; component++) {
|
||||
WriteOperand(ScalarDestinationOperand(inst.dst, component), IR::Value(UINT32_MAX));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case Decoder::Opcode::S_MEMREALTIME:
|
||||
return WriteU32Pair(inst.dst,
|
||||
ExtractU64(IR::U64(ir.Emit(IR::ValueOpcode::ReadRealtimeClock))));
|
||||
|
||||
case Decoder::Opcode::BUFFER_LOAD_UBYTE:
|
||||
case Decoder::Opcode::BUFFER_LOAD_UBYTE_D16:
|
||||
@@ -992,6 +987,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZ:
|
||||
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
|
||||
@@ -1012,6 +1009,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_HI_X:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XY:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZ:
|
||||
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
|
||||
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
|
||||
@@ -1032,6 +1031,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMin32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_UMIN:
|
||||
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:
|
||||
return BUFFER_ATOMIC(inst, IR::ValueOpcode::BufferAtomicSMax32);
|
||||
case Decoder::Opcode::BUFFER_ATOMIC_UMAX:
|
||||
@@ -1149,6 +1150,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::FLAT_LOAD_USHORT:
|
||||
case Decoder::Opcode::FLAT_LOAD_SSHORT:
|
||||
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_DWORDX2:
|
||||
case Decoder::Opcode::FLAT_LOAD_DWORDX3:
|
||||
@@ -1168,15 +1170,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
|
||||
case Decoder::Opcode::IMAGE_STORE:
|
||||
case Decoder::Opcode::IMAGE_STORE_MIP: return IMAGE_STORE(inst);
|
||||
case Decoder::Opcode::IMAGE_SAMPLE: return IMAGE_SAMPLE(inst);
|
||||
case Decoder::Opcode::IMAGE_GATHER4_L:
|
||||
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::IMAGE_GATHER4: return IMAGE_GATHER(inst);
|
||||
|
||||
case Decoder::Opcode::DS_MIN_F32:
|
||||
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_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true, false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
case O::S_CMP_LG_U32:
|
||||
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;
|
||||
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;
|
||||
case O::S_CMP_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true,
|
||||
false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
case O::S_CMP_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true,
|
||||
false);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, true);
|
||||
return;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
|
||||
case O::S_AND_B64:
|
||||
@@ -238,10 +236,12 @@ void Translator::EmitScalar(const Decoder::Instruction& inst) {
|
||||
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseOr32, false, true, true);
|
||||
case O::S_XNOR_B32:
|
||||
return ComposedIntegerBinary(inst, IR::ValueOpcode::BitwiseXor32, false, true, true);
|
||||
case O::S_FF0_I32_B64: return S_FF_I32_B64(inst, true, false);
|
||||
case O::S_FF1_I32_B64: return S_FF_I32_B64(inst, false, false);
|
||||
case O::S_FF0_I32_B64: return S_FF_I32_B64(inst, true, false, false);
|
||||
case O::S_FF1_I32_B64: return S_FF_I32_B64(inst, false, false, false);
|
||||
case O::S_FLBIT_I32_B32: return V_FFBH_32(inst, false);
|
||||
case O::S_FLBIT_I32_B64: return S_FF_I32_B64(inst, false, true);
|
||||
case O::S_FLBIT_I32_B64: return S_FF_I32_B64(inst, false, true, false);
|
||||
case O::S_FLBIT_I32: return V_FFBH_32(inst, true);
|
||||
case O::S_FLBIT_I32_I64: return S_FF_I32_B64(inst, false, true, true);
|
||||
|
||||
case O::S_BITSET0_B32: return S_BITSET_B32(inst, false);
|
||||
case O::S_BITSET1_B32: return S_BITSET_B32(inst, true);
|
||||
|
||||
@@ -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))};
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::LiteralConstant:
|
||||
@@ -138,6 +152,8 @@ IR::U32 Translator::ReadRawU32(const Decoder::Operand& operand) {
|
||||
case Decoder::OperandKind::SharedBase:
|
||||
case Decoder::OperandKind::PrivateBase:
|
||||
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:
|
||||
return ir.GetScalarReg(static_cast<IR::ScalarReg>(operand.reg));
|
||||
case Decoder::OperandKind::Vgpr:
|
||||
@@ -319,7 +335,10 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
case Decoder::OperandKind::Vgpr: {
|
||||
const auto reg = static_cast<IR::VectorReg>(operand.reg);
|
||||
const auto old = ir.GetVectorReg(reg);
|
||||
if (operand.dpp) {
|
||||
// DPP skips EXEC lanes only through row/bank masks, or for invalid sources without
|
||||
// BOUND_CTRL (dpp8 reads zero); otherwise it is a plain masked write.
|
||||
if (operand.dpp && (operand.dpp_row_mask != 0xfu || operand.dpp_bank_mask != 0xfu ||
|
||||
(!operand.dpp_bound_ctrl && !operand.dpp8))) {
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()},
|
||||
DppFlags(operand)));
|
||||
} else {
|
||||
@@ -385,21 +404,9 @@ void Translator::WriteOperand(const Decoder::Operand& operand, IR::Value value)
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::Scc: ir.SetScc(IR::U1(value)); return;
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
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::ExecHi:
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccHi: {
|
||||
const auto mask = BallotMask(IR::U1(value));
|
||||
ir.SetVcc(IR::U1(value));
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::VccHi: EXIT("lane masks are written through WriteMask");
|
||||
default:
|
||||
WriteRawU32(operand, ir.Select(IR::U1(value), IR::U32(IR::Value(1u)),
|
||||
IR::U32(IR::Value(0u))));
|
||||
@@ -468,17 +475,17 @@ IR::U32 Translator::ReadU32(const Decoder::Operand& operand) {
|
||||
}
|
||||
|
||||
std::array<IR::U32, 2> Translator::ReadU32Pair(const Decoder::Operand& operand) {
|
||||
if (operand.kind == Decoder::OperandKind::PrivateBase ||
|
||||
operand.kind == Decoder::OperandKind::SharedBase) {
|
||||
return {IR::U32(IR::Value(0u)), IR::U32(IR::Value(
|
||||
operand.kind == Decoder::OperandKind::PrivateBase ? Decoder::PrivateApertureHigh
|
||||
: Decoder::SharedApertureHigh))};
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::ExecLo) {
|
||||
return {ir.GetExecLo(), ir.GetExecHi()};
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::VccLo) {
|
||||
return {ir.GetVccLo(), ir.GetVccHi()};
|
||||
// Each aperture limit is its base plus 0xffffffff.
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::SharedBase:
|
||||
case Decoder::OperandKind::SharedLimit:
|
||||
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::SharedApertureHigh))};
|
||||
case Decoder::OperandKind::PrivateBase:
|
||||
case Decoder::OperandKind::PrivateLimit:
|
||||
return {ReadRawU32(operand), IR::U32(IR::Value(Decoder::PrivateApertureHigh))};
|
||||
case Decoder::OperandKind::ExecLo: return {ir.GetExecLo(), ir.GetExecHi()};
|
||||
case Decoder::OperandKind::VccLo: return {ir.GetVccLo(), ir.GetVccHi()};
|
||||
default: break;
|
||||
}
|
||||
if (operand.kind == Decoder::OperandKind::FloatInlineConstant) {
|
||||
const auto bits =
|
||||
@@ -636,7 +643,7 @@ IR::U1 Translator::ReadMask(const Decoder::Operand& operand) {
|
||||
}
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi: return ir.GetExec();
|
||||
case Decoder::OperandKind::VccLo:
|
||||
case Decoder::OperandKind::VccLo: return ir.GetVcc();
|
||||
case Decoder::OperandKind::VccHi:
|
||||
return program.wave_size == 32u
|
||||
? ThreadBit({ReadRawU32(operand), IR::U32(IR::Value(0u))})
|
||||
@@ -674,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,
|
||||
bool write_64) {
|
||||
const auto mask = BallotMask(value);
|
||||
void Translator::WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
const std::array<IR::U32, 2>& mask, bool write_64) {
|
||||
switch (operand.kind) {
|
||||
case Decoder::OperandKind::Sgpr: {
|
||||
const auto reg = static_cast<IR::ScalarReg>(operand.reg);
|
||||
@@ -697,43 +703,43 @@ std::array<IR::U32, 2> Translator::WriteMask(const Decoder::Operand& operand, IR
|
||||
ir.SetThreadBitScalarReg(high, IR::U1(IR::Value(false)));
|
||||
ir.SetScalarMaskTag(high, IR::U1(IR::Value(false)));
|
||||
}
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::ExecLo:
|
||||
case Decoder::OperandKind::ExecHi: {
|
||||
ir.SetExec(value);
|
||||
ir.SetExecLo(mask[0]);
|
||||
ir.SetExecHi(mask[1]);
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::VccLo:
|
||||
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]);
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
ir.SetVcc(value);
|
||||
ir.SetVccLo(mask[0]);
|
||||
ir.SetVccHi(mask[1]);
|
||||
return mask;
|
||||
if (write_64 || program.wave_size == 64u) {
|
||||
ir.SetVccHi(mask[1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case Decoder::OperandKind::Scc: ir.SetScc(value); return mask;
|
||||
case Decoder::OperandKind::Scc: ir.SetScc(value); return;
|
||||
default:
|
||||
WriteRawU32(operand, mask[0]);
|
||||
WriteRawU32(OffsetOperand(operand, 1), mask[1]);
|
||||
return mask;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::WriteCompareResult(const Decoder::Operand& operand, IR::U1 value) {
|
||||
if (operand.kind == Decoder::OperandKind::Scc) {
|
||||
WriteOperand(operand, value);
|
||||
return;
|
||||
}
|
||||
void Translator::WriteVectorMask(const Decoder::Operand& operand, IR::U1 value) {
|
||||
// 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
|
||||
// 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,
|
||||
@@ -1150,7 +1156,10 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
}
|
||||
}
|
||||
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_hi = options.wave_size == 64u ? entry_ir.CompositeExtract(initial_mask, 1)
|
||||
: IR::U32(IR::Value(0u));
|
||||
@@ -1235,12 +1244,12 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(
|
||||
primitive_count, u32(8)),
|
||||
vertex_count)));
|
||||
// Instance IDs exclude the start instance, which shaders add from user data.
|
||||
if (mesh.fast_launch) {
|
||||
// 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>(6),
|
||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(6),
|
||||
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||
} else {
|
||||
// GS adjacency addresses local ES records in LDS. Fans retain the draw's
|
||||
// center in every subgroup; strip winding follows the global primitive.
|
||||
@@ -1268,14 +1277,14 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.ShiftLeftLogical(second, u32(18))));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(1),
|
||||
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 index_low = draw(4);
|
||||
const auto index_low = draw(3);
|
||||
const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)),
|
||||
entry_ir.IMul(input_vertex, index_bytes));
|
||||
const auto index_resource = entry_ir.Emit(
|
||||
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());
|
||||
result.memory_info.push_back({.kind = IR::ResourceKind::Global});
|
||||
const auto packed_index = entry_ir.Emit(
|
||||
@@ -1289,9 +1298,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
entry_ir.IMul(index_bytes, u32(8))}));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(5),
|
||||
entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex)));
|
||||
entry_ir.SetVectorReg(
|
||||
static_cast<IR::VectorReg>(8),
|
||||
entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1)));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
builtin(IR::StageInputKind::WorkgroupId, 1));
|
||||
}
|
||||
} else if (options.stage == ShaderType::Local) {
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3), IR::U32(IR::Value(64u)));
|
||||
@@ -1381,10 +1389,27 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph&
|
||||
IR::U32(IR::Value(options.wave_size << 12u)));
|
||||
entry_ir.SetScalarReg(static_cast<IR::ScalarReg>(3),
|
||||
entry_ir.BitwiseOr(vertex_count, IR::U32(IR::Value(1u << 28u))));
|
||||
// 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),
|
||||
builtin(IR::StageInputKind::VertexIndex));
|
||||
index(offsets.vertex, IR::StageInputKind::VertexIndex,
|
||||
IR::StageInputKind::BaseVertex));
|
||||
entry_ir.SetVectorReg(static_cast<IR::VectorReg>(8),
|
||||
builtin(IR::StageInputKind::InstanceIndex));
|
||||
index(offsets.instance, IR::StageInputKind::InstanceIndex,
|
||||
IR::StageInputKind::BaseInstance));
|
||||
}
|
||||
}
|
||||
const bool flush_f32_inputs = options.stage == ShaderType::Compute &&
|
||||
|
||||
@@ -27,6 +27,8 @@ private:
|
||||
Decoder::Operand ScalarDestinationOperand(const Decoder::Operand& operand, uint32_t offset);
|
||||
Decoder::Operand PlainOperand(const Decoder::Operand& operand);
|
||||
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 ReadScalarCode(uint32_t code);
|
||||
IR::U32 ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value);
|
||||
@@ -52,9 +54,9 @@ private:
|
||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMask(const Decoder::Operand& operand);
|
||||
IR::U1 ReadMaskValid(const Decoder::Operand& operand);
|
||||
std::array<IR::U32, 2> WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
bool write_64 = false);
|
||||
void WriteCompareResult(const Decoder::Operand& operand, IR::U1 value);
|
||||
void WriteMask(const Decoder::Operand& operand, IR::U1 value,
|
||||
const std::array<IR::U32, 2>& mask, bool write_64 = false);
|
||||
void WriteVectorMask(const Decoder::Operand& operand, IR::U1 value);
|
||||
|
||||
IR::MemoryFlags AddMemoryInfo(const IR::MemoryInfo& memory, uint32_t pc);
|
||||
IR::ExportFlags AddExportInfo(const Decoder::Instruction& inst);
|
||||
@@ -114,17 +116,16 @@ private:
|
||||
|
||||
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
|
||||
IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high);
|
||||
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar, bool cmpx);
|
||||
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar, bool cmpx);
|
||||
IR::U32 Saturate16(IR::U32 value, bool signed_value);
|
||||
void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar);
|
||||
void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar);
|
||||
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,
|
||||
bool signed_value, bool cmpx);
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half,
|
||||
bool cmpx);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool cmpx,
|
||||
bool is64 = false);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool cmpx, bool half = false);
|
||||
bool signed_value);
|
||||
void EmitFloatCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool half);
|
||||
void EmitFloatOrderedCompare(const Decoder::Instruction& inst, bool ordered, bool is64 = false);
|
||||
void EmitFloatClassCompare(const Decoder::Instruction& inst, bool half = false);
|
||||
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_I32(const Decoder::Instruction& inst);
|
||||
@@ -141,6 +142,7 @@ private:
|
||||
void V_CVT_PKRTZ_F16_F32(const Decoder::Instruction& inst);
|
||||
void V_CVT_PKNORM_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void V_CVT_PK_U8_F32(const Decoder::Instruction& inst);
|
||||
void V_CVT_PK_16_32(const Decoder::Instruction& inst, bool signed_value);
|
||||
void V_PACK_B32_F16(const Decoder::Instruction& inst);
|
||||
void PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool quiet_snan);
|
||||
@@ -151,6 +153,7 @@ private:
|
||||
void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool mix);
|
||||
void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
|
||||
void V_FRACT(const Decoder::Instruction& inst, IR::Type type);
|
||||
void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
|
||||
void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
|
||||
bool mix);
|
||||
@@ -182,7 +185,7 @@ private:
|
||||
void V_AND_OR_B32(const Decoder::Instruction& inst);
|
||||
void V_OR3_B32(const Decoder::Instruction& inst);
|
||||
void V_XOR3_B32(const Decoder::Instruction& inst);
|
||||
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb);
|
||||
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign);
|
||||
void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
|
||||
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend);
|
||||
void V_MAD_U64_U32(const Decoder::Instruction& inst);
|
||||
@@ -240,8 +243,8 @@ private:
|
||||
void V_READLANE_B32(const Decoder::Instruction& inst);
|
||||
void V_WRITELANE_B32(const Decoder::Instruction& inst);
|
||||
void V_PERMLANE16_B32(const Decoder::Instruction& inst, bool x16);
|
||||
void V_INTERP_P1_F32();
|
||||
void V_INTERP_P2_F32(const Decoder::Instruction& inst);
|
||||
void V_INTERP_P2_F16(const Decoder::Instruction& inst);
|
||||
void V_INTERP_MOV_F32(const Decoder::Instruction& inst);
|
||||
void EXP(const Decoder::Instruction& inst);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
using O = Decoder::Opcode;
|
||||
switch (inst.opcode) {
|
||||
case O::V_NOP: return;
|
||||
case O::V_INTERP_P1LL_F16:
|
||||
case O::V_INTERP_P2_F16: return EmitInterpolation(inst);
|
||||
case O::V_ADD_I32: ADD_U32(inst, true, false); return;
|
||||
case O::V_ADDC_U32: ADD_U32(inst, true, true); return;
|
||||
case O::V_SUB_I32: SUB_U32(inst, true, false); return;
|
||||
@@ -23,444 +25,310 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
||||
|
||||
case O::V_CMP_F_I32:
|
||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_F_U32: EmitCompareConstant(inst, false, false); return;
|
||||
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_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual32, IR::Type::U32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_U32:
|
||||
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;
|
||||
case O::V_CMP_NE_U32:
|
||||
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_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
case O::V_CMPX_GE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_LE_U32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_GT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
case O::V_CMPX_GE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SGreaterThanEqual32, IR::Type::U32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan32, IR::Type::U32, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_LE_I32:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false,
|
||||
false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_F_I64:
|
||||
case O::V_CMP_F_U64: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_T_I64:
|
||||
case O::V_CMP_T_U64: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMP_F_U64:
|
||||
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_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_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_I64:
|
||||
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;
|
||||
case O::V_CMP_LT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_GT_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_NE_I64:
|
||||
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_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false);
|
||||
return;
|
||||
|
||||
case O::V_CMP_LT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_LE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
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;
|
||||
// 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:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false,
|
||||
true);
|
||||
return;
|
||||
case O::V_CMPX_GT_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_GE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false,
|
||||
true);
|
||||
return;
|
||||
case O::V_CMPX_GE_I64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true,
|
||||
true);
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
|
||||
return;
|
||||
case O::V_CMP_LE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_GE_U64:
|
||||
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, false);
|
||||
return;
|
||||
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;
|
||||
|
||||
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;
|
||||
case O::V_CMP_EQ_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_NE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::INotEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThan32, false);
|
||||
return;
|
||||
case O::V_CMP_GE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false, false);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::UGreaterThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true);
|
||||
return;
|
||||
case O::V_CMPX_EQ_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false);
|
||||
return;
|
||||
case O::V_CMP_LE_U16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThan32, true);
|
||||
return;
|
||||
case O::V_CMP_GE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SGreaterThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true);
|
||||
return;
|
||||
case O::V_CMP_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, true);
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true);
|
||||
return;
|
||||
|
||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false, false); return;
|
||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false, false); return;
|
||||
case O::V_CMP_F_F32: EmitCompareConstant(inst, false, false); return;
|
||||
case O::V_CMP_TRU_F32: EmitCompareConstant(inst, true, false); return;
|
||||
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;
|
||||
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;
|
||||
case O::V_CMP_LE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false, true);
|
||||
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);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual64, false);
|
||||
return;
|
||||
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;
|
||||
case O::V_CMP_NLG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLG_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NLE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan64, false);
|
||||
return;
|
||||
case O::V_CMP_NLT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual64, false);
|
||||
return;
|
||||
case O::V_CMP_NGE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGE_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan64, false);
|
||||
return;
|
||||
case O::V_CMP_NGT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGT_F64:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false, true);
|
||||
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);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual64, false);
|
||||
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:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_GT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, false);
|
||||
return;
|
||||
case O::V_CMP_GE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_LT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, false);
|
||||
return;
|
||||
case O::V_CMP_LE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NLG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLG_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NLE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, false);
|
||||
return;
|
||||
case O::V_CMP_NLT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NLT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_NGE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGE_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, false);
|
||||
return;
|
||||
case O::V_CMP_NGT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, false);
|
||||
return;
|
||||
case O::V_CMPX_NGT_F32:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, false);
|
||||
return;
|
||||
case O::V_CMP_EQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_EQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_LG_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true, false);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdNotEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_GT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThan32, true);
|
||||
return;
|
||||
case O::V_CMP_GE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_GE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdGreaterThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_LT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThan32, true);
|
||||
return;
|
||||
case O::V_CMP_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPOrdLessThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_NGE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true, false);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThan32, true);
|
||||
return;
|
||||
case O::V_CMP_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NGT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordLessThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMPX_NLE_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThan32, true);
|
||||
return;
|
||||
case O::V_CMP_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true);
|
||||
return;
|
||||
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
|
||||
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
|
||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
|
||||
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false, true); return;
|
||||
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
|
||||
case O::V_CMP_CLASS_F16: EmitFloatClassCompare(inst, false, true); return;
|
||||
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return;
|
||||
case O::V_CMP_O_F32:
|
||||
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true); return;
|
||||
case O::V_CMP_U_F32:
|
||||
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false); return;
|
||||
case O::V_CMP_CLASS_F32:
|
||||
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst); return;
|
||||
case O::V_CMP_CLASS_F16:
|
||||
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_UBYTE1: V_CVT_F32_UBYTE(inst, 1); return;
|
||||
@@ -493,8 +361,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
return;
|
||||
case O::V_CVT_PK_U8_F32: V_CVT_PK_U8_F32(inst); return;
|
||||
case O::V_PACK_B32_F16: V_PACK_B32_F16(inst); return;
|
||||
case O::V_CVT_PK_U16_U32:
|
||||
case O::V_CVT_PK_I16_I32: return PackB16(inst, false, false);
|
||||
case O::V_CVT_PK_U16_U32: return V_CVT_PK_16_32(inst, false);
|
||||
case O::V_CVT_PK_I16_I32: return V_CVT_PK_16_32(inst, true);
|
||||
|
||||
case O::V_LSHLREV_B16:
|
||||
return Integer16Shift(inst, IR::ValueOpcode::ShiftLeftLogical32, false);
|
||||
@@ -561,7 +429,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false);
|
||||
case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false);
|
||||
case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false);
|
||||
case O::V_FRACT_F16: return Float16Unary(inst, IR::ValueOpcode::FPFract32, false);
|
||||
case O::V_FRACT_F16: return V_FRACT(inst, IR::Type::F16);
|
||||
case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin);
|
||||
case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos);
|
||||
case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false);
|
||||
@@ -572,8 +440,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32);
|
||||
case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64);
|
||||
case O::V_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32);
|
||||
case O::V_FRACT_F32: return FloatUnary(inst, IR::ValueOpcode::FPFract32);
|
||||
case O::V_FRACT_F64: return FloatUnary(inst, IR::ValueOpcode::FPFract64);
|
||||
case O::V_FRACT_F32: return V_FRACT(inst, IR::Type::F32);
|
||||
case O::V_FRACT_F64: return V_FRACT(inst, IR::Type::F64);
|
||||
case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32);
|
||||
case O::V_TRUNC_F64: return FloatUnary(inst, IR::ValueOpcode::FPTrunc64);
|
||||
case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32);
|
||||
|
||||
@@ -118,15 +118,14 @@ ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
|
||||
}
|
||||
|
||||
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) {
|
||||
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& inst: *block) {
|
||||
inst.Invalidate();
|
||||
inst.DropArgs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,6 +168,8 @@ bool EquivalentSelectorDomain(const ResourcePlan& program,
|
||||
const DescriptorSource::IndirectDescriptor& right) {
|
||||
return left.selector == right.selector && left.signed_count == right.signed_count &&
|
||||
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.selector_first, right.selector_first) &&
|
||||
EquivalentValue(program, left.selector_mask, right.selector_mask);
|
||||
@@ -487,7 +488,7 @@ void ValidateProgram(const Program& program, bool require_ssa) {
|
||||
const auto access = BufferAccessOf(inst.GetOpcode());
|
||||
const bool packed_formatted =
|
||||
(access == BufferAccess::Read || access == BufferAccess::Write) && memory.formatted &&
|
||||
memory.data_bits == 16u && memory.component_count == 4u && buffer_components == 2u;
|
||||
memory.data_bits == 16u && (memory.component_count + 1u) / 2u == buffer_components;
|
||||
if (buffer_components > 1u &&
|
||||
(!vector_buffer || (memory.data_bits != 32u && !packed_formatted) ||
|
||||
memory.data_dwords != buffer_components || memory.component_index != 0u)) {
|
||||
|
||||
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user