mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-27 16:02:37 +00:00
Compare commits
109
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 |
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)
|
||||
|
||||
+44
-1
@@ -337,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)
|
||||
@@ -407,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"
|
||||
@@ -451,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"
|
||||
@@ -541,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})
|
||||
@@ -632,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>)
|
||||
@@ -652,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>)
|
||||
@@ -678,6 +702,8 @@ if(BUILD_TESTING)
|
||||
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
|
||||
@@ -720,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
|
||||
@@ -742,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
|
||||
@@ -785,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)
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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,18 +112,11 @@ 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();
|
||||
@@ -159,30 +124,22 @@ private:
|
||||
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;
|
||||
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++) {
|
||||
|
||||
@@ -127,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));
|
||||
}
|
||||
|
||||
@@ -200,11 +198,8 @@ void CommandProcessor::Reset() {
|
||||
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) {
|
||||
@@ -237,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() {
|
||||
@@ -244,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;
|
||||
@@ -319,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);
|
||||
@@ -539,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;
|
||||
@@ -699,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;
|
||||
@@ -803,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);
|
||||
@@ -868,7 +799,7 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
|
||||
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;
|
||||
@@ -880,6 +811,28 @@ 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,
|
||||
@@ -913,12 +866,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
||||
// while they are zero.
|
||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
||||
registers.index_offset == Pm4::SH_NOP && (!indexed || m_index_buffer_size != 0) &&
|
||||
// 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),
|
||||
@@ -933,7 +885,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
if (!indexed) {
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0, i);
|
||||
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||
{.vertex_count = args.vertexCount,
|
||||
.instance_count = args.instanceCount,
|
||||
@@ -945,7 +897,7 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||
args.firstInstance, args.firstIndex);
|
||||
args.firstInstance, args.firstIndex, i);
|
||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = std::min(args.indexCount, max_indices),
|
||||
.index_addr = IndexAddress(args.firstIndex),
|
||||
@@ -1015,209 +967,73 @@ void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -1270,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);
|
||||
@@ -1294,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) {
|
||||
@@ -1374,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;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ public:
|
||||
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.
|
||||
@@ -60,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ struct GraphicContext {
|
||||
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 = {};
|
||||
@@ -43,6 +45,7 @@ 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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
+13
-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);
|
||||
@@ -299,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) {
|
||||
@@ -318,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -719,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) {
|
||||
|
||||
+8
-9
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -85,19 +86,16 @@ public:
|
||||
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 {
|
||||
@@ -122,7 +120,6 @@ 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);
|
||||
@@ -144,6 +141,8 @@ private:
|
||||
// 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;
|
||||
|
||||
+103
-105
@@ -251,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()) {
|
||||
@@ -429,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);
|
||||
@@ -442,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;
|
||||
}
|
||||
|
||||
@@ -496,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 ||
|
||||
@@ -515,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,
|
||||
@@ -558,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);
|
||||
@@ -787,15 +779,22 @@ 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 &&
|
||||
@@ -869,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 {}};
|
||||
}
|
||||
@@ -1218,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;
|
||||
}
|
||||
}
|
||||
@@ -1313,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);
|
||||
}
|
||||
}
|
||||
@@ -1381,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);
|
||||
@@ -1405,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;
|
||||
}
|
||||
@@ -1430,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;
|
||||
}
|
||||
@@ -1543,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;
|
||||
@@ -1904,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2060,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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-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,
|
||||
@@ -184,7 +185,6 @@ 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;
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -425,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();
|
||||
|
||||
@@ -139,14 +139,17 @@ public:
|
||||
}
|
||||
|
||||
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 {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -484,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);
|
||||
|
||||
@@ -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_
|
||||
@@ -604,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) {
|
||||
@@ -672,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();
|
||||
}
|
||||
@@ -683,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};
|
||||
@@ -725,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)]);
|
||||
@@ -782,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)]);
|
||||
@@ -803,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);
|
||||
@@ -815,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());
|
||||
@@ -823,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);
|
||||
@@ -864,8 +867,8 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
|
||||
bool uses_image_heap = false;
|
||||
FindBuffers(stages);
|
||||
for (auto* stage: stages) {
|
||||
uses_dma |= stage->runtime->program->info.uses_dma;
|
||||
uses_image_heap |= stage->runtime->program->info.uses_image_heap;
|
||||
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();
|
||||
@@ -879,12 +882,9 @@ void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const>
|
||||
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);
|
||||
@@ -917,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);
|
||||
@@ -938,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");
|
||||
@@ -956,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];
|
||||
@@ -982,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);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -19,7 +20,7 @@ namespace {
|
||||
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
|
||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
||||
// Smaller tables are often written for one draw, so they enter on a second draw of a frame.
|
||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||
|
||||
@@ -44,6 +45,20 @@ void Widen(size_t& begin, size_t& end, size_t first, size_t last) {
|
||||
end = empty ? last : std::max(end, last);
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -118,26 +133,25 @@ ImageHeap::~ImageHeap() {
|
||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto size = request.size & ~uint64_t {3};
|
||||
if (size < MinTableBytes || request.count == 0u || request.step == 0u ||
|
||||
if (request.count == 0u || request.step == 0u ||
|
||||
((request.base | request.first | request.step) & 3u) != 0u) {
|
||||
return false;
|
||||
}
|
||||
auto memory = std::ranges::find_if(m_memories, [&](const auto& candidate) {
|
||||
return candidate->base == request.base && candidate->size == size;
|
||||
});
|
||||
if (memory == m_memories.end()) {
|
||||
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;
|
||||
auto created = std::make_unique<Memory>();
|
||||
created->base = request.base;
|
||||
created->size = size;
|
||||
created->words.resize(size / 4u);
|
||||
created->dead.resize(size / 4u);
|
||||
created->dirty_pages.assign((Common::AlignUp(request.base + size, TRACKER_PAGE_SIZE) -
|
||||
Common::AlignDown(request.base, TRACKER_PAGE_SIZE)) /
|
||||
TRACKER_PAGE_SIZE,
|
||||
uint8_t {1});
|
||||
std::scoped_lock lock {m_mutex};
|
||||
memory = m_memories.insert(m_memories.end(), std::move(created));
|
||||
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.
|
||||
@@ -146,27 +160,28 @@ bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView&
|
||||
(*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->count == request.count && candidate->profile == profile;
|
||||
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 = request.count,
|
||||
.count = count,
|
||||
.limit = limit,
|
||||
.profile = profile,
|
||||
.resolve_epoch = epoch,
|
||||
.layout_epoch = epoch});
|
||||
created->slots.resize(size / 4u + 1u);
|
||||
created->buffer = std::make_unique<Buffer>(
|
||||
m_context.GetGraphics(), m_context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||
size + 4u);
|
||||
UpdateProbes(*created, 0u, size);
|
||||
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();
|
||||
@@ -254,10 +269,12 @@ void ImageHeap::Unwatch(const Memory& memory) {
|
||||
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (auto& memory: m_memories) {
|
||||
const auto begin = std::max(address, memory->base);
|
||||
const auto end = std::min(address + size, memory->base + memory->size);
|
||||
if (begin >= end) continue;
|
||||
// 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);
|
||||
}
|
||||
@@ -350,12 +367,17 @@ void ImageHeap::Release(View& view, uint32_t entry) {
|
||||
}
|
||||
|
||||
void ImageHeap::Unreference(Element& element) {
|
||||
if (auto* image = m_context.GetTextureCache().m_slot_images.try_get(element.binding.image_id)) {
|
||||
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)) {
|
||||
@@ -376,15 +398,56 @@ uint32_t ImageHeap::Allocate() {
|
||||
}
|
||||
|
||||
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; };
|
||||
@@ -393,7 +456,7 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (!binding.image_id) continue;
|
||||
const auto* image = textures.m_slot_images.try_get(binding.image_id);
|
||||
const auto* image = ImageOf(m_elements[index]);
|
||||
if (image == nullptr || !image->registered || image->depth_id ||
|
||||
image->binding.needs_rebind) {
|
||||
mark(index);
|
||||
@@ -413,6 +476,19 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
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.
|
||||
@@ -434,12 +510,15 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
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);
|
||||
@@ -471,8 +550,7 @@ void ImageHeap::WriteDescriptors() {
|
||||
void ImageHeap::Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point,
|
||||
vk::PipelineLayout layout) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto& textures = m_context.GetTextureCache();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
if (view->dirty_begin != view->dirty_end) {
|
||||
const auto slots = std::span(view->slots).subspan(view->dirty_begin,
|
||||
@@ -493,7 +571,7 @@ void ImageHeap::Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point,
|
||||
// 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 = textures.m_slot_images.try_get(element.binding.image_id);
|
||||
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) {
|
||||
@@ -519,26 +597,42 @@ void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
} else {
|
||||
m_unmapped.push_back({address, size});
|
||||
}
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
std::scoped_lock lock {m_mutex};
|
||||
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
|
||||
if (address >= memory->base + memory->size || memory->base >= address + size) {
|
||||
return false;
|
||||
}
|
||||
Unwatch(*memory);
|
||||
for (auto& view: memory->views) {
|
||||
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)] {});
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -22,10 +22,11 @@ class RenderExecutor;
|
||||
struct ImageHeapTestAccess;
|
||||
|
||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
||||
// sampled-image array. Each view of a table owns device slot words, one per table dword, that
|
||||
// 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.
|
||||
// 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);
|
||||
@@ -46,6 +47,8 @@ public:
|
||||
// CPU write fault, on any thread.
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
// At a suspend point.
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||
@@ -57,9 +60,10 @@ private:
|
||||
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;
|
||||
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;
|
||||
@@ -69,6 +73,7 @@ private:
|
||||
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;
|
||||
@@ -78,6 +83,7 @@ private:
|
||||
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;
|
||||
@@ -85,6 +91,7 @@ private:
|
||||
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 {
|
||||
@@ -102,6 +109,7 @@ private:
|
||||
template <bool track>
|
||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||
void Unwatch(const Memory& memory);
|
||||
void Drop(Memory& memory);
|
||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||
void UpdateProbe(View& view, uint64_t probe);
|
||||
void Assign(View& view, size_t slot, uint32_t entry);
|
||||
@@ -109,8 +117,10 @@ private:
|
||||
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;
|
||||
@@ -118,9 +128,11 @@ private:
|
||||
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_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;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,50 +4,62 @@
|
||||
#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)
|
||||
|
||||
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] = {};
|
||||
@@ -58,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:
|
||||
PipelineCache(GraphicContext& graphics, ShaderRecompiler::IR::ImageHeapSource* image_heap);
|
||||
// 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();
|
||||
@@ -121,87 +186,106 @@ public:
|
||||
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"
|
||||
|
||||
@@ -90,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,17 +294,13 @@ 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);
|
||||
pipeline.uses_image_heap |= stage.stage.program->info.uses_image_heap;
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
pipeline.uses_image_heap |= ps_input_info->stage.program->info.uses_image_heap;
|
||||
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,
|
||||
@@ -498,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);
|
||||
@@ -508,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,
|
||||
@@ -555,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;
|
||||
@@ -573,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);
|
||||
@@ -586,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;
|
||||
@@ -616,9 +425,8 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
}
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
pipeline.uses_image_heap = input_info.stage.program->info.uses_image_heap;
|
||||
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};
|
||||
@@ -632,18 +440,12 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
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;
|
||||
@@ -652,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;
|
||||
@@ -50,9 +45,10 @@ enum class DrawOffsetSource : uint8_t {
|
||||
|
||||
// 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;
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
ShaderIndirectOffsets offsets;
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
@@ -198,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;
|
||||
@@ -312,16 +310,14 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
const auto compute_program =
|
||||
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
|
||||
if (!compute_program) return;
|
||||
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) ||
|
||||
@@ -351,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) {
|
||||
@@ -502,16 +497,14 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
input_info.async_compute = async_compute;
|
||||
input_info.indirect_args_address = args_addr;
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
if (!compute_program) return;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -10,13 +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_image_heap), 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_image_heap(*this, m_page_manager) {
|
||||
m_image_heap(*this, m_page_manager), m_occlusion_queries(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -134,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;
|
||||
}
|
||||
|
||||
@@ -143,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,6 +11,7 @@
|
||||
#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"
|
||||
@@ -30,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);
|
||||
|
||||
@@ -49,6 +51,7 @@ public:
|
||||
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);
|
||||
@@ -79,6 +82,7 @@ private:
|
||||
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,
|
||||
@@ -870,62 +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();
|
||||
buffer.GetContext().GetImageHeap().BeginDraw();
|
||||
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) {
|
||||
@@ -1124,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;
|
||||
@@ -1203,10 +1143,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
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,
|
||||
@@ -1238,7 +1175,6 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (mesh_active) {
|
||||
draw_data = {draw.index_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset),
|
||||
emit.first_instance,
|
||||
index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
@@ -1284,6 +1220,8 @@ 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 if (args_buffer.first == nullptr) {
|
||||
@@ -1295,6 +1233,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
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);
|
||||
|
||||
@@ -1311,8 +1250,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
||||
// mesh and quad-list draws do. Indirect records carry their own offsets.
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need the CPU: mesh
|
||||
// and quad-list draws for counts, tessellation draws for patched SGPRs. Indirect records carry
|
||||
// their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
@@ -1320,8 +1260,9 @@ static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig&
|
||||
uint32_t first_instance,
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.program->stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
||||
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};
|
||||
@@ -1385,8 +1326,8 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1407,7 +1348,7 @@ bool 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) {
|
||||
@@ -1415,7 +1356,8 @@ bool 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);
|
||||
@@ -1423,7 +1365,6 @@ bool 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 true;
|
||||
@@ -1434,8 +1375,7 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
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, topology, *emit, index_source,
|
||||
primitive_restart);
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, *emit, index_source);
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
@@ -1491,12 +1431,11 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
DrawRenderState state {.indirect_offsets = args.indirect.offsets};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
ResetBindings();
|
||||
return true;
|
||||
}
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return true;
|
||||
@@ -1504,7 +1443,7 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
|
||||
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: "
|
||||
@@ -1521,7 +1460,7 @@ bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
static_cast<int32_t>(args.first_vertex), args.first_instance,
|
||||
args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, *emit, {}, false);
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, *emit, {});
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
@@ -1556,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
@@ -533,16 +533,16 @@ void DrawMemoryAndIo(const Snapshot& shown) {
|
||||
Bytes(guest.pooled_allocated) + " (" + Bytes(guest.pool_committed) + " committed)");
|
||||
Row("Flexible", Bytes(guest.flexible_used) + " / " + Bytes(guest.flexible_total));
|
||||
Row("Extended", Bytes(guest.automatic_allocated));
|
||||
// Past the pool size the guest sees no page-table entries available.
|
||||
const auto entries = std::max(guest.cpu_page_entries, guest.gpu_page_entries);
|
||||
// Past its total a side sees no page-table entries available.
|
||||
const auto percent = std::max(guest.cpu_page_entries * 100 / guest.cpu_page_entries_total,
|
||||
guest.gpu_page_entries * 100 / guest.gpu_page_entries_total);
|
||||
Row("Page table (2 MB)",
|
||||
Fmt("CPU %llu GPU %llu / %llu",
|
||||
Fmt("CPU %llu / %llu GPU %llu / %llu",
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.cpu_page_entries_total),
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries),
|
||||
static_cast<unsigned long long>(guest.page_entries_total)),
|
||||
entries > guest.page_entries_total ? BAD
|
||||
: entries * 10 > guest.page_entries_total * 9 ? MEDIUM
|
||||
: GOOD);
|
||||
static_cast<unsigned long long>(guest.gpu_page_entries_total)),
|
||||
LevelColor(static_cast<double>(percent), 90, 100));
|
||||
ImGui::EndTable();
|
||||
}
|
||||
if (BeginSection("Emulator process")) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -277,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",
|
||||
@@ -336,6 +340,7 @@ 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");
|
||||
@@ -506,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 {};
|
||||
@@ -593,6 +605,11 @@ 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",
|
||||
@@ -623,6 +640,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
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;
|
||||
@@ -642,6 +660,16 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
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 &&
|
||||
@@ -702,10 +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());
|
||||
@@ -1077,13 +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_EXT_CONDITIONAL_RENDERING_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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <optional>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
@@ -62,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);
|
||||
@@ -113,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;
|
||||
@@ -132,11 +146,26 @@ 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;
|
||||
}
|
||||
@@ -263,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;
|
||||
@@ -302,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;
|
||||
@@ -316,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));
|
||||
}
|
||||
}
|
||||
@@ -332,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) {
|
||||
@@ -535,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 =
|
||||
@@ -683,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),
|
||||
@@ -783,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);
|
||||
@@ -793,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();
|
||||
@@ -815,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,
|
||||
@@ -851,14 +901,11 @@ 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) {
|
||||
@@ -881,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;
|
||||
}
|
||||
|
||||
@@ -810,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;
|
||||
@@ -845,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};
|
||||
};
|
||||
@@ -1069,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;
|
||||
@@ -1081,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;
|
||||
@@ -1104,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,10 +230,12 @@ 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);
|
||||
@@ -240,6 +243,7 @@ uint32_t EmitGetAttributeF16(ValueEmitContext& ctx, const IR::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);
|
||||
@@ -287,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;
|
||||
@@ -145,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;
|
||||
@@ -282,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;
|
||||
};
|
||||
@@ -305,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);
|
||||
|
||||
@@ -414,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);
|
||||
@@ -531,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();
|
||||
|
||||
@@ -154,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;
|
||||
@@ -174,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,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;
|
||||
}
|
||||
|
||||
@@ -472,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));
|
||||
@@ -565,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,
|
||||
|
||||
@@ -476,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
case IR::StageInputKind::LocalInvocationId: return spv::BuiltInLocalInvocationId;
|
||||
case IR::StageInputKind::LocalInvocationIndex: return spv::BuiltInLocalInvocationIndex;
|
||||
case IR::StageInputKind::GlobalInvocationId: return spv::BuiltInGlobalInvocationId;
|
||||
case IR::StageInputKind::BaseVertex: return spv::BuiltInBaseVertex;
|
||||
case IR::StageInputKind::BaseInstance: return spv::BuiltInBaseInstance;
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
@@ -526,7 +528,9 @@ void DefineInputs(EmitterState& state) {
|
||||
case IR::StageInputKind::PrimitiveId:
|
||||
case IR::StageInputKind::InstanceIndex:
|
||||
case IR::StageInputKind::Layer:
|
||||
case IR::StageInputKind::SampleId: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::SampleId:
|
||||
case IR::StageInputKind::BaseVertex:
|
||||
case IR::StageInputKind::BaseInstance: type = TypeI32(state); break;
|
||||
case IR::StageInputKind::WorkgroupId:
|
||||
case IR::StageInputKind::NumWorkgroups:
|
||||
case IR::StageInputKind::LocalInvocationId:
|
||||
@@ -595,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) {
|
||||
@@ -691,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 &&
|
||||
@@ -793,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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -22,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[] = {
|
||||
@@ -69,6 +69,7 @@ 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},
|
||||
@@ -95,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[] = {
|
||||
@@ -285,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) {
|
||||
@@ -419,9 +423,10 @@ void DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
}
|
||||
|
||||
DecodeVectorGpr(IsFlatStoreOpcode(inst.opcode) ? data : vdst, inst.dst);
|
||||
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:
|
||||
@@ -681,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:
|
||||
@@ -697,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,6 +544,7 @@ 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,
|
||||
@@ -594,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,
|
||||
@@ -610,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,
|
||||
@@ -705,15 +710,7 @@ 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,
|
||||
@@ -764,7 +761,9 @@ enum class OperandKind {
|
||||
M0,
|
||||
PopsExitingWaveId,
|
||||
SharedBase,
|
||||
SharedLimit,
|
||||
PrivateBase,
|
||||
PrivateLimit,
|
||||
Null,
|
||||
Vgpr,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -865,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) {
|
||||
@@ -903,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))),
|
||||
@@ -945,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);
|
||||
@@ -969,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:
|
||||
@@ -1041,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:
|
||||
@@ -1158,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:
|
||||
@@ -1177,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);
|
||||
|
||||
@@ -25,444 +25,310 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_PERMLANEX16_B32: V_PERMLANE16_B32(inst, true); return;
|
||||
|
||||
case O::V_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;
|
||||
@@ -495,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);
|
||||
@@ -563,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);
|
||||
@@ -574,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);
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SERIALIZE_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SERIALIZE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Shader cache records. Io(io, value) appends value to a Writer or reads it back from a Reader
|
||||
// field by field, so padding never reaches the bytes; instructions travel as plan-local indices.
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
struct Writer {
|
||||
std::vector<uint8_t> bytes;
|
||||
std::unordered_map<const Inst*, uint32_t> index; // Instructions that values may refer to.
|
||||
|
||||
void Raw(const void* data, size_t size) {
|
||||
const auto* begin = static_cast<const uint8_t*>(data);
|
||||
bytes.insert(bytes.end(), begin, begin + size);
|
||||
}
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
std::span<const uint8_t> bytes;
|
||||
std::vector<Inst*> insts; // Instructions by index.
|
||||
bool ok = true; // False once a read ran past the end or was invalid.
|
||||
|
||||
void Raw(void* data, size_t size) {
|
||||
ok = ok && size <= bytes.size();
|
||||
if (!ok) return;
|
||||
std::copy_n(bytes.data(), size, static_cast<uint8_t*>(data));
|
||||
bytes = bytes.subspan(size);
|
||||
}
|
||||
// Every element takes at least one byte, which bounds counts read from damaged records.
|
||||
uint32_t Count(uint32_t count) {
|
||||
ok = ok && count <= bytes.size();
|
||||
return ok ? count : 0u;
|
||||
}
|
||||
};
|
||||
|
||||
template <class S> constexpr bool Reading = std::is_same_v<S, Reader>;
|
||||
template <class T, template <class...> class Of> constexpr bool IsA = false;
|
||||
template <template <class...> class Of, class... A> constexpr bool IsA<Of<A...>, Of> = true;
|
||||
template <class T, class U> concept Is = std::is_same_v<std::remove_const_t<T>, U>;
|
||||
|
||||
template <class S, class T> void Io(S& io, T& value);
|
||||
|
||||
template <class S, class... T> void IoAll(S& io, T&&... values) {
|
||||
(Io(io, values), ...);
|
||||
}
|
||||
|
||||
// The value's Type, Opaque for an instruction, then the instruction index or the zero-extended
|
||||
// immediate.
|
||||
inline void IoValue(Writer& writer, const Value& value) {
|
||||
auto type = Type::Opaque;
|
||||
uint64_t bits = 0;
|
||||
if (const auto* inst = value.TryInstruction()) {
|
||||
bits = writer.index.at(inst);
|
||||
} else {
|
||||
type = value.GetType();
|
||||
switch (type) {
|
||||
case Type::Void: break;
|
||||
case Type::ScalarReg: bits = RegIndex(value.ScalarRegister()); break;
|
||||
case Type::VectorReg: bits = RegIndex(value.VectorRegister()); break;
|
||||
case Type::U1: bits = value.U1(); break;
|
||||
case Type::U8: bits = value.U8(); break;
|
||||
case Type::U16: bits = value.U16(); break;
|
||||
case Type::U32: bits = value.U32(); break;
|
||||
case Type::U64: bits = value.U64(); break;
|
||||
case Type::F16: bits = value.F16Bits(); break;
|
||||
case Type::F32: bits = std::bit_cast<uint32_t>(value.F32Value()); break;
|
||||
default: EXIT("value type %u has no cache encoding\n", static_cast<uint32_t>(type));
|
||||
}
|
||||
}
|
||||
IoAll(writer, type, bits);
|
||||
}
|
||||
|
||||
inline void IoValue(Reader& reader, Value& value) {
|
||||
auto type = Type::Void;
|
||||
uint64_t bits = 0;
|
||||
IoAll(reader, type, bits);
|
||||
switch (type) {
|
||||
case Type::Void: value = {}; break;
|
||||
case Type::Opaque:
|
||||
reader.ok = reader.ok && bits < reader.insts.size();
|
||||
if (reader.ok) value = Value(reader.insts[bits]);
|
||||
break;
|
||||
case Type::ScalarReg: value = Value(static_cast<ScalarReg>(bits)); break;
|
||||
case Type::VectorReg: value = Value(static_cast<VectorReg>(bits)); break;
|
||||
case Type::U1: value = Value(bits != 0u); break;
|
||||
case Type::U8: value = Value(static_cast<uint8_t>(bits)); break;
|
||||
case Type::U16: value = Value(static_cast<uint16_t>(bits)); break;
|
||||
case Type::U32: value = Value(static_cast<uint32_t>(bits)); break;
|
||||
case Type::U64: value = Value(bits); break;
|
||||
case Type::F16: value = Value::F16(static_cast<uint16_t>(bits)); break;
|
||||
case Type::F32: value = Value::F32(std::bit_cast<float>(static_cast<uint32_t>(bits))); break;
|
||||
default: reader.ok = false; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Scalars and standard containers; structs have their own overloads below.
|
||||
template <class S, class T> void Io(S& io, T& value) {
|
||||
using U = std::remove_const_t<T>;
|
||||
static_assert(!std::is_pointer_v<U>, "pointers do not persist");
|
||||
if constexpr (std::is_arithmetic_v<U> || std::is_enum_v<U>) {
|
||||
io.Raw(&value, sizeof(U));
|
||||
} else if constexpr (std::is_same_v<U, Value>) {
|
||||
IoValue(io, value);
|
||||
} else if constexpr (IsA<U, std::vector> || IsA<U, std::basic_string>) {
|
||||
auto count = static_cast<uint32_t>(value.size());
|
||||
Io(io, count);
|
||||
if constexpr (Reading<S>) value.resize(io.Count(count));
|
||||
using E = typename U::value_type;
|
||||
if constexpr (std::is_arithmetic_v<E> || std::is_enum_v<E>) {
|
||||
io.Raw(value.data(), value.size() * sizeof(E));
|
||||
} else {
|
||||
for (auto& element: value) Io(io, element);
|
||||
}
|
||||
} else if constexpr (IsA<U, std::optional>) {
|
||||
bool has = value.has_value();
|
||||
Io(io, has);
|
||||
if constexpr (Reading<S>) {
|
||||
if (has) value = typename U::value_type {}; // Not emplace(): clang rejects nested aggregates.
|
||||
else value.reset();
|
||||
}
|
||||
if (has) Io(io, *value);
|
||||
} else {
|
||||
static_assert(requires { std::tuple_size<U>::value; }, "struct without an Io overload");
|
||||
std::apply([&](auto&... elements) { IoAll(io, elements...); }, value); // std::array, std::pair
|
||||
}
|
||||
}
|
||||
|
||||
// Binds every field, so that a field added later fails to compile until it is listed.
|
||||
#define KYTY_IO_FIELDS(Struct, ...) \
|
||||
template <class S, Is<Struct> T> void Io(S& io, T& object) { \
|
||||
auto& [__VA_ARGS__] = object; \
|
||||
IoAll(io, __VA_ARGS__); \
|
||||
}
|
||||
KYTY_IO_FIELDS(MemoryInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y)
|
||||
KYTY_IO_FIELDS(BufferResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
|
||||
KYTY_IO_FIELDS(ImageResource, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w)
|
||||
KYTY_IO_FIELDS(SamplerResource, a, b, c, d, e, f, g, h, i, j, k)
|
||||
KYTY_IO_FIELDS(SampledResourcePair, a, b, c)
|
||||
KYTY_IO_FIELDS(StageInput, a, b, c, d, e)
|
||||
KYTY_IO_FIELDS(StageOutput, a, b, c, d)
|
||||
KYTY_IO_FIELDS(ShaderInfo, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
|
||||
A, B, C, D, E, F, G, H, I, J, K)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::CountedRows, a, b)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks, a, b, c)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::SelectorRead, a, b, c, d, e, f, g, h, i, j, k,
|
||||
l)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p,
|
||||
q, r)
|
||||
KYTY_IO_FIELDS(DescriptorSource, a, b, c)
|
||||
KYTY_IO_FIELDS(SrtRead, a, b)
|
||||
KYTY_IO_FIELDS(ResourceBlock, a, b, c, d, e)
|
||||
KYTY_IO_FIELDS(UniformFill, a, b, c, d, e, f)
|
||||
KYTY_IO_FIELDS(UniformFillPlan, a, b, c, d)
|
||||
KYTY_IO_FIELDS(ResourceSpecialization::Buffer, a, b, c, d, e, f, g, h)
|
||||
KYTY_IO_FIELDS(ResourceSpecialization::Image, a, b, c, d, e, f, g, h, i, j, k)
|
||||
KYTY_IO_FIELDS(ResourceSpecialization::Sampler, a, b, c)
|
||||
KYTY_IO_FIELDS(ResourceSpecialization, a, b, c)
|
||||
KYTY_IO_FIELDS(Bindings, a, b, c, d, e)
|
||||
KYTY_IO_FIELDS(CompiledShaderInfo, a, b, c, d, e, f, g, h, i, j)
|
||||
KYTY_IO_FIELDS(Spirv::Profile, a, b, c)
|
||||
KYTY_IO_FIELDS(ShaderTessellationInputInfo, a, b, c, d, e, f, g, h, i)
|
||||
KYTY_IO_FIELDS(RectListParameter, a, b, c)
|
||||
#undef KYTY_IO_FIELDS
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SERIALIZE_H_ */
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -28,8 +27,6 @@ class CodeGenerator;
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
class SrtWalker;
|
||||
|
||||
// Finite descriptor domains also bound the arithmetic used to prove their indices.
|
||||
constexpr uint32_t MaxIndirectDescriptorProbes = 65536u;
|
||||
|
||||
@@ -254,6 +251,8 @@ enum class StageInputKind {
|
||||
LocalInvocationId,
|
||||
LocalInvocationIndex,
|
||||
GlobalInvocationId,
|
||||
BaseVertex,
|
||||
BaseInstance,
|
||||
Parameter,
|
||||
};
|
||||
|
||||
@@ -368,7 +367,7 @@ static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 58u);
|
||||
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
static constexpr uint32_t MeshDrawDwordCount = 6;
|
||||
static constexpr uint32_t MeshDrawDwordCount = 5;
|
||||
static constexpr uint32_t NoStart = UINT32_MAX;
|
||||
std::array<uint32_t, DwordCount> dwords {};
|
||||
|
||||
@@ -498,10 +497,16 @@ struct Bindings {
|
||||
uint32_t dispatch_thread_dword = PushData::NoStart;
|
||||
uint32_t memory_offset_dword = 0;
|
||||
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
// Packed 8-bit binding offsets, then each binding's bound range in bytes: OpArrayLength
|
||||
// reads 0 for ranges of 2 GiB or more on NVIDIA.
|
||||
[[nodiscard]] uint32_t MemorySizeDword() const {
|
||||
return memory_offset_dword +
|
||||
(descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)] + 3u) / 4u;
|
||||
}
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
return MemorySizeDword() +
|
||||
descriptor_counts[static_cast<size_t>(DescriptorBindingKind::Buffers)];
|
||||
}
|
||||
[[nodiscard]] bool UsesPushData() const {
|
||||
return push_data_start_dword != PushData::NoStart;
|
||||
}
|
||||
@@ -544,6 +549,7 @@ struct ShaderInfo {
|
||||
bool subgroup_shuffle = false;
|
||||
bool subgroup_arithmetic = false;
|
||||
bool subgroup_local_invocation_id = false;
|
||||
bool helper_invocation = false;
|
||||
bool compute_derivatives = false;
|
||||
bool image_gather_extended = false;
|
||||
bool function_lds = false;
|
||||
@@ -568,6 +574,13 @@ struct DescriptorSource {
|
||||
|
||||
bool operator==(const CountedRows& other) const = default;
|
||||
};
|
||||
struct MaskRanks {
|
||||
uint32_t source = UINT32_MAX;
|
||||
uint32_t stride = 0; // Byte spacing of the mask words its load can address.
|
||||
uint32_t offset = 0;
|
||||
|
||||
bool operator==(const MaskRanks& other) const = default;
|
||||
};
|
||||
uint32_t source = UINT32_MAX;
|
||||
uint32_t stride = 0;
|
||||
uint32_t offset = 0;
|
||||
@@ -575,10 +588,12 @@ struct DescriptorSource {
|
||||
uint32_t bit_offset = 0;
|
||||
uint32_t bit_mask = UINT32_MAX;
|
||||
bool global = false;
|
||||
// Four DWORD bitmap words per record; set-bit ranks subtract from the last key.
|
||||
// Four or eight DWORD bitmap words per record; set ranks subtract from the last key.
|
||||
bool bitset = false;
|
||||
std::optional<CountedRows> rows;
|
||||
std::vector<std::pair<uint32_t, uint32_t>> byte_ranges;
|
||||
std::optional<uint32_t> excluded; // A guard keeps this key from the read.
|
||||
std::optional<MaskRanks> ranks; // Records are the bits some mask word sets.
|
||||
|
||||
bool operator==(const SelectorRead& other) const = default;
|
||||
};
|
||||
@@ -602,8 +617,12 @@ struct DescriptorSource {
|
||||
bool indexed = false;
|
||||
bool signed_count = false;
|
||||
uint32_t workgroup_axis = UINT32_MAX;
|
||||
// Nonzero shift: workgroup keys are (selector_first + ID) * multiplier >> shift.
|
||||
uint32_t workgroup_multiplier = 0;
|
||||
uint32_t workgroup_shift = 0;
|
||||
Value key_count;
|
||||
// First selector index, or first descriptor key for a direct or bitmap range.
|
||||
// First selector index, workgroup key base, or first descriptor key for a direct or bitmap
|
||||
// range.
|
||||
Value selector_first;
|
||||
Value selector_mask;
|
||||
std::vector<uint32_t> sources;
|
||||
@@ -630,6 +649,7 @@ struct ResourceBlock {
|
||||
Value condition;
|
||||
std::vector<uint32_t> successors;
|
||||
std::vector<uint32_t> sources;
|
||||
std::vector<uint32_t> images;
|
||||
std::vector<uint32_t> srt_reads;
|
||||
};
|
||||
|
||||
@@ -658,24 +678,10 @@ struct UniformFillPlan {
|
||||
// condition and fill values without translated blocks, plus reusable evaluation scratch.
|
||||
struct ResourcePlan {
|
||||
struct SelectorKeys {
|
||||
uint32_t source = UINT32_MAX;
|
||||
std::vector<uint32_t> values;
|
||||
bool resolved = false;
|
||||
};
|
||||
|
||||
struct EvaluationContext {
|
||||
struct Entry {
|
||||
uint64_t value = 0;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
std::vector<Entry> values;
|
||||
uint64_t generation = 0;
|
||||
const SrtWalker* session = nullptr;
|
||||
Value active_mask;
|
||||
uint8_t read_policy = 0;
|
||||
};
|
||||
|
||||
ResourcePlan();
|
||||
~ResourcePlan();
|
||||
|
||||
@@ -695,21 +701,18 @@ struct ResourcePlan {
|
||||
std::vector<ResourceBlock> control_flow;
|
||||
std::unordered_map<const Inst*, std::vector<uint32_t>> control_phi_predecessors;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
bool requires_specialization_memory = false;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> source_reads;
|
||||
bool capture_specialization_reads = false;
|
||||
bool srt_plan_complete = false;
|
||||
bool resource_tracking_complete = false;
|
||||
ShaderInfo info;
|
||||
UniformFillPlan uniform_fill;
|
||||
// x86 walker over the retained SRT, descriptor and condition values.
|
||||
std::unique_ptr<Xbyak::CodeGenerator> srt_code;
|
||||
// GPU-thread scratch for nested clean/EXEC memos, activity and material keys.
|
||||
mutable std::deque<EvaluationContext> evaluation_contexts;
|
||||
mutable uint32_t evaluation_value_count = 0;
|
||||
mutable std::vector<uint8_t> active_sources;
|
||||
mutable std::vector<uint8_t> visited_blocks;
|
||||
mutable std::vector<uint8_t> reachable_blocks;
|
||||
mutable std::vector<uint32_t> pending_blocks;
|
||||
uint32_t srt_user_data = 0; // User-data DWORDs the walker reads.
|
||||
uint32_t srt_captures = 0; // Scratch offset of loaded addresses.
|
||||
// GPU-thread scratch for walker results and material keys.
|
||||
mutable std::vector<uint64_t> srt_scratch;
|
||||
mutable std::vector<uint32_t> material_keys;
|
||||
mutable std::vector<SelectorKeys> selector_keys;
|
||||
};
|
||||
|
||||
@@ -25,22 +25,6 @@ Value Value::F32(float value) {
|
||||
return Value(Type::F32, std::bit_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
bool Value::IsEmpty() const {
|
||||
return type == Type::Void;
|
||||
}
|
||||
|
||||
bool Value::IsImmediate() const {
|
||||
return type != Type::Opaque;
|
||||
}
|
||||
|
||||
bool Value::IsIdentity() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
||||
}
|
||||
|
||||
bool Value::IsPhi() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
||||
}
|
||||
|
||||
Type Value::GetType() const {
|
||||
if (IsPhi()) {
|
||||
return inst->Flags<Type>();
|
||||
@@ -56,19 +40,6 @@ Inst* Value::Instruction() const {
|
||||
return inst;
|
||||
}
|
||||
|
||||
Inst* Value::TryInstruction() const {
|
||||
return type == Type::Opaque ? inst : nullptr;
|
||||
}
|
||||
|
||||
Inst* Value::ResolveInstruction() const {
|
||||
EXIT_IF(type != Type::Opaque);
|
||||
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
||||
}
|
||||
|
||||
Value Value::Resolve() const {
|
||||
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
||||
}
|
||||
|
||||
ScalarReg Value::ScalarRegister() const {
|
||||
EXIT_IF(type != Type::ScalarReg);
|
||||
return scalar_reg;
|
||||
@@ -94,11 +65,6 @@ uint16_t Value::U16() const {
|
||||
return imm_u16;
|
||||
}
|
||||
|
||||
uint32_t Value::U32() const {
|
||||
EXIT_IF(type != Type::U32);
|
||||
return imm_u32;
|
||||
}
|
||||
|
||||
uint64_t Value::U64() const {
|
||||
EXIT_IF(type != Type::U64);
|
||||
return imm_u64;
|
||||
@@ -152,10 +118,6 @@ Inst::~Inst() {
|
||||
ClearArgs();
|
||||
}
|
||||
|
||||
ValueOpcode Inst::GetOpcode() const {
|
||||
return opcode;
|
||||
}
|
||||
|
||||
Type Inst::GetType() const {
|
||||
if (opcode == ValueOpcode::Phi) {
|
||||
return static_cast<Type>(flags);
|
||||
@@ -178,51 +140,25 @@ size_t Inst::UseCount() const {
|
||||
return uses.size();
|
||||
}
|
||||
|
||||
size_t Inst::NumArgs() const {
|
||||
return num_args == PhiArity ? phi_args.size() : num_args;
|
||||
}
|
||||
|
||||
size_t Inst::NumPhiBlocks() const {
|
||||
return num_args == PhiArity ? phi_args.size() : 0;
|
||||
}
|
||||
|
||||
Value Inst::Arg(size_t index) const {
|
||||
EXIT_IF(index >= NumArgs());
|
||||
if (num_args <= InlineArity) {
|
||||
return fixed_args[index];
|
||||
}
|
||||
return num_args == PhiArity ? phi_args[index].second : large_args[index];
|
||||
}
|
||||
|
||||
Block* Inst::PhiBlock(size_t index) const {
|
||||
EXIT_IF(opcode != ValueOpcode::Phi || index >= phi_args.size());
|
||||
return phi_args[index].first;
|
||||
}
|
||||
|
||||
Block* Inst::Parent() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
const std::vector<Use>& Inst::Uses() const {
|
||||
return uses;
|
||||
}
|
||||
|
||||
void Inst::SetParent(Block* block) {
|
||||
parent = block;
|
||||
}
|
||||
|
||||
void Inst::SetArg(size_t index, Value value) {
|
||||
const auto old = Arg(index);
|
||||
if (auto* old_inst = old.TryInstruction(); old_inst != nullptr) {
|
||||
auto& arg = ArgSlot(*this, index);
|
||||
if (auto* old_inst = arg.TryInstruction(); old_inst != nullptr) {
|
||||
RemoveUse(old_inst, index);
|
||||
}
|
||||
if (num_args <= InlineArity) {
|
||||
fixed_args[index] = value;
|
||||
} else if (num_args == PhiArity) {
|
||||
phi_args[index].second = value;
|
||||
} else {
|
||||
large_args[index] = value;
|
||||
}
|
||||
arg = value;
|
||||
if (auto* new_inst = value.TryInstruction(); new_inst != nullptr) {
|
||||
AddUse(new_inst, index);
|
||||
}
|
||||
@@ -238,11 +174,15 @@ void Inst::AddPhiOperand(Block* predecessor, Value value) {
|
||||
}
|
||||
|
||||
void Inst::ReplaceUsesWith(Value replacement, bool preserve) {
|
||||
const auto old_uses = uses;
|
||||
for (const auto& use: old_uses) {
|
||||
use.user->SetArg(use.operand, replacement);
|
||||
}
|
||||
// Detach the arguments first (a phi can use itself), then move every use over in order.
|
||||
Invalidate();
|
||||
const auto old_uses = std::exchange(uses, {});
|
||||
for (const auto& use: old_uses) {
|
||||
ArgSlot(*use.user, use.operand) = replacement;
|
||||
}
|
||||
if (auto* target = replacement.TryInstruction(); target != nullptr) {
|
||||
target->uses.insert(target->uses.end(), old_uses.begin(), old_uses.end());
|
||||
}
|
||||
if (preserve) {
|
||||
opcode = ValueOpcode::Identity;
|
||||
num_args = 1;
|
||||
@@ -256,9 +196,6 @@ void Inst::Invalidate() {
|
||||
}
|
||||
|
||||
void Inst::AddUse(Inst* used, size_t operand) {
|
||||
const auto found = std::ranges::find_if(
|
||||
used->uses, [&](const Use& use) { return use.user == this && use.operand == operand; });
|
||||
EXIT_IF(found != used->uses.end());
|
||||
used->uses.push_back({this, operand});
|
||||
}
|
||||
|
||||
@@ -275,6 +212,10 @@ void Inst::ClearArgs() {
|
||||
RemoveUse(value_inst, index);
|
||||
}
|
||||
}
|
||||
DropArgs();
|
||||
}
|
||||
|
||||
void Inst::DropArgs() {
|
||||
if (num_args == PhiArity) {
|
||||
std::destroy_at(&phi_args);
|
||||
std::construct_at(&fixed_args);
|
||||
|
||||
@@ -33,14 +33,14 @@ public:
|
||||
static Value F16(uint16_t bits);
|
||||
static Value F32(float value);
|
||||
|
||||
[[nodiscard]] bool IsEmpty() const;
|
||||
[[nodiscard]] bool IsImmediate() const;
|
||||
[[nodiscard]] bool IsEmpty() const { return type == Type::Void; }
|
||||
[[nodiscard]] bool IsImmediate() const { return type != Type::Opaque; }
|
||||
[[nodiscard]] bool IsIdentity() const;
|
||||
[[nodiscard]] bool IsPhi() const;
|
||||
[[nodiscard]] Type GetType() const;
|
||||
|
||||
[[nodiscard]] Inst* Instruction() const;
|
||||
[[nodiscard]] Inst* TryInstruction() const;
|
||||
[[nodiscard]] Inst* TryInstruction() const { return type == Type::Opaque ? inst : nullptr; }
|
||||
[[nodiscard]] Inst* ResolveInstruction() const;
|
||||
[[nodiscard]] Value Resolve() const;
|
||||
[[nodiscard]] ScalarReg ScalarRegister() const;
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
Inst(Inst&&) = delete;
|
||||
Inst& operator=(Inst&&) = delete;
|
||||
|
||||
[[nodiscard]] ValueOpcode GetOpcode() const;
|
||||
[[nodiscard]] ValueOpcode GetOpcode() const { return opcode; }
|
||||
[[nodiscard]] Type GetType() const;
|
||||
[[nodiscard]] bool MayHaveSideEffects() const;
|
||||
[[nodiscard]] bool HasUses() const;
|
||||
@@ -121,15 +121,11 @@ public:
|
||||
[[nodiscard]] size_t NumPhiBlocks() const;
|
||||
[[nodiscard]] Value Arg(size_t index) const;
|
||||
[[nodiscard]] Block* PhiBlock(size_t index) const;
|
||||
[[nodiscard]] Block* Parent() const;
|
||||
[[nodiscard]] const std::vector<Use>& Uses() const;
|
||||
// Runtime indices belong to the resource plan that owns this instruction.
|
||||
[[nodiscard]] uint32_t EvaluationIndex(uint32_t& count) const {
|
||||
if (evaluation_index == UINT32_MAX) {
|
||||
evaluation_index = count++;
|
||||
}
|
||||
return evaluation_index;
|
||||
}
|
||||
[[nodiscard]] Block* Parent() const { return parent; }
|
||||
[[nodiscard]] const std::vector<Use>& Uses() const { return uses; }
|
||||
// SRT walker output index, assigned by the resource plan that owns this instruction.
|
||||
[[nodiscard]] uint32_t WalkOutput() const { return walk_output; }
|
||||
void SetWalkOutput(uint32_t index) const { walk_output = index; }
|
||||
// Backend definitions belong to one emission. Wave64 emulation needs one per half.
|
||||
[[nodiscard]] uint32_t Definition(uint32_t half) const { return definitions[half]; }
|
||||
void SetDefinition(uint32_t half, uint32_t definition) const {
|
||||
@@ -142,6 +138,8 @@ public:
|
||||
void AddPhiOperand(Block* predecessor, Value value);
|
||||
void ReplaceUsesWith(Value replacement, bool preserve = true);
|
||||
void Invalidate();
|
||||
// Drops the arguments without updating their use lists, for teardown that destroys both sides.
|
||||
void DropArgs();
|
||||
|
||||
template <typename T>
|
||||
requires(sizeof(T) <= sizeof(uint64_t) && std::is_trivially_copyable_v<T>)
|
||||
@@ -161,6 +159,8 @@ public:
|
||||
private:
|
||||
friend void EliminateDeadCode(const std::vector<Block*>& blocks);
|
||||
|
||||
template <typename Self>
|
||||
static auto& ArgSlot(Self& self, size_t index);
|
||||
void AddUse(Inst* used, size_t operand);
|
||||
void RemoveUse(Inst* used, size_t operand);
|
||||
void ClearArgs();
|
||||
@@ -171,7 +171,7 @@ private:
|
||||
ValueOpcode opcode;
|
||||
uint8_t num_args;
|
||||
bool live = false;
|
||||
mutable uint32_t evaluation_index = UINT32_MAX;
|
||||
mutable uint32_t walk_output = UINT32_MAX;
|
||||
mutable std::array<uint32_t, 2> definitions {};
|
||||
uint64_t flags;
|
||||
Block* parent = nullptr;
|
||||
@@ -185,4 +185,44 @@ private:
|
||||
|
||||
static_assert(sizeof(Inst) <= 120, "Inst operand and backend storage unintentionally increased");
|
||||
|
||||
// Called per operand by the IR passes and the resource walker; inline, as the build has no LTO.
|
||||
inline bool Value::IsIdentity() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Identity;
|
||||
}
|
||||
|
||||
inline bool Value::IsPhi() const {
|
||||
return type == Type::Opaque && inst->GetOpcode() == ValueOpcode::Phi;
|
||||
}
|
||||
|
||||
inline Inst* Value::ResolveInstruction() const {
|
||||
EXIT_IF(type != Type::Opaque);
|
||||
return IsIdentity() ? inst->Arg(0).ResolveInstruction() : inst;
|
||||
}
|
||||
|
||||
inline Value Value::Resolve() const {
|
||||
return IsIdentity() ? inst->Arg(0).Resolve() : *this;
|
||||
}
|
||||
|
||||
inline uint32_t Value::U32() const {
|
||||
EXIT_IF(type != Type::U32);
|
||||
return imm_u32;
|
||||
}
|
||||
|
||||
inline size_t Inst::NumArgs() const {
|
||||
return num_args == PhiArity ? phi_args.size() : num_args;
|
||||
}
|
||||
|
||||
template <typename Self>
|
||||
auto& Inst::ArgSlot(Self& self, size_t index) {
|
||||
EXIT_IF(index >= self.NumArgs());
|
||||
if (self.num_args <= InlineArity) {
|
||||
return self.fixed_args[index];
|
||||
}
|
||||
return self.num_args == PhiArity ? self.phi_args[index].second : self.large_args[index];
|
||||
}
|
||||
|
||||
inline Value Inst::Arg(size_t index) const {
|
||||
return ArgSlot(*this, index);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -123,6 +123,7 @@ BufferAccess BufferAccessOf(ValueOpcode opcode) {
|
||||
case ValueOpcode::BufferAtomicISub32:
|
||||
case ValueOpcode::BufferAtomicSMin32:
|
||||
case ValueOpcode::BufferAtomicUMin32:
|
||||
case ValueOpcode::BufferAtomicUMin64:
|
||||
case ValueOpcode::BufferAtomicSMax32:
|
||||
case ValueOpcode::BufferAtomicUMax32:
|
||||
case ValueOpcode::BufferAtomicAnd32:
|
||||
@@ -140,6 +141,7 @@ uint32_t BufferComponentCount(ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case ValueOpcode::BufferAtomicSwap64:
|
||||
case ValueOpcode::BufferAtomicIAdd64:
|
||||
case ValueOpcode::BufferAtomicUMin64:
|
||||
case ValueOpcode::BufferAtomicAnd64:
|
||||
case ValueOpcode::BufferAtomicOr64:
|
||||
case ValueOpcode::LoadBufferU32x2:
|
||||
|
||||
@@ -10,6 +10,7 @@ VALUE_OPCODE(ConditionRef, U1, U1)
|
||||
VALUE_OPCODE(GetUserData, U32, ScalarReg)
|
||||
VALUE_OPCODE(GetDispatchThreadExtent, U32, U32)
|
||||
VALUE_OPCODE(GetShaderBase, U64)
|
||||
VALUE_OPCODE(ReadRealtimeClock, U64)
|
||||
VALUE_OPCODE(MeshDrawParameter, U32, U32)
|
||||
VALUE_OPCODE(MeshAllocate, Void, U32)
|
||||
VALUE_OPCODE(TessellationBase, U32, U32)
|
||||
@@ -95,6 +96,7 @@ VALUE_OPCODE(WqmU64, U64, U64)
|
||||
// Core value operations used to expand architectural side effects.
|
||||
VALUE_OPCODE(SelectU1, U1, U1, U1, U1)
|
||||
VALUE_OPCODE(SelectF32, F32, U1, F32, F32)
|
||||
VALUE_OPCODE(SelectF64, F64, U1, F64, F64)
|
||||
VALUE_OPCODE(IAdd32, U32, U32, U32)
|
||||
VALUE_OPCODE(IAdd64, U64, U64, U64)
|
||||
VALUE_OPCODE(ISub32, U32, U32, U32)
|
||||
@@ -217,11 +219,13 @@ VALUE_OPCODE(FPFract64, F64, F64)
|
||||
|
||||
VALUE_OPCODE(LaneId, U32)
|
||||
VALUE_OPCODE(Ballot, U32x4, U1)
|
||||
VALUE_OPCODE(LiveMask, U32x4)
|
||||
VALUE_OPCODE(ReadFirstLane, U32, U32, U1)
|
||||
VALUE_OPCODE(ReadLane, U32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupUMinU32, U32, U32)
|
||||
VALUE_OPCODE(SubgroupUMaxU32, U32, U32)
|
||||
VALUE_OPCODE(WriteLane, U32, U32, U32, U32)
|
||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(Permlane16U32, U32, U32, U32, U32, U1, U32)
|
||||
VALUE_OPCODE(PermuteU32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BpermuteU32, U32, U32, U32, U1)
|
||||
|
||||
@@ -265,6 +269,7 @@ VALUE_OPCODE(BufferAtomicIAdd64, U64, BufferResource, U32, U32, U32, U64, U1)
|
||||
VALUE_OPCODE(BufferAtomicISub32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicSMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicUMin32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicUMin64, U64, BufferResource, U32, U32, U32, U64, U1)
|
||||
VALUE_OPCODE(BufferAtomicSMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicUMax32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(BufferAtomicAnd32, U32, BufferResource, U32, U32, U32, U32, U1)
|
||||
@@ -307,8 +312,8 @@ VALUE_OPCODE(SharedAtomicOr32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicOr64, Void, U32, U64, U1)
|
||||
VALUE_OPCODE(SharedAtomicXor32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SharedAtomicMaskedOr32, Void, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U1)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U1)
|
||||
VALUE_OPCODE(DataAppend, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(DataConsume, U32, U32, U32, U32, U1)
|
||||
VALUE_OPCODE(SwizzleU32, U32, U32, U32, U1)
|
||||
|
||||
VALUE_OPCODE(ImageQueryDimensions, U32x4, ImageResource, ImageAddress)
|
||||
|
||||
@@ -167,6 +167,16 @@ bool ReplaceBinaryIdentity(Inst& inst, Type type, uint64_t identity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Whether one operand is the other's negation.
|
||||
bool IsComplementPair(const Inst& inst, ValueOpcode negation) {
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
const auto* negate = Arg(inst, arg).TryInstruction();
|
||||
if (negate != nullptr && negate->GetOpcode() == negation &&
|
||||
Arg(*negate, 0) == Arg(inst, arg ^ 1u)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FoldSelect(Inst& inst) {
|
||||
const auto condition = Arg(inst, 0);
|
||||
const auto true_value = Arg(inst, 1);
|
||||
@@ -626,8 +636,9 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
case ValueOpcode::BitwiseXor32:
|
||||
if (!FoldU32(inst, [opcode = inst.GetOpcode()](uint32_t a, uint32_t b) {
|
||||
return opcode == ValueOpcode::BitwiseOr32 ? a | b : a ^ b;
|
||||
})) {
|
||||
ReplaceBinaryIdentity(inst, Type::U32, 0u);
|
||||
}) && !ReplaceBinaryIdentity(inst, Type::U32, 0u) &&
|
||||
IsComplementPair(inst, ValueOpcode::BitwiseNot32)) {
|
||||
Replace(inst, Value(UINT32_MAX));
|
||||
}
|
||||
return;
|
||||
case ValueOpcode::BitwiseNot32: {
|
||||
@@ -802,6 +813,8 @@ void FoldInstruction(Block& block, Block::iterator instruction,
|
||||
Replace(inst, lhs.U1() ? lhs : rhs);
|
||||
} else if (IsImmediate(rhs, Type::U1)) {
|
||||
Replace(inst, rhs.U1() ? rhs : lhs);
|
||||
} else if (IsComplementPair(inst, ValueOpcode::LogicalNot)) {
|
||||
Replace(inst, Value(true));
|
||||
} else {
|
||||
const auto* left = lhs.TryInstruction();
|
||||
const auto* right = rhs.TryInstruction();
|
||||
@@ -892,8 +905,10 @@ private:
|
||||
if (inst.GetOpcode() != ValueOpcode::CompositeExtractU32x4 ||
|
||||
!Immediate(Arg(inst, 1), 0u)) return {};
|
||||
const auto* source = Arg(inst, 0).TryInstruction();
|
||||
return source != nullptr && source->GetOpcode() == ValueOpcode::Ballot
|
||||
? Arg(*source, 0) : Value {};
|
||||
if (source == nullptr) return {};
|
||||
// Helper lanes keep the per-lane EXEC, so the live mask projects to true.
|
||||
if (source->GetOpcode() == ValueOpcode::LiveMask) return Value(true);
|
||||
return source->GetOpcode() == ValueOpcode::Ballot ? Arg(*source, 0) : Value {};
|
||||
}
|
||||
|
||||
bool CanProject(Value value) {
|
||||
|
||||
@@ -99,89 +99,66 @@ Value GetRealValue(PhiMap& phi_map, Value source, uint32_t lane, uint32_t wave_s
|
||||
return Value(&phi);
|
||||
}
|
||||
|
||||
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction.
|
||||
// A full-EXEC prefix scan with both half-wave endpoint reads is a reduction. Wave64 first joins
|
||||
// the two rows of each half; a wave32 half is one row.
|
||||
// Fragment subgroups may have no physical invocation at either endpoint.
|
||||
class UMinReductionMatcher {
|
||||
class WaveReductionMatcher {
|
||||
public:
|
||||
explicit UMinReductionMatcher(Block* block): block(block) {}
|
||||
WaveReductionMatcher(const Block* block, ValueOpcode operation)
|
||||
: block(block), operation(operation) {}
|
||||
|
||||
Value Match(const Inst& minimum) {
|
||||
const auto* low = Get(minimum.Arg(0), ValueOpcode::ReadLane);
|
||||
const auto* high = Get(minimum.Arg(1), ValueOpcode::ReadLane);
|
||||
Value Match(const Inst& reduction, uint32_t wave_size) const {
|
||||
const auto* low = Get(reduction.Arg(0), ValueOpcode::ReadLane);
|
||||
const auto* high = Get(reduction.Arg(1), ValueOpcode::ReadLane);
|
||||
if (low == nullptr || high == nullptr || low->Arg(0).Resolve() != high->Arg(0).Resolve()) {
|
||||
return {};
|
||||
}
|
||||
const auto lane = [](Value value, uint32_t expected) {
|
||||
const auto lane = [&](Value value, uint32_t expected) {
|
||||
value = value.Resolve();
|
||||
return value.IsImmediate() && value.GetType() == Type::U32 &&
|
||||
value.U32() % 64u == expected;
|
||||
value.U32() % wave_size == expected;
|
||||
};
|
||||
if (!((lane(low->Arg(1), 31) && lane(high->Arg(1), 63)) ||
|
||||
(lane(low->Arg(1), 63) && lane(high->Arg(1), 31)))) {
|
||||
const auto half = wave_size / 2u;
|
||||
if (!((lane(low->Arg(1), half - 1u) && lane(high->Arg(1), wave_size - 1u)) ||
|
||||
(lane(low->Arg(1), wave_size - 1u) && lane(high->Arg(1), half - 1u)))) {
|
||||
return {};
|
||||
}
|
||||
const auto* joined = Get(Unwrap(low->Arg(0)), ValueOpcode::UMin32);
|
||||
if (wave_size == 32u) return MatchScan(low->Arg(0));
|
||||
const auto* joined = Get(low->Arg(0), operation);
|
||||
if (joined == nullptr) return {};
|
||||
for (size_t index = 0; index < 2; index++) {
|
||||
auto attempt = *this;
|
||||
const auto* cross = attempt.Get(attempt.Unwrap(joined->Arg(index)), ValueOpcode::Permlane16U32);
|
||||
const auto* cross = Get(joined->Arg(index), ValueOpcode::Permlane16U32);
|
||||
if (cross == nullptr) continue;
|
||||
const auto flags = cross->Flags<PermlaneFlags>();
|
||||
auto prefix = attempt.Unwrap(joined->Arg(1 - index));
|
||||
const auto flags = cross->Flags<PermlaneFlags>();
|
||||
const auto prefix = joined->Arg(1 - index).Resolve();
|
||||
if (!flags.x16 || flags.fetch_inactive || !flags.bound_control ||
|
||||
cross->Arg(1).Resolve() != Value(UINT32_MAX) ||
|
||||
cross->Arg(2).Resolve() != Value(UINT32_MAX) ||
|
||||
!attempt.FullExec(cross->Arg(3)) || attempt.Unwrap(cross->Arg(0)) != prefix) {
|
||||
cross->Arg(2).Resolve() != Value(UINT32_MAX) || !FullExec(cross->Arg(3)) ||
|
||||
cross->Arg(0).Resolve() != prefix) {
|
||||
continue;
|
||||
}
|
||||
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
||||
prefix = attempt.MatchRow(prefix, shift);
|
||||
}
|
||||
prefix = attempt.Unwrap(prefix);
|
||||
if (prefix.IsEmpty() || attempt.saved_exec.IsEmpty()) return prefix;
|
||||
const auto* seed = attempt.Get(prefix, ValueOpcode::SelectU32);
|
||||
if (seed != nullptr && seed->Arg(2).Resolve() == Value(UINT32_MAX)) {
|
||||
const auto predicate = seed->Arg(0).Resolve();
|
||||
const auto* subset = attempt.Get(predicate, ValueOpcode::LogicalAnd);
|
||||
if (predicate == attempt.saved_exec ||
|
||||
(subset != nullptr && (subset->Arg(0).Resolve() == attempt.saved_exec ||
|
||||
subset->Arg(1).Resolve() == attempt.saved_exec))) {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
if (const auto seed = MatchScan(prefix); !seed.IsEmpty()) return seed;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
// The row scan's input.
|
||||
Value MatchScan(Value prefix) const {
|
||||
for (const uint32_t shift: {8u, 4u, 2u, 1u}) {
|
||||
prefix = MatchRow(prefix, shift);
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
Inst* Get(Value value, ValueOpcode opcode) const {
|
||||
auto* inst = value.Resolve().TryInstruction();
|
||||
return inst != nullptr && inst->Parent() == block && inst->GetOpcode() == opcode
|
||||
? inst : nullptr;
|
||||
}
|
||||
|
||||
bool FullExec(Value predicate) {
|
||||
predicate = predicate.Resolve();
|
||||
if (predicate == Value(true)) return true;
|
||||
const auto* either = Get(predicate, ValueOpcode::LogicalOr);
|
||||
if (either == nullptr) return false;
|
||||
for (size_t index = 0; index < 2; index++) {
|
||||
const auto* negate = Get(either->Arg(index), ValueOpcode::LogicalNot);
|
||||
if (negate == nullptr || negate->Arg(0).Resolve() != either->Arg(1 - index).Resolve()) continue;
|
||||
const auto original = negate->Arg(0).Resolve();
|
||||
if (saved_exec.IsEmpty()) saved_exec = original;
|
||||
return saved_exec == original;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Value Unwrap(Value value) {
|
||||
while (const auto* select = Get(value, ValueOpcode::SelectU32)) {
|
||||
if (!FullExec(select->Arg(0))) break;
|
||||
value = select->Arg(1);
|
||||
}
|
||||
return value.Resolve();
|
||||
}
|
||||
// Constant propagation folds the x | ~x EXEC of s_orn2_saveexec to true.
|
||||
static bool FullExec(Value predicate) { return predicate.Resolve() == Value(true); }
|
||||
|
||||
static bool IsRowShift(const Inst& inst, uint32_t shift) {
|
||||
const auto flags = inst.Flags<DppMoveFlags>();
|
||||
@@ -189,22 +166,24 @@ private:
|
||||
flags.bank_mask == 0xf && !flags.fetch_inactive && !flags.bound_control && !flags.dpp8;
|
||||
}
|
||||
|
||||
Value MatchRow(Value value, uint32_t shift) {
|
||||
const auto* update = Get(Unwrap(value), ValueOpcode::DppUpdateU32);
|
||||
Value MatchRow(Value value, uint32_t shift) const {
|
||||
const auto* update = Get(value, ValueOpcode::DppUpdateU32);
|
||||
if (update == nullptr || !IsRowShift(*update, shift) || !FullExec(update->Arg(2))) return {};
|
||||
const auto* minimum = Get(update->Arg(0), ValueOpcode::UMin32);
|
||||
if (minimum == nullptr) return {};
|
||||
const auto* combined = Get(update->Arg(0), operation);
|
||||
if (combined == nullptr) return {};
|
||||
for (size_t index = 0; index < 2; index++) {
|
||||
const auto* move = Get(minimum->Arg(index), ValueOpcode::DppMoveU32);
|
||||
const auto* move = Get(combined->Arg(index), ValueOpcode::DppMoveU32);
|
||||
if (move == nullptr || !IsRowShift(*move, shift) || !FullExec(move->Arg(1))) continue;
|
||||
const auto previous = Unwrap(minimum->Arg(1 - index));
|
||||
if (Unwrap(move->Arg(0)) == previous && Unwrap(update->Arg(1)) == previous) return previous;
|
||||
const auto previous = combined->Arg(1 - index).Resolve();
|
||||
if (move->Arg(0).Resolve() == previous && update->Arg(1).Resolve() == previous) {
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Block* block;
|
||||
Value saved_exec;
|
||||
const Block* block;
|
||||
ValueOpcode operation;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -217,18 +196,22 @@ ReadLaneStats EliminateReadLane(Program& program, uint32_t wave_size) {
|
||||
|
||||
for (auto* block: program.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (wave_size == 64u && inst.GetOpcode() == ValueOpcode::UMin32) {
|
||||
const auto seed = UMinReductionMatcher(block).Match(inst);
|
||||
const auto opcode = inst.GetOpcode();
|
||||
if (opcode == ValueOpcode::UMin32 || opcode == ValueOpcode::UMax32) {
|
||||
const auto seed = WaveReductionMatcher(block, opcode).Match(inst, wave_size);
|
||||
if (!seed.IsEmpty()) {
|
||||
const auto position = std::find_if(block->begin(), block->end(),
|
||||
[&](const Inst& candidate) { return &candidate == &inst; });
|
||||
auto& reduction = *block->PrependNewInst(position, ValueOpcode::SubgroupUMinU32, {seed});
|
||||
const auto reduce = opcode == ValueOpcode::UMin32
|
||||
? ValueOpcode::SubgroupUMinU32
|
||||
: ValueOpcode::SubgroupUMaxU32;
|
||||
auto& reduction = *block->PrependNewInst(position, reduce, {seed});
|
||||
inst.ReplaceUsesWith(Value(&reduction));
|
||||
stats.rewritten_reductions++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inst.GetOpcode() != ValueOpcode::ReadLane) {
|
||||
if (opcode != ValueOpcode::ReadLane) {
|
||||
continue;
|
||||
}
|
||||
const auto selector = inst.Arg(1).Resolve();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/Serialize.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
@@ -186,35 +187,6 @@ const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
|
||||
return &program.descriptor_sources[source];
|
||||
}
|
||||
|
||||
void MarkCleanReads(const std::unordered_set<const Inst*>& planned_reads,
|
||||
const DescriptorSource* source, Value extra = {}) {
|
||||
if (source == nullptr && extra.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
std::vector<Value> pending;
|
||||
if (source != nullptr) {
|
||||
pending.assign(source->dwords.begin(), source->dwords.begin() + source->dword_count);
|
||||
}
|
||||
if (!extra.IsEmpty()) pending.push_back(extra);
|
||||
std::vector<const Inst*> visited;
|
||||
while (!pending.empty()) {
|
||||
auto* inst = pending.back().Resolve().TryInstruction();
|
||||
pending.pop_back();
|
||||
if (inst == nullptr || std::ranges::find(visited, inst) != visited.end()) {
|
||||
continue;
|
||||
}
|
||||
visited.push_back(inst);
|
||||
if (planned_reads.contains(inst)) {
|
||||
auto flags = inst->Flags<SrtReadFlags>();
|
||||
flags.clean = 1u;
|
||||
inst->SetFlags(flags);
|
||||
}
|
||||
for (size_t arg = 0; arg < inst->NumArgs(); arg++) {
|
||||
pending.push_back(inst->Arg(arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadTable(uint64_t base, uint64_t size, uint64_t offset,
|
||||
const SrtRuntime& runtime, std::span<uint32_t> words) {
|
||||
const auto count = std::min<uint64_t>(words.size(), offset < size ? (size - offset) / 4u : 0u);
|
||||
@@ -309,22 +281,22 @@ bool CanReadIndexedSelector(const ShaderBufferResource& material, uint32_t offse
|
||||
uint64_t {UINT32_MAX} + 1u;
|
||||
}
|
||||
|
||||
bool EvaluateIndirectCount(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
const SrtRuntime& runtime, SrtWalker& clean, uint32_t& count) {
|
||||
bool EvaluateIndirectCount(const ResourcePlan& program, const DescriptorSource::IndirectDescriptor& indirect,
|
||||
const SrtRuntime& runtime, uint32_t& count) {
|
||||
if (indirect.workgroup_axis != UINT32_MAX) {
|
||||
if (runtime.workgroup_counts.size() != 3u || indirect.workgroup_axis >= 3u) return false;
|
||||
count = std::ranges::find(runtime.workgroup_counts, 0u) != runtime.workgroup_counts.end()
|
||||
? 0u : runtime.workgroup_counts[indirect.workgroup_axis];
|
||||
return true;
|
||||
}
|
||||
if (!clean.Evaluate(indirect.key_count, count)) return false;
|
||||
if (!WalkedValue(program, indirect.key_count, count)) return false;
|
||||
if (indirect.signed_count && std::bit_cast<int32_t>(count) < 0) count = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
bool ResolveSelectorKeys(const ResourcePlan& program, const DescriptorSource::IndirectDescriptor& indirect,
|
||||
const DescriptorValue& material_value, const SrtRuntime& runtime,
|
||||
SrtWalker& clean, std::vector<uint32_t>& keys) {
|
||||
std::vector<uint32_t>& keys) {
|
||||
const auto* selector = &*indirect.selector;
|
||||
keys.clear();
|
||||
const auto read_selector = [&](uint64_t base, uint64_t size, uint64_t offset,
|
||||
@@ -359,11 +331,23 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
keys.resize(count);
|
||||
return true;
|
||||
};
|
||||
// Reads the key of each record whose rank is set.
|
||||
const auto read_ranks = [&](uint64_t base, uint64_t size, uint64_t first, uint64_t step,
|
||||
uint32_t ranks) {
|
||||
keys.reserve(std::popcount(ranks));
|
||||
for (; ranks != 0u; ranks &= ranks - 1u) {
|
||||
const auto offset = first + std::countr_zero(ranks) * step;
|
||||
uint32_t key = 0;
|
||||
if (offset > UINT32_MAX || !read_selector(base, size, offset, {&key, 1})) return false;
|
||||
keys.push_back(key);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (selector->rows) {
|
||||
const auto& rows = *selector->rows;
|
||||
DescriptorValue count_value;
|
||||
ShaderBufferResource counts, material;
|
||||
if (!clean.EvaluateDescriptor(rows.source, count_value) ||
|
||||
if (!WalkedDescriptor(program, rows.source, count_value) ||
|
||||
!DecodeBufferDescriptor(count_value, counts) ||
|
||||
!DecodeBufferDescriptor(material_value, material) || selector->stride != 4u ||
|
||||
selector->offset != 0u || rows.stride == 0u || (rows.stride & 3u) != 0u ||
|
||||
@@ -392,7 +376,7 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
if (candidate_end - first > MaxIndirectDescriptorProbes) break;
|
||||
end = candidate_end;
|
||||
}
|
||||
// Coalesce small gaps, but interpret only selected prefixes. The strict
|
||||
// Coalesce small gaps, but interpret only selected prefixes. The specialization
|
||||
// reader captures the entire fetched span for writable-alias rejection.
|
||||
batch.resize(end - first);
|
||||
if (!ReadScalarTable(material.Base48(), material.GetSize(), first * 4u, runtime, batch))
|
||||
@@ -405,19 +389,21 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
}
|
||||
} else if (selector->bitset) {
|
||||
ShaderBufferResource material;
|
||||
std::array<uint32_t, 4> masks {};
|
||||
std::array<uint32_t, 8> storage {};
|
||||
const auto record_words = std::min<size_t>(selector->stride / 4u, storage.size());
|
||||
const auto masks = std::span(storage).first(record_words);
|
||||
uint32_t first = 0, count = 0;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
!clean.Evaluate(indirect.selector_first, first) ||
|
||||
!EvaluateIndirectCount(indirect, runtime, clean, count) || count != masks.size() * 32u ||
|
||||
selector->stride != sizeof(masks) || selector->offset != 0u ||
|
||||
material.Type() != 0u || material.Stride() != sizeof(masks) ||
|
||||
!WalkedValue(program, indirect.selector_first, first) ||
|
||||
!EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u ||
|
||||
selector->stride != masks.size_bytes() || selector->offset != 0u ||
|
||||
material.Type() != 0u || material.Stride() != selector->stride ||
|
||||
material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u ||
|
||||
(material.Base48() & 3u) != 0u) return false;
|
||||
if (material.Format() != Prospero::BufferFormat::kInvalid) {
|
||||
const auto words = material.GetSize() / sizeof(uint32_t);
|
||||
// Bitmap storage can grow with resolution while its candidate domain stays
|
||||
// at 128 keys. Bound scratch memory, preserving each coherent read's capture.
|
||||
// Bitmap storage can grow with resolution while its candidate domain stays at 32 keys
|
||||
// per record word. Bound scratch memory, preserving each coherent read's capture.
|
||||
for (uint64_t first_word = 0; first_word < words; first_word += keys.size()) {
|
||||
keys.resize(std::min<uint64_t>(words - first_word, MaxIndirectDescriptorProbes));
|
||||
if (!ReadScalarTable(material.Base48(), material.GetSize(), first_word * 4u,
|
||||
@@ -462,8 +448,8 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
base = material.Base48();
|
||||
size = material.GetSize();
|
||||
}
|
||||
if (!clean.Evaluate(indirect.selector_first, first) ||
|
||||
!EvaluateIndirectCount(indirect, runtime, clean, count) || count > MaxIndirectDescriptorProbes)
|
||||
if (!WalkedValue(program, indirect.selector_first, first) ||
|
||||
!EvaluateIndirectCount(program, indirect, runtime, count) || count > MaxIndirectDescriptorProbes)
|
||||
return false;
|
||||
// SMEM uses its instruction's byte stride; V# stride only supplies its bounds.
|
||||
const auto step = selector->stride != 0u ? selector->stride : material.Stride();
|
||||
@@ -473,35 +459,48 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
uint64_t {first} + count > material.NumRecords())) return false;
|
||||
const auto last = count != 0u ? uint64_t {first} + count - 1u : 0u;
|
||||
if (last > UINT32_MAX) return false;
|
||||
// Out-of-bounds scalar reads return zero.
|
||||
const auto end = count != 0u ? last * step + selector->offset + 4u : 0u;
|
||||
if (end > uint64_t {UINT32_MAX} + 1u ||
|
||||
(indirect.workgroup_axis == UINT32_MAX && end > size))
|
||||
return false;
|
||||
if (!read_keys(base, size, uint64_t {first} * step + selector->offset, step, count))
|
||||
if (end > uint64_t {UINT32_MAX} + 1u) return false;
|
||||
const auto start = uint64_t {first} * step + selector->offset;
|
||||
if (selector->ranks) {
|
||||
// A scan selects the ranks some word of its mask's address class sets.
|
||||
const auto& mask = *selector->ranks;
|
||||
DescriptorValue mask_value;
|
||||
ShaderBufferResource masks;
|
||||
if (count > 32u || !WalkedDescriptor(program, mask.source, mask_value) ||
|
||||
!DecodeBufferDescriptor(mask_value, masks)) return false;
|
||||
// Each chunk holds whole strides of the power-of-two class, or one of its words.
|
||||
const auto words = mask.stride / 4u;
|
||||
const auto chunk =
|
||||
words <= MaxIndirectDescriptorProbes ? MaxIndirectDescriptorProbes : 1u;
|
||||
uint32_t ranks = 0;
|
||||
for (auto at = uint64_t {mask.offset}; at + 4u <= masks.GetSize();
|
||||
at += uint64_t {std::max(chunk, words)} * 4u) {
|
||||
keys.resize(std::min<uint64_t>((masks.GetSize() - at) / 4u, chunk));
|
||||
if (!ReadScalarTable(masks.Base48(), masks.GetSize(), at, runtime, keys))
|
||||
return false;
|
||||
for (size_t word = 0; word < keys.size(); word += words) ranks |= keys[word];
|
||||
}
|
||||
keys.clear();
|
||||
if (count < 32u) ranks &= (1u << count) - 1u;
|
||||
if (!read_ranks(base, size, start, step, ranks)) return false;
|
||||
} else if (!read_keys(base, size, start, step, count)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!indirect.selector_mask.IsEmpty()) {
|
||||
uint32_t mask = 0;
|
||||
uint32_t count = 0;
|
||||
if (material_value.dword_count != 2u ||
|
||||
!clean.Evaluate(indirect.selector_mask, mask) ||
|
||||
!EvaluateIndirectCount(indirect, runtime, clean, count) || count == 0u || count > 32u) {
|
||||
!WalkedValue(program, indirect.selector_mask, mask) ||
|
||||
!EvaluateIndirectCount(program, indirect, runtime, count) || count == 0u || count > 32u) {
|
||||
return false;
|
||||
}
|
||||
if (count < 32u) mask &= (1u << count) - 1u;
|
||||
const auto material_base =
|
||||
(static_cast<uint64_t>(material_value.dwords[1]) << 32u) | material_value.dwords[0];
|
||||
keys.reserve(std::popcount(mask));
|
||||
while (mask != 0u) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
const auto offset = static_cast<uint64_t>(selector->offset) +
|
||||
static_cast<uint64_t>(index) * selector->stride;
|
||||
if (offset > UINT32_MAX) return false;
|
||||
uint32_t key = 0;
|
||||
if (!read_selector(material_base, UINT64_MAX, static_cast<uint32_t>(offset),
|
||||
{&key, 1})) return false;
|
||||
keys.push_back(key);
|
||||
mask &= mask - 1u;
|
||||
}
|
||||
if (!read_ranks(material_base, UINT64_MAX, selector->offset, selector->stride, mask))
|
||||
return false;
|
||||
} else if (selector->stride == 0u) {
|
||||
ShaderBufferResource material;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
@@ -531,6 +530,7 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
if (selector->bit_offset != 0u || selector->bit_mask != UINT32_MAX || selector->maximum != UINT32_MAX)
|
||||
for (auto& key: keys)
|
||||
key = std::min((key >> selector->bit_offset) & selector->bit_mask, selector->maximum);
|
||||
if (selector->excluded) std::erase(keys, *selector->excluded);
|
||||
std::ranges::sort(keys);
|
||||
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||
}
|
||||
@@ -541,8 +541,7 @@ template <typename Specialization, typename Normalize>
|
||||
bool MaterializeIndirectDescriptor(const ResourcePlan& program,
|
||||
uint32_t source_index,
|
||||
uint32_t resource_index, uint32_t dword_count, uint32_t read_dwords,
|
||||
const SrtRuntime& runtime, SrtWalker& clean,
|
||||
ResourceSnapshot& snapshot,
|
||||
const SrtRuntime& runtime, ResourceSnapshot& snapshot,
|
||||
std::vector<DescriptorValue>& descriptors,
|
||||
std::vector<Specialization>& specializations,
|
||||
Normalize&& normalize) {
|
||||
@@ -605,8 +604,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
if (selector == nullptr) keys.clear();
|
||||
DescriptorValue material_value;
|
||||
if ((selector != nullptr && (byte_keys || !selector_keys->resolved) &&
|
||||
!clean.EvaluateDescriptor(selector->source, material_value)) ||
|
||||
!clean.EvaluateDescriptor(indirect.table_source, table_value)) {
|
||||
!WalkedDescriptor(program, selector->source, material_value)) ||
|
||||
!WalkedDescriptor(program, indirect.table_source, table_value)) {
|
||||
return false;
|
||||
}
|
||||
if (table_value.dword_count == 2u) {
|
||||
@@ -659,7 +658,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
: uint64_t {1} << 32u;
|
||||
if (!indirect.key_count.IsEmpty()) {
|
||||
uint32_t key_count = 0u;
|
||||
if (!EvaluateIndirectCount(indirect, runtime, clean, key_count)) return false;
|
||||
if (!EvaluateIndirectCount(program, indirect, runtime, key_count)) return false;
|
||||
domain = std::min(domain, uint64_t {key_count});
|
||||
}
|
||||
if (indexed_pointer &&
|
||||
@@ -771,8 +770,8 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
if (selector == nullptr) {
|
||||
uint32_t key_count = 0;
|
||||
uint32_t first_key = 0;
|
||||
if (!EvaluateIndirectCount(indirect, runtime, clean, key_count)) return false;
|
||||
if (!indirect.selector_first.IsEmpty() && !clean.Evaluate(indirect.selector_first, first_key))
|
||||
if (!EvaluateIndirectCount(program, indirect, runtime, key_count)) return false;
|
||||
if (!indirect.selector_first.IsEmpty() && !WalkedValue(program, indirect.selector_first, first_key))
|
||||
return false;
|
||||
if (indirect.table_stride == 0u || key_count > MaxIndirectDescriptorProbes ||
|
||||
(indirect.selector_first.IsEmpty() && key_count != 0u &&
|
||||
@@ -785,10 +784,16 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
std::iota(keys.begin(), keys.end(), first_key);
|
||||
if (!keys.empty() && keys.back() < keys.front())
|
||||
std::rotate(keys.begin(), keys.begin() + (UINT32_MAX - first_key + 1ull), keys.end());
|
||||
if (indirect.workgroup_shift != 0u) {
|
||||
for (auto& key: keys)
|
||||
key = static_cast<uint32_t>(uint64_t {key} * indirect.workgroup_multiplier >>
|
||||
indirect.workgroup_shift);
|
||||
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
|
||||
}
|
||||
} else {
|
||||
if (!indirect.selector_mask.IsEmpty() && table_value.dword_count != 2u) return false;
|
||||
if (!selector_keys->resolved) {
|
||||
if (!ResolveSelectorKeys(indirect, material_value, runtime, clean, keys)) return false;
|
||||
if (!ResolveSelectorKeys(program, indirect, material_value, runtime, keys)) return false;
|
||||
selector_keys->resolved = true;
|
||||
}
|
||||
}
|
||||
@@ -827,7 +832,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
std::span(candidate.dwords).first(read_dwords))) {
|
||||
return read_failed("table descriptor read", key, 0u, table_base, table_offset, descriptor_bytes);
|
||||
}
|
||||
} else if (!clean.EvaluateDescriptor(sources[entry], candidate)) {
|
||||
} else if (!WalkedDescriptor(program, sources[entry], candidate)) {
|
||||
return false;
|
||||
}
|
||||
if (!normalize(candidate))
|
||||
@@ -1070,6 +1075,26 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
return dimension == Decoder::ImageDimension::Dim1D ||
|
||||
dimension == Decoder::ImageDimension::Dim2D;
|
||||
};
|
||||
// The descriptor's type selects the address layout. A 2D or 3D candidate reads the leading
|
||||
// coordinates of an equal or larger dimension, but an array with more than one slice reads
|
||||
// the next as its slice. Derivative, LOD, clamp and mip operands shift with the dimension.
|
||||
const auto leading = [&](Decoder::ImageDimension dimension, const DescriptorValue& value) {
|
||||
const bool sliced =
|
||||
static_cast<Prospero::ImageType>((value.dwords[3] >> 28u) & 0xfu) ==
|
||||
Prospero::ImageType::kColor2DArray &&
|
||||
(value.dwords[4] & 0x1fffu) != ((value.dwords[4] >> 16u) & 0x1fffu);
|
||||
return !sliced &&
|
||||
(dimension == Decoder::ImageDimension::Dim2D || dimension == base.dimension);
|
||||
};
|
||||
const bool leading_coordinates = (base.dimension == Decoder::ImageDimension::Dim2D ||
|
||||
base.dimension == Decoder::ImageDimension::Dim3D) &&
|
||||
leading(image_class.dimension, snapshot.images[exemplar]) &&
|
||||
std::ranges::none_of(program.memory_info, [&](const MemoryInfo& memory) {
|
||||
return memory.kind == ResourceKind::Image && memory.resource == root_index &&
|
||||
(memory.image_has_mip || (memory.image_sample_flags &
|
||||
(Decoder::ImageSampleFlagLod | Decoder::ImageSampleFlagDerivative |
|
||||
Decoder::ImageSampleFlagLodClamp)) != 0u);
|
||||
});
|
||||
for (uint32_t candidate = 0; candidate < specialization.images.size(); candidate++) {
|
||||
auto& image = specialization.images[candidate];
|
||||
if (image.indirect_root != root_index) {
|
||||
@@ -1089,8 +1114,10 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
base.dimension == Decoder::ImageDimension::Dim2D &&
|
||||
!image.cube && !image_class.cube && is_1d_or_2d(image.dimension) &&
|
||||
is_1d_or_2d(image_class.dimension);
|
||||
const bool prefix_coordinates =
|
||||
leading_coordinates && leading(image.dimension, snapshot.images[candidate]);
|
||||
if ((!same_coordinates && !(is_2d(image.dimension) && is_2d(image_class.dimension)) &&
|
||||
!unfiltered_coordinates) ||
|
||||
!unfiltered_coordinates && !prefix_coordinates) ||
|
||||
image.conversion_format != image_class.conversion_format ||
|
||||
(!storage_swizzle && image.shader_swizzle != image_class.shader_swizzle)) {
|
||||
return SpecializationFail(
|
||||
@@ -1193,14 +1220,16 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program, co
|
||||
block.sources.push_back(program.info.buffers.at(memory.resource).source);
|
||||
} else {
|
||||
block.sources.push_back(program.info.images.at(memory.resource).source);
|
||||
block.images.push_back(memory.resource);
|
||||
if (image.needs_sampler) {
|
||||
block.sources.push_back(program.info.samplers.at(memory.sampler).source);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::ranges::sort(block.sources);
|
||||
block.sources.erase(std::unique(block.sources.begin(), block.sources.end()),
|
||||
block.sources.end());
|
||||
for (auto* list: {&block.sources, &block.images}) {
|
||||
std::ranges::sort(*list);
|
||||
list->erase(std::unique(list->begin(), list->end()), list->end());
|
||||
}
|
||||
}
|
||||
for (auto& block: blocks) block.condition = predicate(block.condition);
|
||||
if (std::ranges::none_of(
|
||||
@@ -1341,6 +1370,17 @@ static UniformFillPlan AnalyzeUniformFill(const Program& program) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// State an extracted or loaded plan derives from its fields: one key set per selector group, which
|
||||
// extraction numbers densely in order of first use (false when they are not), and the walker.
|
||||
static bool DerivePlanState(ResourcePlan& plan) {
|
||||
for (const auto group: plan.selector_groups) {
|
||||
if (group == plan.selector_keys.size()) plan.selector_keys.emplace_back();
|
||||
if (group != UINT32_MAX && group >= plan.selector_keys.size()) return false;
|
||||
}
|
||||
if (plan.srt_plan_complete && plan.resource_tracking_complete) CompileSrtWalker(plan);
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
ResourcePlan plan;
|
||||
plan.stage = program.stage;
|
||||
@@ -1380,16 +1420,13 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
if (const auto found = cloned.find(source); found != cloned.end()) {
|
||||
return Value(found->second);
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::LoadBufferU32) {
|
||||
plan.requires_specialization_memory = true;
|
||||
plan.capture_specialization_reads = true;
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::LoadBufferU32) plan.capture_specialization_reads = true;
|
||||
auto& target =
|
||||
plan.value_storage.emplace_back(source->GetOpcode(), source->Flags<uint64_t>());
|
||||
if (source->GetOpcode() == ValueOpcode::LoadAddressU32 ||
|
||||
source->GetOpcode() == ValueOpcode::ReadConstBuffer ||
|
||||
source->GetOpcode() == ValueOpcode::LoadBufferU32) {
|
||||
target.SetFlags(SrtReadFlags {.index = source->Flags<MemoryFlags>().index});
|
||||
target.SetFlags(MemoryFlags {.index = source->Flags<MemoryFlags>().index});
|
||||
}
|
||||
cloned.emplace(source, &target);
|
||||
if (source->GetOpcode() == ValueOpcode::Phi) {
|
||||
@@ -1411,7 +1448,6 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
target.dword_count = source.dword_count;
|
||||
target.indirect_descriptor = source.indirect_descriptor;
|
||||
if (target.indirect_descriptor.has_value()) {
|
||||
plan.requires_specialization_memory = true;
|
||||
capture_indirect_reads = true;
|
||||
target.indirect_descriptor->key_count = Clone(target.indirect_descriptor->key_count);
|
||||
target.indirect_descriptor->selector_first =
|
||||
@@ -1425,17 +1461,18 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
}
|
||||
// Destination tables share keys when their complete selector domains agree.
|
||||
// Resolve those domains lazily at dispatch time, without repeated guest reads.
|
||||
std::vector<uint32_t> group_sources; // The first source of each group.
|
||||
plan.selector_groups.resize(plan.descriptor_sources.size(), UINT32_MAX);
|
||||
for (uint32_t source = 0; source < plan.descriptor_sources.size(); ++source) {
|
||||
const auto& descriptor = program.descriptor_sources[source].indirect_descriptor;
|
||||
if (!descriptor || !descriptor->selector) continue;
|
||||
const auto& a = *descriptor;
|
||||
uint32_t group = 0;
|
||||
for (; group < plan.selector_keys.size(); ++group) {
|
||||
const auto& b = *program.descriptor_sources[plan.selector_keys[group].source].indirect_descriptor;
|
||||
for (; group < group_sources.size(); ++group) {
|
||||
const auto& b = *program.descriptor_sources[group_sources[group]].indirect_descriptor;
|
||||
if (EquivalentSelectorDomain(program, a, b)) break;
|
||||
}
|
||||
if (group == plan.selector_keys.size()) plan.selector_keys.push_back({.source = source});
|
||||
if (group == group_sources.size()) group_sources.push_back(source);
|
||||
plan.selector_groups[source] = group;
|
||||
}
|
||||
plan.srt_reads.reserve(program.srt_reads.size());
|
||||
@@ -1523,57 +1560,16 @@ ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
plan.uniform_fill.element_count = Clone(plan.uniform_fill.element_count);
|
||||
plan.capture_specialization_reads |=
|
||||
capture_indirect_reads || capture_control_reads || !plan.source_reads.empty();
|
||||
if (plan.capture_specialization_reads) {
|
||||
// Every former flat alias now targets its retained raw read. Mark those reads
|
||||
// on the normalized graph, so discarded Phi/EXEC dependencies stay discarded.
|
||||
std::unordered_set<const Inst*> planned_reads;
|
||||
for (const auto& read: plan.srt_reads) {
|
||||
auto* inst = read.value.ResolveInstruction();
|
||||
EXIT_IF(inst == nullptr || (inst->GetOpcode() != ValueOpcode::LoadAddressU32 &&
|
||||
inst->GetOpcode() != ValueOpcode::ReadConstBuffer));
|
||||
planned_reads.insert(inst);
|
||||
}
|
||||
for (const auto& source: plan.descriptor_sources) {
|
||||
if (!source.indirect_descriptor.has_value()) continue;
|
||||
const auto& indirect = *source.indirect_descriptor;
|
||||
if (indirect.selector) {
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.selector->source));
|
||||
if (indirect.selector->rows)
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.selector->rows->source));
|
||||
}
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.selector_mask);
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.selector_first);
|
||||
MarkCleanReads(planned_reads, nullptr, indirect.key_count);
|
||||
if (indirect.sources.empty()) {
|
||||
MarkCleanReads(planned_reads, Source(plan, indirect.table_source));
|
||||
} else {
|
||||
for (const auto candidate: indirect.sources) {
|
||||
MarkCleanReads(planned_reads, Source(plan, candidate));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clean writable addresses prove that shader writes cannot overlap captured reads.
|
||||
for (const auto& buffer: plan.info.buffers) {
|
||||
if (buffer.written) MarkCleanReads(planned_reads, Source(plan, buffer.source));
|
||||
}
|
||||
for (const auto& image: plan.info.images) {
|
||||
if (image.written) MarkCleanReads(planned_reads, Source(plan, image.source));
|
||||
}
|
||||
}
|
||||
if (capture_indirect_reads || !plan.source_reads.empty())
|
||||
plan.resource_tracking_complete &= !program.has_address_writes;
|
||||
CompileSrtWalker(plan);
|
||||
DerivePlanState(plan);
|
||||
return plan;
|
||||
}
|
||||
|
||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization) {
|
||||
for (auto& keys: program.selector_keys) keys.resolved = false;
|
||||
if (!program.resource_tracking_complete ||
|
||||
((program.requires_specialization_memory || !program.source_reads.empty()) &&
|
||||
runtime.read_specialization_memory == nullptr)) {
|
||||
return false;
|
||||
}
|
||||
if (!program.resource_tracking_complete) return false;
|
||||
const auto uses_workgroup_count = [](const DescriptorSource& source) {
|
||||
return source.indirect_descriptor && source.indirect_descriptor->workgroup_axis != UINT32_MAX;
|
||||
};
|
||||
@@ -1582,20 +1578,16 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
std::ranges::any_of(program.descriptor_sources, uses_workgroup_count));
|
||||
auto& reads = snapshot.specialization_reads;
|
||||
reads = program.source_reads;
|
||||
SrtReadCapture capture {runtime, reads};
|
||||
auto observed = capture_reads ? capture.ObservedRuntime() : runtime;
|
||||
SrtWalker clean(program, CleanRuntime(observed));
|
||||
SrtWalker walker(program, observed,
|
||||
capture_reads || program.requires_specialization_memory ? &clean : nullptr);
|
||||
if (!walker.RefreshFlatBuffer(snapshot.flattened_srt)) {
|
||||
if (!WalkSrt(program, runtime, snapshot.flattened_srt, capture_reads ? &reads : nullptr)) {
|
||||
return SpecializationFail(fmt::format("SRT refresh complete={} reads={} user_data={}",
|
||||
program.srt_plan_complete, program.srt_reads.size(), runtime.user_data.size()));
|
||||
}
|
||||
const auto active = std::span<const uint8_t>(program.active_sources);
|
||||
SrtReadCapture capture {runtime, reads};
|
||||
auto observed = capture_reads ? capture.ObservedRuntime() : runtime;
|
||||
std::array<uint32_t, 3> workgroup_counts {};
|
||||
if (runtime.indirect_args_address != 0u) {
|
||||
for (uint32_t source = 0; source < program.descriptor_sources.size(); ++source) {
|
||||
if ((!active.empty() && !active[source]) ||
|
||||
if (!WalkedActive(program, source) ||
|
||||
!uses_workgroup_count(program.descriptor_sources[source])) continue;
|
||||
if ((runtime.indirect_args_address & 3u) != 0u ||
|
||||
!ReadTable(runtime.indirect_args_address, sizeof(workgroup_counts), 0u,
|
||||
@@ -1610,24 +1602,24 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
std::array<uint32_t, 4> stored {};
|
||||
bool uniform_fill = words != 0;
|
||||
if (uniform_fill && !fill.pattern_size.IsEmpty())
|
||||
uniform_fill = clean.Evaluate(fill.pattern_size, words) && words >= 1 && words <= 4;
|
||||
uniform_fill = WalkedValue(program, fill.pattern_size, words) && words >= 1 && words <= 4;
|
||||
uint32_t elements = 0;
|
||||
if (uniform_fill && !fill.element_count.IsEmpty())
|
||||
uniform_fill = clean.Evaluate(fill.element_count, elements) && elements != 0;
|
||||
uniform_fill = WalkedValue(program, fill.element_count, elements) && elements != 0;
|
||||
for (uint32_t i = 0; i < words && uniform_fill; ++i) {
|
||||
uniform_fill = clean.Evaluate(fill.values[i], stored[i]) && stored[i] == stored[0];
|
||||
uniform_fill = WalkedValue(program, fill.values[i], stored[i]) && stored[i] == stored[0];
|
||||
}
|
||||
if (uniform_fill) {
|
||||
snapshot.uniform_fill = fill.fill;
|
||||
snapshot.uniform_fill.value = stored[0];
|
||||
snapshot.uniform_fill.bounded_elements = elements;
|
||||
}
|
||||
const auto evaluate = [&](uint32_t source, DescriptorValue& value, bool written = false) {
|
||||
const auto evaluate = [&](uint32_t source, DescriptorValue& value) {
|
||||
if (source >= program.descriptor_sources.size()) {
|
||||
return false;
|
||||
}
|
||||
if (active.empty() || active[source]) {
|
||||
return (capture_reads && written ? clean : walker).EvaluateDescriptor(source, value) ||
|
||||
if (WalkedActive(program, source)) {
|
||||
return WalkedDescriptor(program, source, value) ||
|
||||
SpecializationFail(fmt::format("descriptor evaluation source={}", source));
|
||||
}
|
||||
value = {};
|
||||
@@ -1651,9 +1643,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
if (source == nullptr) return false;
|
||||
if (source->indirect_descriptor.has_value()) {
|
||||
snapshot.buffers[i] = {.dword_count = 4u};
|
||||
if (active.empty() || active[base.source]) {
|
||||
if (WalkedActive(program, base.source)) {
|
||||
if (!MaterializeIndirectDescriptor(
|
||||
program, base.source, i, 4u, 4u, observed, clean, snapshot,
|
||||
program, base.source, i, 4u, 4u, observed, snapshot,
|
||||
snapshot.buffers, specialization.buffers,
|
||||
[&](DescriptorValue& value) {
|
||||
return NormalizeIndirectBuffer(
|
||||
@@ -1661,7 +1653,7 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
}))
|
||||
return resource_failed("buffer", i, base.source, snapshot.buffers.size());
|
||||
}
|
||||
} else if (!evaluate(base.source, snapshot.buffers[i], base.written)) {
|
||||
} else if (!evaluate(base.source, snapshot.buffers[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1711,13 +1703,13 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
}
|
||||
if (source->indirect_descriptor.has_value()) {
|
||||
snapshot.images[i] = {.dword_count = 8u};
|
||||
if (!active.empty() && !active[image.source]) {
|
||||
if (!WalkedActive(program, image.source) || !WalkedImageActive(program, i)) {
|
||||
continue;
|
||||
}
|
||||
const auto& indirect = *source->indirect_descriptor;
|
||||
const bool bounded_table = IsBoundedDescriptorTable(program, indirect);
|
||||
if (!MaterializeIndirectDescriptor(
|
||||
program, image.source, i, 8u, image.r128 ? 4u : 8u, observed, clean, snapshot, snapshot.images,
|
||||
program, image.source, i, 8u, image.r128 ? 4u : 8u, observed, snapshot, snapshot.images,
|
||||
specialization.images, [&](DescriptorValue& value) {
|
||||
// A broad heap also contains resources for other typed image operations.
|
||||
if (NullImageDescriptor(value) || !ValidImageDescriptor(value, image.r128) ||
|
||||
@@ -1728,10 +1720,12 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
return resource_failed("image", i, image.source, snapshot.images.size());
|
||||
}
|
||||
} else {
|
||||
if (!evaluate(image.source, snapshot.images[i], image.written)) {
|
||||
if (!evaluate(image.source, snapshot.images[i])) {
|
||||
return false;
|
||||
}
|
||||
if (!ValidImageDescriptor(snapshot.images[i], image.r128)) {
|
||||
// An image listed only by unreached blocks reads zero, not its source's descriptor.
|
||||
if (!WalkedImageActive(program, i) ||
|
||||
!ValidImageDescriptor(snapshot.images[i], image.r128)) {
|
||||
snapshot.images[i].dwords.fill(0);
|
||||
}
|
||||
}
|
||||
@@ -1749,8 +1743,8 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
const auto* indirect = descriptor->indirect_descriptor ? &*descriptor->indirect_descriptor : nullptr;
|
||||
if (indirect != nullptr && indirect->sources.empty()) {
|
||||
snapshot.samplers[i] = {.dword_count = 4u};
|
||||
if ((active.empty() || active[source]) && !MaterializeIndirectDescriptor(
|
||||
program, source, i, 4u, 4u, observed, clean, snapshot, snapshot.samplers,
|
||||
if (WalkedActive(program, source) && !MaterializeIndirectDescriptor(
|
||||
program, source, i, 4u, 4u, observed, snapshot, snapshot.samplers,
|
||||
specialization.samplers,
|
||||
[](DescriptorValue&) { return true; }))
|
||||
return resource_failed("sampler", i, source, snapshot.samplers.size());
|
||||
@@ -1777,8 +1771,9 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
}
|
||||
|
||||
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) {
|
||||
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, and
|
||||
// entries that need shader-side conversion, read as null instead of failing the draw.
|
||||
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, entries of
|
||||
// another dimension and entries that need shader-side conversion read as null instead of
|
||||
// failing the draw.
|
||||
const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu);
|
||||
const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu);
|
||||
const auto numeric_class =
|
||||
@@ -2065,4 +2060,86 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
image_remap.Apply(images);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using ControlPhis = decltype(ResourcePlan::control_phi_predecessors);
|
||||
|
||||
// Instructions come first, so that any value can refer to them by index, then their arguments.
|
||||
// Plan Phis have no blocks; a control Phi lists the plan blocks of its arguments after them.
|
||||
void IoValues(Writer& writer, const std::list<Inst>& values, const ControlPhis& phis) {
|
||||
IoAll(writer, static_cast<uint32_t>(values.size()));
|
||||
for (const auto& inst: values) {
|
||||
writer.index.emplace(&inst, static_cast<uint32_t>(writer.index.size()));
|
||||
IoAll(writer, inst.GetOpcode(), inst.Flags<uint64_t>());
|
||||
}
|
||||
for (const auto& inst: values) {
|
||||
if (inst.GetOpcode() != ValueOpcode::Phi) {
|
||||
for (size_t index = 0; index < inst.NumArgs(); ++index) IoAll(writer, inst.Arg(index));
|
||||
continue;
|
||||
}
|
||||
std::vector<Value> args;
|
||||
for (size_t index = 0; index < inst.NumArgs(); ++index) {
|
||||
EXIT_IF(inst.PhiBlock(index) != nullptr);
|
||||
args.push_back(inst.Arg(index));
|
||||
}
|
||||
const auto found = phis.find(&inst);
|
||||
IoAll(writer, args, found != phis.end() ? found->second : std::vector<uint32_t> {});
|
||||
}
|
||||
}
|
||||
|
||||
void IoValues(Reader& reader, std::list<Inst>& values, ControlPhis& phis) {
|
||||
uint32_t count = 0;
|
||||
Io(reader, count);
|
||||
count = reader.Count(count);
|
||||
for (uint32_t index = 0; index < count && reader.ok; ++index) {
|
||||
ValueOpcode opcode {};
|
||||
uint64_t flags = 0;
|
||||
IoAll(reader, opcode, flags);
|
||||
reader.ok = reader.ok && opcode < ValueOpcode::Count;
|
||||
if (reader.ok) reader.insts.push_back(&values.emplace_back(opcode, flags));
|
||||
}
|
||||
for (auto* inst: reader.insts) {
|
||||
if (inst->GetOpcode() != ValueOpcode::Phi) {
|
||||
for (size_t index = 0; index < inst->NumArgs(); ++index) {
|
||||
Value arg;
|
||||
Io(reader, arg);
|
||||
inst->SetArg(index, arg);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::vector<Value> args;
|
||||
std::vector<uint32_t> predecessors;
|
||||
IoAll(reader, args, predecessors);
|
||||
for (const auto arg: args) inst->AddPhiOperand(nullptr, arg);
|
||||
if (!predecessors.empty()) phis.emplace(inst, std::move(predecessors));
|
||||
}
|
||||
}
|
||||
|
||||
// Binds every plan field: one added later fails to compile here until it is stored or derived.
|
||||
template <class S, class P> void IoPlan(S& io, P& plan) {
|
||||
auto& [stage, shader_hash, user_data_base, user_data_count, values, memory_info, descriptor_sources,
|
||||
selector_groups, control_flow, control_phis, srt_reads, source_reads, capture_specialization_reads,
|
||||
srt_plan_complete, resource_tracking_complete, info, uniform_fill,
|
||||
// Derived on load.
|
||||
srt_code, srt_user_data, srt_captures, srt_scratch, material_keys, selector_keys] = plan;
|
||||
IoValues(io, values, control_phis);
|
||||
IoAll(io, stage, shader_hash, user_data_base, user_data_count, memory_info, descriptor_sources,
|
||||
selector_groups, control_flow, srt_reads, source_reads, capture_specialization_reads,
|
||||
srt_plan_complete, resource_tracking_complete, info, uniform_fill);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<uint8_t> SaveResourcePlan(const ResourcePlan& plan) {
|
||||
Writer writer;
|
||||
IoPlan(writer, plan);
|
||||
return std::move(writer.bytes);
|
||||
}
|
||||
|
||||
bool LoadResourcePlan(std::span<const uint8_t> bytes, ResourcePlan& plan) {
|
||||
Reader reader {bytes};
|
||||
IoPlan(reader, plan);
|
||||
return reader.ok && reader.bytes.empty() && DerivePlanState(plan);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
@@ -53,6 +53,11 @@ struct ResourceSpecialization {
|
||||
// its values and is independent of the translated shader CFG.
|
||||
ResourcePlan ExtractResourcePlan(const Program& program);
|
||||
|
||||
// Shader cache record of a plan; loading it into a default-constructed plan rebuilds the walker
|
||||
// and scratch state like extraction. A failed load leaves a plan that must not be used.
|
||||
std::vector<uint8_t> SaveResourcePlan(const ResourcePlan& plan);
|
||||
bool LoadResourcePlan(std::span<const uint8_t> bytes, ResourcePlan& plan);
|
||||
|
||||
// Refreshes cached resources and specialization in place. A failed refresh must not be used.
|
||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <fmt/format.h>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
@@ -1014,7 +1016,9 @@ private:
|
||||
(!shared_image_heap && (a.table_offset != b.table_offset ||
|
||||
a.table_immediate != b.table_immediate ||
|
||||
a.table_stride != b.table_stride)) || a.indexed != b.indexed ||
|
||||
a.workgroup_axis != b.workgroup_axis || a.sources != b.sources ||
|
||||
a.workgroup_axis != b.workgroup_axis ||
|
||||
a.workgroup_multiplier != b.workgroup_multiplier ||
|
||||
a.workgroup_shift != b.workgroup_shift || a.sources != b.sources ||
|
||||
a.signed_count != b.signed_count ||
|
||||
!EquivalentValue(m_program, a.key_count, b.key_count) ||
|
||||
a.selector_first.IsEmpty() != b.selector_first.IsEmpty() ||
|
||||
@@ -1136,31 +1140,63 @@ private:
|
||||
LaneQuantifier lanes = LaneQuantifier::All;
|
||||
};
|
||||
|
||||
// The immediate that edge proves value differs from on every lane, if any.
|
||||
std::optional<uint32_t> ExcludedBy(const EdgePredicate& edge, Value value) const {
|
||||
if (edge.lanes != LaneQuantifier::All) return {};
|
||||
const auto* test = edge.condition.TryInstruction();
|
||||
if (test != nullptr && test->NumArgs() == 2u &&
|
||||
((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) ||
|
||||
(test->GetOpcode() == ValueOpcode::IEqual32 && !edge.positive))) {
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
uint32_t immediate;
|
||||
if (ImmediateU32(test->Arg(arg), immediate) &&
|
||||
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return immediate;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool NonzeroOnEntry(Value value, const Block* block) const {
|
||||
if (block == nullptr) return false;
|
||||
bool witnessed = false;
|
||||
const bool guarded = GuardedOnEntry(block, [](const Block* current) {
|
||||
return current->ImmPredecessors().size() != 1u;
|
||||
}, [&](const EdgePredicate& edge) {
|
||||
if (edge.lanes != LaneQuantifier::All) return false;
|
||||
const auto* test = edge.condition.TryInstruction();
|
||||
if (test != nullptr && test->NumArgs() == 2u &&
|
||||
((test->GetOpcode() == ValueOpcode::INotEqual32 && edge.positive) ||
|
||||
(test->GetOpcode() == ValueOpcode::IEqual32 && !edge.positive))) {
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
uint32_t immediate;
|
||||
if (ImmediateU32(test->Arg(arg), immediate) && immediate == 0u &&
|
||||
EquivalentValue(m_program, test->Arg(arg ^ 1u), value)) return witnessed = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return ExcludedBy(edge, value) == 0u && (witnessed = true);
|
||||
});
|
||||
return guarded && witnessed;
|
||||
}
|
||||
|
||||
uint32_t WorkgroupAxis(Value key) const {
|
||||
// The key that every path from its definition to block rules out, if any.
|
||||
std::optional<uint32_t> ExcludedKeyOnEntry(Value key, const Block* block) const {
|
||||
const auto* definition = key.Resolve().TryInstruction();
|
||||
if (definition == nullptr) return {};
|
||||
std::optional<uint32_t> excluded;
|
||||
const auto defined = [&](const Block* current) { return current == definition->Parent(); };
|
||||
const bool guarded = GuardedOnEntry(block, defined, [&](const EdgePredicate& edge) {
|
||||
const auto immediate = ExcludedBy(edge, key);
|
||||
if (!immediate || (excluded && excluded != immediate)) return false;
|
||||
excluded = immediate;
|
||||
return true;
|
||||
});
|
||||
return guarded ? excluded : std::nullopt;
|
||||
}
|
||||
|
||||
// The workgroup ID axis of key; with first, also of a runtime first + workgroup ID sum.
|
||||
uint32_t WorkgroupAxis(Value key, Value* first = nullptr) const {
|
||||
const auto* builtin = key.Resolve().TryInstruction();
|
||||
uint32_t axis = UINT32_MAX;
|
||||
if (first != nullptr && builtin != nullptr && builtin->GetOpcode() == ValueOpcode::IAdd32) {
|
||||
for (uint32_t arm = 0; arm < 2u; ++arm) {
|
||||
axis = WorkgroupAxis(builtin->Arg(arm));
|
||||
const auto base = builtin->Arg(arm ^ 1u);
|
||||
if (axis == UINT32_MAX ||
|
||||
!ValidateRuntimeValue(m_program, base, RuntimeValueType::Integer)) continue;
|
||||
*first = base;
|
||||
return axis;
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (m_program.stage != ShaderType::Compute || builtin == nullptr ||
|
||||
builtin->GetOpcode() != ValueOpcode::GetBuiltin || builtin->NumArgs() != 2u ||
|
||||
builtin->Arg(0) != Value(static_cast<uint32_t>(StageInputKind::WorkgroupId)) ||
|
||||
@@ -1168,6 +1204,27 @@ private:
|
||||
return axis;
|
||||
}
|
||||
|
||||
// Matches key = (first + workgroup ID) * multiplier >> shift with shift >= 32; first and the
|
||||
// division are optional.
|
||||
bool MatchWorkgroupKey(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
|
||||
uint32_t bits = 0, multiplier = 0;
|
||||
const auto* shift = key.Resolve().TryInstruction();
|
||||
if (shift != nullptr && shift->GetOpcode() == ValueOpcode::ShiftRightLogical32) {
|
||||
const auto* high = shift->Arg(0).Resolve().TryInstruction();
|
||||
if (high != nullptr && high->GetOpcode() == ValueOpcode::UMulHi &&
|
||||
ImmediateU32(shift->Arg(1), bits) && bits < 32u &&
|
||||
ImmediateU32(high->Arg(1), multiplier)) {
|
||||
key = high->Arg(0);
|
||||
bits += 32u;
|
||||
}
|
||||
}
|
||||
indirect.workgroup_axis = WorkgroupAxis(key, &indirect.selector_first);
|
||||
if (indirect.workgroup_axis == UINT32_MAX) return false;
|
||||
indirect.workgroup_multiplier = multiplier;
|
||||
indirect.workgroup_shift = bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchTableOffset(Value value, Value& key, uint32_t& offset, uint32_t& stride) const {
|
||||
offset = 0;
|
||||
for (;;) {
|
||||
@@ -1672,6 +1729,8 @@ private:
|
||||
uint32_t initial_arm;
|
||||
};
|
||||
|
||||
static bool ZeroBased(uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; }
|
||||
|
||||
std::optional<Induction> MatchInduction(Value key, const auto& accepts_step) const {
|
||||
key = key.Resolve();
|
||||
const auto* phi = key.TryInstruction();
|
||||
@@ -1733,8 +1792,7 @@ private:
|
||||
}
|
||||
|
||||
uint32_t PostTestLoopMaximum(Value key) const {
|
||||
const auto induction = MatchInduction(key,
|
||||
[](uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; });
|
||||
const auto induction = MatchInduction(key, ZeroBased);
|
||||
if (!induction) return UINT32_MAX;
|
||||
const auto [phi, step, initial_arm] = *induction;
|
||||
const auto* incoming = phi->PhiBlock(initial_arm ^ 1u);
|
||||
@@ -1767,8 +1825,7 @@ private:
|
||||
|
||||
const Inst* BoundedLoop(Value key, const Block* use, const auto& accepts_bound,
|
||||
LaneQuantifier lanes = LaneQuantifier::Any) const {
|
||||
return BoundedInduction(key, use,
|
||||
[](uint32_t initial, uint32_t step) { return initial == 0u && step == 1u; },
|
||||
return BoundedInduction(key, use, ZeroBased,
|
||||
[&](const Inst& compare, uint32_t initial) {
|
||||
return (compare.GetOpcode() == ValueOpcode::SLessThan32 ||
|
||||
compare.GetOpcode() == ValueOpcode::ULessThan32) &&
|
||||
@@ -1793,6 +1850,29 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
// A zero-based key that only per-lane data bounds takes the count all uniform zero-based
|
||||
// loops in the program share, assuming each lane's count stays within it.
|
||||
bool ProgramLoopCount(Value key, DescriptorSource::IndirectDescriptor& indirect) const {
|
||||
if (!MatchInduction(key, ZeroBased) || BoundedLoop(key, nullptr) != nullptr) return false;
|
||||
const Inst* count = nullptr;
|
||||
for (auto* block: m_program.blocks) {
|
||||
for (auto& phi: *block) {
|
||||
if (phi.GetOpcode() != ValueOpcode::Phi) break;
|
||||
const auto* compare = BoundedLoop(Value(&phi), nullptr);
|
||||
if (compare == nullptr) continue;
|
||||
if (count != nullptr && !EquivalentValue(m_program, compare->Arg(1), count->Arg(1)))
|
||||
return false;
|
||||
count = compare;
|
||||
}
|
||||
}
|
||||
if (count == nullptr) return false;
|
||||
std::printf("Warning: shader 0x%016llx bounds a per-lane table loop by its uniform loops' "
|
||||
"count\n", static_cast<unsigned long long>(m_program.shader_hash));
|
||||
indirect.key_count = count->Arg(1);
|
||||
indirect.signed_count = count->GetOpcode() == ValueOpcode::SLessThan32;
|
||||
return true;
|
||||
}
|
||||
|
||||
Value ActiveValue(Value value, Value guard) const {
|
||||
value = value.Resolve();
|
||||
while (const auto* select = value.TryInstruction()) {
|
||||
@@ -1829,6 +1909,10 @@ private:
|
||||
}
|
||||
|
||||
bool Dominates(const Block* initial, const Block* use, Value witness = {}) const {
|
||||
// The proven local-zero invocation fails every all-lane bypass condition that excludes it,
|
||||
// such as an all-inactive or an all-nonzero bypass.
|
||||
const auto* negated = SimplifyGuard(witness).TryInstruction();
|
||||
const bool nonzero = negated != nullptr && negated->GetOpcode() == ValueOpcode::LogicalNot;
|
||||
std::vector<const Block*> pending {use};
|
||||
for (size_t index = 0; index < pending.size(); ++index) {
|
||||
const auto* current = pending[index];
|
||||
@@ -1837,9 +1921,9 @@ private:
|
||||
for (const auto* previous: current->ImmPredecessors()) {
|
||||
if (!witness.IsEmpty()) {
|
||||
const auto edge = ConditionalEdge(previous, current);
|
||||
// The proven local-zero invocation makes an all-inactive bypass impossible.
|
||||
if (edge && edge->lanes == LaneQuantifier::All && !edge->positive &&
|
||||
Implies(witness, edge->condition)) continue;
|
||||
if (edge && edge->lanes == LaneQuantifier::All &&
|
||||
(edge->positive ? nonzero && Implies(edge->condition, negated->Arg(0))
|
||||
: Implies(witness, edge->condition))) continue;
|
||||
}
|
||||
if (std::ranges::find(pending, previous) == pending.end()) pending.push_back(previous);
|
||||
}
|
||||
@@ -1948,7 +2032,10 @@ private:
|
||||
|
||||
bool LocalZeroWitness(Value guard) const {
|
||||
const auto* equal = SimplifyGuard(guard).TryInstruction();
|
||||
if (equal == nullptr || equal->GetOpcode() != ValueOpcode::IEqual32) return false;
|
||||
const bool negated = equal != nullptr && equal->GetOpcode() == ValueOpcode::LogicalNot;
|
||||
if (negated) equal = SimplifyGuard(equal->Arg(0)).TryInstruction();
|
||||
const auto expected = negated ? ValueOpcode::INotEqual32 : ValueOpcode::IEqual32;
|
||||
if (equal == nullptr || equal->GetOpcode() != expected) return false;
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg) {
|
||||
if (equal->Arg(arg).Resolve() != Value(0u)) continue;
|
||||
Value induction;
|
||||
@@ -1982,6 +2069,10 @@ private:
|
||||
EquivalentValue(m_program, test->Arg(0), value)) return test->Arg(1);
|
||||
if ((positive ? op == ValueOpcode::UGreaterThan32 : op == ValueOpcode::ULessThanEqual32) &&
|
||||
EquivalentValue(m_program, test->Arg(1), value)) return test->Arg(0);
|
||||
uint32_t limit = 0;
|
||||
if ((positive ? op == ValueOpcode::ULessThanEqual32 : op == ValueOpcode::UGreaterThan32) &&
|
||||
ImmediateU32(test->Arg(1), limit) && limit != UINT32_MAX &&
|
||||
EquivalentValue(m_program, test->Arg(0), value)) return Value(limit + 1u);
|
||||
const auto* scan = value.Resolve().TryInstruction();
|
||||
if (finite_bitscan && scan != nullptr && scan->GetOpcode() == ValueOpcode::FindILsb32 &&
|
||||
(positive ? op == ValueOpcode::INotEqual32 : op == ValueOpcode::IEqual32)) {
|
||||
@@ -2056,7 +2147,7 @@ private:
|
||||
const auto* read = ActiveValue(value, active).TryInstruction();
|
||||
if (read == nullptr || read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
||||
m_program.stage != ShaderType::Compute ||
|
||||
read->Arg(1).Resolve() != Value(true)) return {};
|
||||
!Implies(active, read->Arg(1))) return {};
|
||||
uint64_t group_size = 1u;
|
||||
for (const auto dimension: m_input.compute->threads_num) {
|
||||
if (dimension == 0u || dimension > m_input.compute->wave_size) return {};
|
||||
@@ -2087,10 +2178,16 @@ private:
|
||||
const auto* limit = BoundedLoop(read_offset.index, read->Parent(),
|
||||
[](const Inst& compare, uint32_t) { return compare.GetOpcode() == ValueOpcode::ULessThan32; });
|
||||
if (limit == nullptr) return {};
|
||||
auto counter_active = active;
|
||||
const auto* counter_read = ActiveValue(limit->Arg(1), active).TryInstruction();
|
||||
// Every lane reads the same counter word, so a selected lane carries it.
|
||||
if (counter_read != nullptr && counter_read->GetOpcode() == ValueOpcode::ReadFirstLane) {
|
||||
counter_active = counter_read->Arg(1);
|
||||
counter_read = ActiveValue(counter_read->Arg(0), counter_active).TryInstruction();
|
||||
}
|
||||
uint32_t consumed_counter = 0;
|
||||
if (counter_read == nullptr || counter_read->GetOpcode() != ValueOpcode::LoadSharedU32 ||
|
||||
counter_read->Arg(1).Resolve() != Value(true) ||
|
||||
!Implies(counter_active, counter_read->Arg(1)) ||
|
||||
!constant_address(*counter_read, consumed_counter)) return {};
|
||||
|
||||
struct Initializer { const Inst* inst; uint32_t address; uint32_t words; };
|
||||
@@ -2304,25 +2401,23 @@ private:
|
||||
((offset | stride | memory->offset) & 3u) == 0u &&
|
||||
uint64_t {offset} + memory->offset <= UINT32_MAX;
|
||||
if (affine) {
|
||||
workgroup_axis = WorkgroupAxis(index);
|
||||
if (workgroup_axis == UINT32_MAX) {
|
||||
const auto* sum = index.Resolve().TryInstruction();
|
||||
if (sum != nullptr && sum->GetOpcode() == ValueOpcode::IAdd32) {
|
||||
for (uint32_t arm = 0; arm < 2u; ++arm) {
|
||||
const auto axis = WorkgroupAxis(sum->Arg(arm));
|
||||
const auto base = sum->Arg(arm ^ 1u);
|
||||
if (axis == UINT32_MAX ||
|
||||
!ValidateRuntimeValue(m_program, base, RuntimeValueType::Integer)) continue;
|
||||
workgroup_axis = axis;
|
||||
first = base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
workgroup_axis = WorkgroupAxis(index, &first);
|
||||
const auto* bound = BoundedLoop(index, read->Parent());
|
||||
if (bound != nullptr && bound->GetOpcode() == ValueOpcode::ULessThan32) {
|
||||
count = bound->Arg(1);
|
||||
} else if (workgroup_axis == UINT32_MAX && !require_bound) {
|
||||
// Other bounded indices, such as bit scans, read the records up to their maximum;
|
||||
// a bit scan of a nonzero mask load reads only the ranks its words set.
|
||||
const auto maximum = SelectorMaximum(index);
|
||||
if (maximum == 31u) selector.ranks = BitScanRanks(index);
|
||||
if (maximum < MaxIndirectDescriptorProbes) count = Value(maximum + 1u);
|
||||
}
|
||||
// A record selected from a shared append list is below the list's bound.
|
||||
const auto* lane = index.Resolve().TryInstruction();
|
||||
if (count.IsEmpty() && lane != nullptr &&
|
||||
lane->GetOpcode() == ValueOpcode::ReadFirstLane &&
|
||||
HasActiveLane(lane->Arg(1), lane->Parent()))
|
||||
count = SharedLoadBound(lane->Arg(0), lane->Arg(1));
|
||||
}
|
||||
// Raw pointers have no V# bounds for enumerating an unbounded selector domain.
|
||||
if (memory->kind == ResourceKind::ScalarAddress && count.IsEmpty() &&
|
||||
@@ -2498,7 +2593,8 @@ private:
|
||||
// Each equation is an inductive subset obligation. Phis require every incoming
|
||||
// value; AND needs either operand. Grounding rejects cycles without an initial
|
||||
// value, then the greatest fixed point checks every initial and carried edge.
|
||||
bool BitsetMaskSubset(Value mask, Value active, Value word, const Inst& read) const {
|
||||
bool BitsetMaskSubset(Value mask, Value active, Value word,
|
||||
std::span<const Inst* const> reads) const {
|
||||
struct State {
|
||||
Value value;
|
||||
Value guard;
|
||||
@@ -2510,7 +2606,10 @@ private:
|
||||
bool all = true;
|
||||
std::vector<size_t> terms;
|
||||
};
|
||||
const auto load_guard = read.Arg(4).Resolve();
|
||||
// A scalar load reads one word without EXEC; four-word loads take their guard.
|
||||
const bool scalar = reads[0]->GetOpcode() == ValueOpcode::ReadConstBuffer;
|
||||
const auto load_guard = scalar ? Value(true) : reads[0]->Arg(4).Resolve();
|
||||
const auto words = static_cast<uint32_t>(reads.size()) * (scalar ? 1u : 4u);
|
||||
std::vector<State> states(2);
|
||||
std::vector<Equation> equations(2);
|
||||
const auto add = [&](State state) {
|
||||
@@ -2521,9 +2620,9 @@ private:
|
||||
equations.emplace_back();
|
||||
return states.size() - 1u;
|
||||
};
|
||||
std::array<size_t, 5> roots {add({.value = active})};
|
||||
for (uint32_t component = 0; component < 4u; ++component)
|
||||
roots[component + 1u] = add({mask, active, component, component});
|
||||
std::vector<size_t> roots {add({.value = active})};
|
||||
for (uint32_t component = 0; component < words; ++component)
|
||||
roots.push_back(add({mask, active, component, component}));
|
||||
for (size_t index = 2; index < states.size(); ++index) {
|
||||
const auto state = states[index];
|
||||
const auto* inst = state.value.TryInstruction();
|
||||
@@ -2573,11 +2672,14 @@ private:
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::CompositeExtractU32x4: {
|
||||
uint32_t component;
|
||||
if (inst->Arg(0).Resolve().TryInstruction() == &read &&
|
||||
ImmediateU32(inst->Arg(1), component) && component == state.component)
|
||||
if (inst->Arg(0).Resolve().TryInstruction() == reads[state.component / 4u] &&
|
||||
ImmediateU32(inst->Arg(1), component) && component == state.component % 4u)
|
||||
equation.terms = {1u};
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::ReadConstBuffer:
|
||||
if (scalar && inst == reads[state.component]) equation.terms = {1u};
|
||||
break;
|
||||
case ValueOpcode::Phi:
|
||||
// The predecessor may be an earlier outer iteration. Establish
|
||||
// this register's invariant without the current word or EXEC.
|
||||
@@ -2598,17 +2700,17 @@ private:
|
||||
const auto* masked = key.TryInstruction();
|
||||
uint32_t bound = 0;
|
||||
if (masked != nullptr && masked->GetOpcode() == ValueOpcode::BitwiseAnd32 &&
|
||||
DescendingLoop(word, masked->Parent(), bound) && bound == 3u) {
|
||||
DescendingLoop(word, masked->Parent(), bound) && bound == words - 1u) {
|
||||
for (uint32_t side = 0; side < 2u; ++side) {
|
||||
uint32_t bits;
|
||||
if (ImmediateU32(masked->Arg(side), bits) && (bits & 3u) == 3u)
|
||||
if (ImmediateU32(masked->Arg(side), bits) && (bits & bound) == bound)
|
||||
key = masked->Arg(side ^ 1u).Resolve();
|
||||
}
|
||||
}
|
||||
return EquivalentValue(m_program, key, word);
|
||||
});
|
||||
uint32_t literal;
|
||||
if (ImmediateU32(equality, literal) && literal < 4u) selected_word = literal;
|
||||
if (ImmediateU32(equality, literal) && literal < words) selected_word = literal;
|
||||
equation.terms.push_back(term(inst->Arg(1), inst->Arg(0), selected_word));
|
||||
}
|
||||
if (!selected || !*selected)
|
||||
@@ -2646,7 +2748,53 @@ private:
|
||||
return std::ranges::all_of(roots, [&](size_t root) { return valid[root]; });
|
||||
}
|
||||
|
||||
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds,
|
||||
// The mask words whose set bits are every record a bit scan of index selects: the scanned
|
||||
// value keeps only bits of one scalar load, which reads a word its address can reach.
|
||||
std::optional<DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks>
|
||||
BitScanRanks(Value index) {
|
||||
const auto* scan = index.Resolve().TryInstruction();
|
||||
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindILsb32) return {};
|
||||
const auto* masked = scan->Arg(0).Resolve().TryInstruction();
|
||||
if (masked == nullptr) return {};
|
||||
std::vector<const Inst*> values {masked};
|
||||
const Inst* load = nullptr;
|
||||
for (size_t i = 0; i < values.size() && load == nullptr; ++i) {
|
||||
const auto* value = values[i];
|
||||
if (value->GetOpcode() == ValueOpcode::ReadConstBuffer) load = value;
|
||||
if (value->GetOpcode() != ValueOpcode::Phi &&
|
||||
value->GetOpcode() != ValueOpcode::BitwiseAnd32) continue;
|
||||
for (size_t arg = 0; arg < value->NumArgs(); ++arg) {
|
||||
const auto* next = value->Arg(arg).Resolve().TryInstruction();
|
||||
if (next != nullptr && std::ranges::find(values, next) == values.end())
|
||||
values.push_back(next);
|
||||
}
|
||||
}
|
||||
uint32_t memory_index = 0;
|
||||
const auto* memory = load != nullptr ? ScalarReadMemory(*load, memory_index) : nullptr;
|
||||
if (memory == nullptr || memory->offset > INT32_MAX ||
|
||||
!MemoryIndexBelongsTo(memory_index, *load) ||
|
||||
!BitsetMaskSubset(scan->Arg(0), Value(true), Value(0u), std::array {load})) return {};
|
||||
auto address = load->Arg(1);
|
||||
auto active = Value(true);
|
||||
const auto* lane = address.Resolve().TryInstruction();
|
||||
if (lane != nullptr && lane->GetOpcode() == ValueOpcode::ReadFirstLane) {
|
||||
if (!HasActiveLane(lane->Arg(1), lane->Parent())) return {};
|
||||
active = lane->Arg(1);
|
||||
address = lane->Arg(0);
|
||||
}
|
||||
// A left shift keeps the address a multiple of its scale; other addresses read any word.
|
||||
const auto* shift = ActiveValue(address, active).TryInstruction();
|
||||
uint32_t bits = 0;
|
||||
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
|
||||
!ImmediateU32(shift->Arg(1), bits) || bits >= 32u) bits = 0;
|
||||
DescriptorSource source;
|
||||
if (!MakeRuntimeTableSource(*load, source)) return {};
|
||||
return DescriptorSource::IndirectDescriptor::SelectorRead::MaskRanks {
|
||||
.source = InternSource(source), .stride = std::max(1u << bits, 4u),
|
||||
.offset = memory->offset & ~3u};
|
||||
}
|
||||
|
||||
bool MatchBitsetSelector(Value rank, Value active, SelectorBounds bounds, uint32_t words,
|
||||
DescriptorSource::IndirectDescriptor& indirect) {
|
||||
const auto selected_value = [&](Value value) {
|
||||
for (;;) {
|
||||
@@ -2670,7 +2818,7 @@ private:
|
||||
}
|
||||
const auto* word_inst = word.TryInstruction();
|
||||
if (word_inst == nullptr || std::ranges::none_of(bounds, [&](const auto& bound) {
|
||||
return bound.first == word_inst && bound.second == 3u;
|
||||
return bound.first == word_inst && bound.second == words - 1u;
|
||||
})) return false;
|
||||
// FFBH's leading-zero count is inverted again to recover the set-bit rank.
|
||||
for (uint32_t inverse = 0; inverse < 2u; ++inverse) {
|
||||
@@ -2682,21 +2830,34 @@ private:
|
||||
const auto* scan = bit.TryInstruction();
|
||||
if (scan == nullptr || scan->GetOpcode() != ValueOpcode::FindUMsb32 ||
|
||||
!ImpliesNonzero(active, scan->Arg(0))) return false;
|
||||
const auto bitmap_load = [&](const Inst& read, uint32_t offset) {
|
||||
const auto index = read.Flags<MemoryFlags>().index;
|
||||
if (read.GetOpcode() != ValueOpcode::LoadBufferU32x4 || InCycle(read.Parent()) ||
|
||||
index >= m_program.memory_info.size()) return false;
|
||||
const auto& memory = m_program.memory_info[index];
|
||||
return memory.kind == ResourceKind::Buffer && !memory.typed && !memory.formatted &&
|
||||
memory.idxen && !memory.offen && memory.offset == offset &&
|
||||
memory.data_bits == 32u && memory.data_dwords == 4u &&
|
||||
read.Arg(2).Resolve() == Value(0u) && read.Arg(3).Resolve() == Value(0u);
|
||||
};
|
||||
for (const auto* block: m_program.blocks) {
|
||||
for (const auto& read: *block) {
|
||||
if (read.GetOpcode() != ValueOpcode::LoadBufferU32x4 || InCycle(block)) continue;
|
||||
const auto index = read.Flags<MemoryFlags>().index;
|
||||
if (index >= m_program.memory_info.size()) continue;
|
||||
const auto& memory = m_program.memory_info[index];
|
||||
if (memory.kind != ResourceKind::Buffer || memory.typed || memory.formatted ||
|
||||
!memory.idxen || memory.offen || memory.offset != 0u || memory.data_bits != 32u ||
|
||||
memory.data_dwords != 4u || read.Arg(2).Resolve() != Value(0u) ||
|
||||
read.Arg(3).Resolve() != Value(0u) ||
|
||||
!BitsetMaskSubset(scan->Arg(0), active, word, read)) continue;
|
||||
if (!bitmap_load(read, 0u)) continue;
|
||||
// An eight-word record continues in a second four-word load of the same buffer and EXEC.
|
||||
std::array<const Inst*, 2> reads {&read};
|
||||
if (words == 8u)
|
||||
for (const auto* other: m_program.blocks)
|
||||
for (const auto& load: *other)
|
||||
if (reads[1] == nullptr && bitmap_load(load, 16u) &&
|
||||
EquivalentValue(m_program, load.Arg(0), read.Arg(0)) &&
|
||||
EquivalentValue(m_program, load.Arg(4), read.Arg(4))) reads[1] = &load;
|
||||
const auto parts = std::span(reads).first(words / 4u);
|
||||
DescriptorSource source;
|
||||
if (!MakeRuntimeTableSource(read, source)) continue;
|
||||
if (std::ranges::find(parts, nullptr) != parts.end() ||
|
||||
!BitsetMaskSubset(scan->Arg(0), active, word, parts) ||
|
||||
!MakeRuntimeTableSource(read, source)) continue;
|
||||
indirect.selector.emplace(DescriptorSource::IndirectDescriptor::SelectorRead {
|
||||
.source = InternSource(source), .stride = 16u, .offset = 0u, .bitset = true});
|
||||
.source = InternSource(source), .stride = words * 4u, .offset = 0u, .bitset = true});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2726,7 +2887,8 @@ private:
|
||||
first.SetArg(1, Value(maximum));
|
||||
indirect.selector_first = Value(&first);
|
||||
indirect.key_count = Value(maximum + 1u);
|
||||
if (maximum == 127u) MatchBitsetSelector(lane->Arg(0), lane->Arg(1), bounds, indirect);
|
||||
if (maximum == 127u || maximum == 255u)
|
||||
MatchBitsetSelector(lane->Arg(0), lane->Arg(1), bounds, (maximum + 1u) / 32u, indirect);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2993,6 +3155,47 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
// The EXEC predicate of the image access through use; empty for any other use.
|
||||
Value AccessPredicate(const Use& use, bool sampler) const {
|
||||
const auto& access = *use.user;
|
||||
if (use.operand != (sampler ? 1u : 0u) ||
|
||||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
|
||||
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
|
||||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
|
||||
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
|
||||
const auto mask = access.Arg(access.NumArgs() - 1u);
|
||||
return mask.GetType() == Type::U1 ? mask : Value {};
|
||||
}
|
||||
|
||||
// A key no bound covers takes its maximum from a signed clamp to a constant, on the key or on
|
||||
// the key plus a constant in an earlier-tracked sibling read of the same table. Negative keys
|
||||
// and keys past that maximum read the first row.
|
||||
uint32_t ClampedTableMaximum(Value key, const DescriptorSource& table,
|
||||
const DescriptorSource::IndirectDescriptor& indirect) const {
|
||||
uint32_t row = 0, offset = 0;
|
||||
const auto clamped = [&](Value value) {
|
||||
const auto* min = value.Resolve().TryInstruction();
|
||||
if (min != nullptr && min->GetOpcode() == ValueOpcode::SMin32)
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg)
|
||||
if (ImmediateU32(min->Arg(arg), row)) return min->Arg(arg ^ 1u);
|
||||
return Value {};
|
||||
};
|
||||
if (!clamped(key).IsEmpty()) return row;
|
||||
for (const auto& sibling: m_indirect_descriptors) {
|
||||
const auto& read = *m_sources[sibling.source].indirect_descriptor;
|
||||
const auto* add = clamped(sibling.key).Resolve().TryInstruction();
|
||||
if (add == nullptr || add->GetOpcode() != ValueOpcode::IAdd32 ||
|
||||
m_sources[read.table_source] != table ||
|
||||
read.table_offset != indirect.table_offset ||
|
||||
read.table_stride != indirect.table_stride ||
|
||||
read.table_immediate != indirect.table_immediate) continue;
|
||||
for (uint32_t arg = 0; arg < 2u; ++arg)
|
||||
if (ImmediateU32(add->Arg(arg), offset) &&
|
||||
EquivalentValue(m_program, add->Arg(arg ^ 1u), key)) return row;
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
bool TryMakeTableDescriptor(Inst& handle, const DescriptorSource& descriptor,
|
||||
IndirectDescriptorPlan& plan) {
|
||||
const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource;
|
||||
@@ -3019,6 +3222,7 @@ private:
|
||||
} else if (!plan.table_indexed &&
|
||||
MatchScalarMaterialKey(key, indirect, material_source,
|
||||
!sampler && table_source.dword_count == 4u, sampler)) {
|
||||
indirect.selector->excluded = ExcludedKeyOnEntry(key, handle.Parent());
|
||||
// Keep live descriptor payloads, but project descriptor-only SMEM onto
|
||||
// the GPU key using the same plan as other indirect image reads.
|
||||
plan.retain_reads |= table_source.dword_count == 4u;
|
||||
@@ -3080,36 +3284,42 @@ private:
|
||||
if (indirect.key_count.IsEmpty() &&
|
||||
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, false) &&
|
||||
!MatchUniformizedMaterialKey(key, handle, indirect, material_source, true) &&
|
||||
!MatchReversedImageRange(key, indirect)) {
|
||||
const auto maximum = SelectorMaximum(key);
|
||||
if (maximum >= MaxIndirectDescriptorProbes) return false;
|
||||
indirect.key_count = Value(maximum + 1u);
|
||||
!MatchReversedImageRange(key, indirect) && !MatchWorkgroupKey(key, indirect)) {
|
||||
auto maximum = SelectorMaximum(key);
|
||||
if (maximum >= MaxIndirectDescriptorProbes) {
|
||||
// Each image access bounds the key on the lanes it executes.
|
||||
maximum = 0u;
|
||||
for (const auto& use: handle.Uses()) {
|
||||
const auto active = AccessPredicate(use, false);
|
||||
if (active.IsEmpty()) {
|
||||
maximum = UINT32_MAX;
|
||||
break;
|
||||
}
|
||||
maximum = std::max(maximum, SelectorMaximum(key, {}, 0, active));
|
||||
}
|
||||
}
|
||||
if (maximum >= MaxIndirectDescriptorProbes)
|
||||
maximum = ClampedTableMaximum(key, table_source, indirect);
|
||||
if (maximum < MaxIndirectDescriptorProbes)
|
||||
indirect.key_count = Value(maximum + 1u);
|
||||
else if (!ProgramLoopCount(key, indirect))
|
||||
return false;
|
||||
}
|
||||
if ((table_offset & 3u) != 0u) return false;
|
||||
} else {
|
||||
// Keep the GPU selector and descriptor reads, and preserve the proven scalar
|
||||
// offset domain within the bounded image table.
|
||||
if (sampler || plan.split_offsets) return false;
|
||||
const auto active = [&](const Use& use, bool sampler) -> Value {
|
||||
const auto& access = *use.user;
|
||||
if (use.operand != (sampler ? 1u : 0u) ||
|
||||
ImageOpcodeInfoOf(access.GetOpcode()).access == ImageAccess::None) return {};
|
||||
if (sampler && (access.GetOpcode() != ValueOpcode::ImageSampleRaw ||
|
||||
(m_program.memory_info[access.Flags<MemoryFlags>().index].image_sample_flags &
|
||||
Decoder::ImageSampleFlagAdjust) != 0u)) return {};
|
||||
const auto mask = access.Arg(access.NumArgs() - 1u);
|
||||
return mask.GetType() == Type::U1 ? mask : Value {};
|
||||
};
|
||||
bool shared_selector = false;
|
||||
if (!plan.table_indexed) {
|
||||
for (const auto& owner: m_indirect_descriptors) {
|
||||
const auto& selected = *m_sources[owner.source].indirect_descriptor;
|
||||
if (!selected.selector || !EquivalentValue(m_program, key, owner.key) ||
|
||||
!std::ranges::all_of(handle.Uses(), [&](const Use& target) {
|
||||
const auto target_active = active(target, false);
|
||||
const auto target_active = AccessPredicate(target, false);
|
||||
return !target_active.IsEmpty() &&
|
||||
std::ranges::any_of(owner.handle->Uses(), [&](const Use& witness) {
|
||||
const auto owner_active = active(witness,
|
||||
const auto owner_active = AccessPredicate(witness,
|
||||
owner.handle->GetOpcode() == ValueOpcode::GetSamplerResource);
|
||||
return !owner_active.IsEmpty() && AvailableBefore(*witness.user, *target.user) &&
|
||||
Implies(target_active, owner_active);
|
||||
@@ -3430,6 +3640,42 @@ private:
|
||||
return result;
|
||||
}
|
||||
|
||||
// Bounds of an F32 value on the lanes where guard holds; NaN when unknown.
|
||||
std::pair<float, float> FloatRange(Value value, Value guard, uint32_t depth = 0) const {
|
||||
constexpr std::pair unknown {NAN, NAN};
|
||||
value = ActiveValue(value, guard);
|
||||
if (value.IsImmediate() && value.GetType() == Type::F32)
|
||||
return {value.F32Value(), value.F32Value()};
|
||||
const auto* inst = value.TryInstruction();
|
||||
// Each multiply-add evaluates three operands; keep the search shallow.
|
||||
if (depth > 12u || inst == nullptr) return unknown;
|
||||
const auto range = [&](uint32_t arg) {
|
||||
return FloatRange(inst->Arg(arg), guard, depth + 1u);
|
||||
};
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::BitCastF32U32:
|
||||
case ValueOpcode::BitCastU32F32: return range(0);
|
||||
// Clamping maps NaN to zero.
|
||||
case ValueOpcode::FPSaturate32: return {0.0f, 1.0f};
|
||||
case ValueOpcode::FPFloor32: {
|
||||
const auto [low, high] = range(0);
|
||||
return {std::floor(low), std::floor(high)};
|
||||
}
|
||||
case ValueOpcode::FPMad32: {
|
||||
// The rounded multiply and add are monotonic, so the bounds lie at the corners.
|
||||
const auto a = range(0), b = range(1), c = range(2);
|
||||
if (!std::isfinite(a.first) || !std::isfinite(a.second) ||
|
||||
!std::isfinite(b.first) || !std::isfinite(b.second)) return unknown;
|
||||
const auto [low, high] = std::minmax({a.first * b.first, a.first * b.second,
|
||||
a.second * b.first, a.second * b.second});
|
||||
const std::pair bounds {low + c.first, high + c.second};
|
||||
if (!std::isfinite(bounds.first) || !std::isfinite(bounds.second)) return unknown;
|
||||
return bounds;
|
||||
}
|
||||
default: return unknown;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SelectorMaximum(Value value, SelectorBounds bounds = {}, uint32_t depth = 0,
|
||||
Value guard = {}) const {
|
||||
if (depth > 32u) return UINT32_MAX;
|
||||
@@ -3445,9 +3691,13 @@ private:
|
||||
switch (inst->GetOpcode()) {
|
||||
case ValueOpcode::ReadFirstLane:
|
||||
return SelectedSelectorDomain(*inst, guard).maximum;
|
||||
case ValueOpcode::ReadLane:
|
||||
// ReadLane ignores EXEC; only context-free bounds on every source lane apply.
|
||||
return PossibleU32Bits(inst->Arg(0));
|
||||
case ValueOpcode::ReadLane: {
|
||||
// ReadLane ignores EXEC; only context-free bounds on every source lane apply,
|
||||
// unless guard proves a waterfall key equal to the executing lanes' local value.
|
||||
const auto local = EqualLocalKey(guard, value);
|
||||
return local.IsEmpty() ? PossibleU32Bits(inst->Arg(0))
|
||||
: SelectorMaximum(local, bounds, depth + 1u, guard);
|
||||
}
|
||||
case ValueOpcode::Phi: {
|
||||
const auto initial = InitialCandidateMask(value);
|
||||
if (!initial.IsEmpty()) return SelectorMaximum(initial, bounds, depth + 1u, guard);
|
||||
@@ -3474,6 +3724,18 @@ private:
|
||||
NonzeroOnEntry(inst->Arg(0), inst->Parent()) ? 31u : UINT32_MAX;
|
||||
case ValueOpcode::UMin32:
|
||||
case ValueOpcode::BitwiseAnd32: return std::min(maximum(0), maximum(1));
|
||||
case ValueOpcode::SMin32: {
|
||||
// Signed and unsigned minimums agree on nonnegative operands.
|
||||
const auto left = maximum(0), right = maximum(1);
|
||||
if (std::max(left, right) <= INT32_MAX) return std::min(left, right);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::ConvertS32F32: {
|
||||
// The conversion truncates toward zero.
|
||||
const auto [low, high] = FloatRange(inst->Arg(0), guard);
|
||||
if (low >= 0.0f && high < 0x1p31f) return static_cast<uint32_t>(high);
|
||||
break;
|
||||
}
|
||||
case ValueOpcode::SelectU32:
|
||||
if (!guard.IsEmpty() && Implies(guard, inst->Arg(0))) return maximum(1);
|
||||
return SelectorPredicateFalse(inst->Arg(0), bounds, depth + 1u, guard)
|
||||
|
||||
@@ -175,6 +175,8 @@ void AddBuiltinInput(ShaderInfo& info, StageInputKind kind) {
|
||||
case StageInputKind::GlobalInvocationId:
|
||||
AddInput(info, kind, 0, 3, "gl_GlobalInvocationID");
|
||||
break;
|
||||
case StageInputKind::BaseVertex: AddInput(info, kind, 0, 1, "gl_BaseVertex"); break;
|
||||
case StageInputKind::BaseInstance: AddInput(info, kind, 0, 1, "gl_BaseInstance"); break;
|
||||
case StageInputKind::PackedAncillary:
|
||||
case StageInputKind::Parameter: break;
|
||||
}
|
||||
@@ -366,7 +368,6 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
info.function_lds |= kind == ResourceKind::Lds &&
|
||||
program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh;
|
||||
if (shared_access == SharedAccess::Append || shared_access == SharedAccess::Consume) {
|
||||
info.subgroup_ballot = true;
|
||||
info.subgroup_shuffle = true;
|
||||
info.subgroup_local_invocation_id = true;
|
||||
}
|
||||
@@ -445,6 +446,8 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
case StageInputKind::InstanceIndex:
|
||||
case StageInputKind::FrontFacing:
|
||||
case StageInputKind::LocalInvocationIndex:
|
||||
case StageInputKind::BaseVertex:
|
||||
case StageInputKind::BaseInstance:
|
||||
if (component != 0u) {
|
||||
return Fail("typed scalar builtin component is out of range");
|
||||
}
|
||||
@@ -517,7 +520,9 @@ void Visit(Program& program, ShaderStageInputInfo input_info, InputUsage& inputs
|
||||
inst.Flags<CFG::BranchCondition>() != CFG::BranchCondition::ScalarInstruction;
|
||||
break;
|
||||
case ValueOpcode::Ballot: info.subgroup_ballot = true; break;
|
||||
case ValueOpcode::SubgroupUMinU32: info.subgroup_arithmetic = true; break;
|
||||
case ValueOpcode::LiveMask: info.subgroup_ballot = info.helper_invocation = true; break;
|
||||
case ValueOpcode::SubgroupUMinU32:
|
||||
case ValueOpcode::SubgroupUMaxU32: info.subgroup_arithmetic = true; break;
|
||||
case ValueOpcode::DppMoveU32:
|
||||
case ValueOpcode::ReadFirstLane:
|
||||
case ValueOpcode::ReadLane: {
|
||||
@@ -576,7 +581,7 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
|
||||
info.has_bitwise_xor = info.uses_lds = info.uses_gds = info.uses_flattened_srt =
|
||||
info.uses_dispatch_threads = info.uses_swizzle = false;
|
||||
info.bvh = info.subgroup_ballot = info.subgroup_vote = info.subgroup_barrier =
|
||||
info.subgroup_shuffle = info.subgroup_arithmetic =
|
||||
info.subgroup_shuffle = info.subgroup_arithmetic = info.helper_invocation =
|
||||
info.subgroup_local_invocation_id = info.compute_derivatives = info.image_gather_extended =
|
||||
info.function_lds = info.function_scratch = info.pixel_valid_mask = info.buffer_int64_atomics =
|
||||
info.buffer_u8 = info.buffer_u16 = info.shared_int64_atomics = info.coherent_buffers = info.float64 = false;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,8 +8,6 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
class Value;
|
||||
|
||||
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span<uint32_t> values);
|
||||
|
||||
// Image descriptors at base + first + k * step, k < count, within size bytes that a sampled
|
||||
@@ -42,7 +40,6 @@ protected:
|
||||
struct SrtRuntime {
|
||||
std::span<const uint32_t> user_data;
|
||||
uint64_t shader_base = 0;
|
||||
SrtMemoryReader read_memory = nullptr;
|
||||
void* userdata = nullptr;
|
||||
SrtMemoryReader read_specialization_memory = nullptr;
|
||||
std::span<const uint32_t> workgroup_counts;
|
||||
@@ -57,20 +54,12 @@ public:
|
||||
SrtRuntime ObservedRuntime();
|
||||
|
||||
private:
|
||||
static bool ReadStrict(void* userdata, uint64_t address, std::span<uint32_t> values);
|
||||
static bool ReadOrdinary(void* userdata, uint64_t address, std::span<uint32_t> values);
|
||||
static bool Read(void* userdata, uint64_t address, std::span<uint32_t> values);
|
||||
|
||||
SrtRuntime m_source;
|
||||
std::vector<std::pair<uint64_t, uint64_t>>& m_ranges;
|
||||
};
|
||||
|
||||
// Retained reads no longer need the guest instruction PC. A clean read evaluates
|
||||
// its address and bounds through the strict reader as well as the final DWORD.
|
||||
struct SrtReadFlags {
|
||||
uint32_t index = 0;
|
||||
uint32_t clean = 0;
|
||||
};
|
||||
|
||||
enum class RuntimeValueType { Any, Integer, ImmutableInteger };
|
||||
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
@@ -78,9 +67,21 @@ bool ValidateRuntimeValue(const ResourcePlan& program, Value value,
|
||||
// Uniform across a compute wave, including values derived from its workgroup ID.
|
||||
// This does not imply that the host can evaluate the value.
|
||||
bool ValidateWaveUniformValue(const ResourcePlan& program, Value value);
|
||||
void CompileSrtWalker(ResourcePlan& program);
|
||||
// Uses the strict reader for values that affect shader specialization.
|
||||
SrtRuntime CleanRuntime(SrtRuntime runtime);
|
||||
// Compiles the plan's x86 walker. Without values it walks the plan's resources; otherwise it
|
||||
// only evaluates the values, with every block reachable.
|
||||
void CompileSrtWalker(ResourcePlan& plan, std::span<const Value> values = {});
|
||||
// Runs the walker. flat receives the scalar reads of reachable blocks and reads, when non-null,
|
||||
// one DWORD range per guest load. False when a reachable read is unknown.
|
||||
bool WalkSrt(const ResourcePlan& plan, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::vector<std::pair<uint64_t, uint64_t>>* reads);
|
||||
// Results of the last walk; false when unknown.
|
||||
bool WalkedValue(const ResourcePlan& plan, Value value, uint32_t& result);
|
||||
bool WalkedDescriptor(const ResourcePlan& plan, uint32_t source, DescriptorValue& result);
|
||||
bool WalkedActive(const ResourcePlan& plan, uint32_t source);
|
||||
bool WalkedImageActive(const ResourcePlan& plan, uint32_t image);
|
||||
// Length of the guest load at rip if it belongs to the walker running on this thread, else 0.
|
||||
// The exception handler completes such a load as a zero read.
|
||||
uint32_t SrtWalkerLoadLength(uint64_t rip);
|
||||
|
||||
// Nonnegative affine coefficients: element zero is constant, remaining elements are
|
||||
// caller-selected leaves. Runtime range proofs must additionally exclude U32 wrap.
|
||||
@@ -124,43 +125,6 @@ std::optional<std::array<uint64_t, Size>> AffineIndex(
|
||||
return left;
|
||||
}
|
||||
|
||||
// One memoized evaluation session shared by the entire shader resource refresh.
|
||||
class SrtWalker {
|
||||
public:
|
||||
SrtWalker(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
SrtWalker* clean_evaluator = nullptr);
|
||||
~SrtWalker();
|
||||
SrtWalker(const SrtWalker&) = delete;
|
||||
SrtWalker& operator=(const SrtWalker&) = delete;
|
||||
|
||||
bool Evaluate(Value value, uint32_t& result);
|
||||
bool EvaluateDescriptor(uint32_t source, DescriptorValue& result);
|
||||
// Refreshes reachable scalar reads and active descriptor sources in one walk.
|
||||
bool RefreshFlatBuffer(std::vector<uint32_t>& flat);
|
||||
|
||||
private:
|
||||
SrtWalker(SrtWalker& parent, Value active_mask, bool clean = false,
|
||||
SrtWalker* clean_evaluator = nullptr);
|
||||
ResourcePlan::EvaluationContext& AcquireContext();
|
||||
void ResetUnknowns();
|
||||
static float Float32(uint64_t bits);
|
||||
bool EvaluateWide(Value value, uint64_t& result);
|
||||
bool Arg(const Inst& inst, size_t index, uint64_t& result);
|
||||
bool EvaluatePhi(const Inst& inst, uint64_t& result);
|
||||
bool EvaluateExtract(const Inst& inst, uint64_t& result);
|
||||
bool EvaluateRawRead(const Inst& inst, uint64_t& result);
|
||||
bool EvaluateInst(const Inst& inst, uint64_t& result);
|
||||
|
||||
const ResourcePlan& m_program;
|
||||
SrtRuntime m_runtime;
|
||||
SrtWalker* m_clean_evaluator = nullptr;
|
||||
Value m_active_mask;
|
||||
const std::vector<uint8_t>* m_reachable_blocks = nullptr;
|
||||
const SrtWalker* m_session;
|
||||
uint8_t m_read_policy;
|
||||
ResourcePlan::EvaluationContext& m_context;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SRTWALKER_H_ */
|
||||
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user