mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 00:33:06 +00:00
Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f90a8b1589 | ||
|
|
17a0f53d39 | ||
|
|
943bc19f46 | ||
|
|
5429888d4c | ||
|
|
025d1f7edf | ||
|
|
3c62269751 | ||
|
|
ceb154d9e4 | ||
|
|
26ca609236 | ||
|
|
4ebae87a1b | ||
|
|
b4209dde1b | ||
|
|
72d7da7266 | ||
|
|
ce5e57aa36 | ||
|
|
6357740fa1 | ||
|
|
be1c84f4d7 | ||
|
|
560756f34c | ||
|
|
52c80be266 | ||
|
|
22a9392d23 | ||
|
|
95bbc9c65d | ||
|
|
c5f7644751 | ||
|
|
4db225008b | ||
|
|
dc3bd68dee | ||
|
|
c1dfb309a1 | ||
|
|
ba7f9969ef | ||
|
|
7524e8f79e | ||
|
|
153dadd0b3 | ||
|
|
463a096249 | ||
|
|
597137e8b1 | ||
|
|
b2c9ec7609 | ||
|
|
99044c7c49 | ||
|
|
83f8bfd3c9 | ||
|
|
dd5bc26c64 | ||
|
|
72903bfce6 | ||
|
|
59d8256b36 | ||
|
|
9a20c6f738 | ||
|
|
67c89e0915 | ||
|
|
5d8381e0f3 | ||
|
|
e864cb20bd | ||
|
|
40aaab482e | ||
|
|
ec8b75b244 | ||
|
|
9210264d47 | ||
|
|
66e21a6245 | ||
|
|
87e49282b8 | ||
|
|
1816004357 | ||
|
|
bfc377627a | ||
|
|
4b651f2ba7 | ||
|
|
8c01d8e849 | ||
|
|
e0c5ae090c | ||
|
|
906d25568f | ||
|
|
8291cfeb04 | ||
|
|
dbc7b8b2ff | ||
|
|
269e411ef2 | ||
|
|
170c107779 | ||
|
|
8dc0d56b7e | ||
|
|
d7856753e0 | ||
|
|
bdf48dcf3b | ||
|
|
f356fa3730 | ||
|
|
07f6420273 | ||
|
|
6aaa533296 | ||
|
|
dc40478e7b | ||
|
|
56a82bcd41 | ||
|
|
bf5dd62eea | ||
|
|
227f12ee4b | ||
|
|
246d511568 | ||
|
|
ba0e93daac | ||
|
|
77a8f30758 | ||
|
|
1ffaaa96b1 | ||
|
|
706aa030ad | ||
|
|
ecc30a1f2d | ||
|
|
3688412dca | ||
|
|
45f53236e8 | ||
|
|
9dcff95e80 | ||
|
|
719c44310a | ||
|
|
7bb618d41a | ||
|
|
87e815b250 | ||
|
|
f9e14084d6 | ||
|
|
38c884e8c1 | ||
|
|
27dc7c281f | ||
|
|
79837b423d | ||
|
|
aafe014d0c | ||
|
|
a358b847c1 | ||
|
|
a951a91703 | ||
|
|
a875d1b205 | ||
|
|
f5f4e7d48a | ||
|
|
86fed10b81 | ||
|
|
949de19bb0 | ||
|
|
c513a4a13a | ||
|
|
b54057942d | ||
|
|
b6495a5b67 | ||
|
|
5b315a9ea7 | ||
|
|
5f5d6effa4 |
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
|
||||
|
||||
+34
-1
@@ -265,6 +265,7 @@ set(gpu_tiler_shader_names
|
||||
promote_d16
|
||||
demote_d16
|
||||
color_transform
|
||||
color_clear
|
||||
)
|
||||
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
|
||||
foreach(shader_name IN LISTS gpu_tiler_shader_names)
|
||||
@@ -336,7 +337,7 @@ if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak opus OpenXR::openxr_loader)
|
||||
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL3::SDL3-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak opus OpenXR::openxr_loader libzstd_static)
|
||||
|
||||
# Linux system libraries.
|
||||
if(LINUX)
|
||||
@@ -450,6 +451,11 @@ add_executable(lru_cache_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_include_directories(lru_cache_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(slot_vector_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SlotVectorTests.cpp"
|
||||
)
|
||||
target_include_directories(slot_vector_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
|
||||
@@ -540,6 +546,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})
|
||||
@@ -651,6 +670,9 @@ if(BUILD_TESTING)
|
||||
add_test(NAME host_input COMMAND $<TARGET_FILE:host_input_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||
add_test(NAME jpeg_dec COMMAND $<TARGET_FILE:jpeg_dec_tests>)
|
||||
add_test(NAME jpeg_enc COMMAND $<TARGET_FILE:jpeg_enc_tests>)
|
||||
add_test(NAME occlusion_queries COMMAND $<TARGET_FILE:occlusion_queries_tests>)
|
||||
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||
@@ -675,6 +697,14 @@ if(BUILD_TESTING)
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
|
||||
add_test(NAME gpu_command_lane
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
|
||||
add_test(NAME wait_until_safe_for_rendering
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --wait-until-safe-for-rendering-only)
|
||||
add_test(NAME pm4_flip
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --flip-only)
|
||||
add_test(NAME depth_bits
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-bits-only)
|
||||
add_test(NAME msaa_storage
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --msaa-storage-only)
|
||||
add_test(NAME pm4_context_state
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --context-state-only)
|
||||
add_test(NAME gpu_tiler
|
||||
@@ -735,6 +765,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
|
||||
|
||||
@@ -46,6 +46,16 @@ or graphical glitches, so please include the version you tested when reporting a
|
||||
## Screenshots
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Cyberpunk 2077</strong><br>
|
||||
<img src="docs/screenshots/ps5-07.png" width="300" alt="Cyberpunk 2077 running in KytyPS5">
|
||||
</td>
|
||||
<td align="center">
|
||||
<strong>MLB The Show 23</strong><br>
|
||||
<img src="docs/screenshots/ps5-08.png" width="300" alt="MLB The Show 23 running in KytyPS5">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<strong>Astro Bot</strong><br>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -109,4 +109,23 @@ std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::optional<AddContentEntry> ParseAddContent(std::string_view value) {
|
||||
static const std::regex label_pattern("^[A-Z0-9]{16}$");
|
||||
const auto eq = value.find('=');
|
||||
const auto label = value.substr(0, eq);
|
||||
if (!std::regex_match(label.begin(), label.end(), label_pattern)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AddContentEntry entry {{}, std::string(label), {}, {}};
|
||||
if (eq != std::string_view::npos) {
|
||||
entry.mount_dir = PathFromUtf8(FixFilenameSlash(value.substr(eq + 1)));
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::is_directory(entry.mount_dir, ec)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -2,7 +2,9 @@
|
||||
#define KYTY_COMMON_ADDITIONAL_CONTENT_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
@@ -10,7 +12,7 @@ namespace Common {
|
||||
struct AddContentEntry {
|
||||
std::string content_id;
|
||||
std::string entitlement_label;
|
||||
std::filesystem::path mount_dir;
|
||||
std::filesystem::path mount_dir; // Empty for an add-on without extra data.
|
||||
std::string title;
|
||||
};
|
||||
|
||||
@@ -18,6 +20,9 @@ struct AddContentEntry {
|
||||
std::vector<AddContentEntry> FindAddContent(const std::filesystem::path& dir,
|
||||
const std::string& title_id);
|
||||
|
||||
// Parses a user add-on: "LABEL" without extra data, or "LABEL=DIR" with its data in DIR.
|
||||
std::optional<AddContentEntry> ParseAddContent(std::string_view value);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_ADDITIONAL_CONTENT_H_ */
|
||||
@@ -29,6 +29,9 @@ enum class Counter {
|
||||
TextureUploadBytes,
|
||||
TextureDownloadBytes,
|
||||
TextureEvictions,
|
||||
ColorMetadataReadbacks, // CPU blocked reading back GPU-written DCC/CMASK metadata.
|
||||
ColorMetadataReadbackNs,
|
||||
ColorMetadataChecks, // GPU clear-key checks of DCC/CMASK metadata.
|
||||
BufferUploadBytes,
|
||||
BufferDownloadBytes,
|
||||
BufferEvictions,
|
||||
|
||||
@@ -133,6 +133,10 @@ bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
|
||||
bool ShaderCacheEnabled() {
|
||||
return g_config->shader_cache_enabled;
|
||||
}
|
||||
|
||||
ShaderOptimizationType GetShaderOptimizationType() {
|
||||
return g_config->shader_optimization_type;
|
||||
}
|
||||
@@ -197,6 +201,10 @@ bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
bool OcclusionQueriesEnabled() {
|
||||
return g_config->occlusion_queries_enabled;
|
||||
}
|
||||
|
||||
bool TrophyEnabled() {
|
||||
return g_config->trophy_enabled;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ struct ConfigOptions {
|
||||
uint32_t console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
bool shader_validation_enabled = false;
|
||||
bool shader_cache_enabled = false;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||
LogDirection shader_log_direction = LogDirection::Silent;
|
||||
std::filesystem::path shader_log_folder = "_Shaders";
|
||||
@@ -88,6 +89,7 @@ struct ConfigOptions {
|
||||
bool readback_tiled_images = false;
|
||||
bool sync_raw_image_buffers = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool occlusion_queries_enabled = false;
|
||||
bool trophy_enabled = true;
|
||||
uint32_t trophy_duration_seconds = 6;
|
||||
bool perf_overlay_enabled = false;
|
||||
@@ -131,6 +133,7 @@ uint32_t GetConsoleLanguage();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
bool ShaderValidationEnabled();
|
||||
bool ShaderCacheEnabled();
|
||||
ShaderOptimizationType GetShaderOptimizationType();
|
||||
LogDirection GetShaderLogDirection();
|
||||
std::filesystem::path GetShaderLogFolder();
|
||||
@@ -154,6 +157,7 @@ bool ReadbackLinearImagesEnabled();
|
||||
bool ReadbackTiledImagesEnabled();
|
||||
bool SyncRawImageBuffersEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool OcclusionQueriesEnabled();
|
||||
bool TrophyEnabled();
|
||||
uint32_t GetTrophyDurationSeconds();
|
||||
bool PlayGoHackEnabled();
|
||||
|
||||
+52
-52
@@ -1,15 +1,15 @@
|
||||
#ifndef EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
#define EMULATOR_SRC_COMMON_SLOTVECTOR_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -19,105 +19,105 @@ struct SlotId {
|
||||
static constexpr uint32_t INVALID_INDEX = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
constexpr SlotId() noexcept = default;
|
||||
constexpr SlotId(uint32_t value) noexcept: index(value), generation(1) {}
|
||||
constexpr SlotId(uint32_t value, uint32_t slot_generation) noexcept
|
||||
: index(value), generation(slot_generation) {}
|
||||
constexpr SlotId(uint32_t index) noexcept: index(index) {}
|
||||
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept { return index != INVALID_INDEX; }
|
||||
[[nodiscard]] constexpr explicit operator bool() const noexcept {
|
||||
return index != INVALID_INDEX;
|
||||
}
|
||||
constexpr auto operator<=>(const SlotId&) const noexcept = default;
|
||||
|
||||
uint32_t index = INVALID_INDEX;
|
||||
uint32_t generation = 0;
|
||||
};
|
||||
|
||||
// Stable-address slot storage for cache resources that are intentionally non-movable.
|
||||
// Fixed-size chunks keep the stored, non-movable resources in place. Occupancy is a byte per
|
||||
// slot, not a bitset: a deferred erase runs while fault threads read other slots.
|
||||
template <typename T>
|
||||
class SlotVector {
|
||||
static constexpr uint32_t ChunkSize = 256;
|
||||
|
||||
public:
|
||||
SlotVector() = default;
|
||||
KYTY_CLASS_NO_COPY(SlotVector);
|
||||
|
||||
~SlotVector() noexcept {
|
||||
ForEach([](SlotId, T& value) { value.~T(); });
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator[](SlotId id) noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
return *m_values[id.index].value;
|
||||
return m_chunks[id.index / ChunkSize][id.index % ChunkSize].object;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
return *m_values[id.index].value;
|
||||
return m_chunks[id.index / ChunkSize][id.index % ChunkSize].object;
|
||||
}
|
||||
|
||||
[[nodiscard]] T* try_get(SlotId id) noexcept {
|
||||
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
|
||||
return is_allocated(id) ? &(*this)[id] : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* try_get(SlotId id) const noexcept {
|
||||
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
|
||||
return is_allocated(id) ? &(*this)[id] : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_allocated(SlotId id) const noexcept {
|
||||
return id && id.index < m_values.size() &&
|
||||
m_values[id.index].generation == id.generation && m_values[id.index].value.has_value();
|
||||
return id.index < m_stored.size() && m_stored[id.index] != 0;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
[[nodiscard]] SlotId insert(Args&&... args) {
|
||||
uint32_t index = 0;
|
||||
if (m_free_list.empty()) {
|
||||
index = static_cast<uint32_t>(m_values.size());
|
||||
m_values.emplace_back();
|
||||
m_values.back().value.emplace(std::forward<Args>(args)...);
|
||||
} else {
|
||||
index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
EXIT_IF(m_values[index].value.has_value());
|
||||
m_values[index].value.emplace(std::forward<Args>(args)...);
|
||||
Reserve();
|
||||
}
|
||||
++m_size;
|
||||
return SlotId {index, m_values[index].generation};
|
||||
const uint32_t index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
new (&(*this)[index]) T(std::forward<Args>(args)...);
|
||||
m_stored[index] = 1;
|
||||
return SlotId {index};
|
||||
}
|
||||
|
||||
void erase(SlotId id) noexcept {
|
||||
EXIT_IF(!is_allocated(id));
|
||||
auto& slot = m_values[id.index];
|
||||
slot.value.reset();
|
||||
if (++slot.generation == 0) {
|
||||
slot.generation = 1;
|
||||
}
|
||||
(*this)[id].~T();
|
||||
m_stored[id.index] = 0;
|
||||
m_free_list.push_back(id.index);
|
||||
--m_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t size() const noexcept { return m_size; }
|
||||
[[nodiscard]] size_t capacity() const noexcept { return m_values.size(); }
|
||||
|
||||
template <typename F>
|
||||
void ForEach(F&& fn) {
|
||||
for (uint32_t index = 0; index < m_values.size(); ++index) {
|
||||
if (m_values[index].value) {
|
||||
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
|
||||
for (uint32_t index = 0; index < m_stored.size(); ++index) {
|
||||
if (m_stored[index] != 0) {
|
||||
fn(SlotId {index}, (*this)[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void ForEach(F&& fn) const {
|
||||
for (uint32_t index = 0; index < m_values.size(); ++index) {
|
||||
if (m_values[index].value) {
|
||||
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
|
||||
for (uint32_t index = 0; index < m_stored.size(); ++index) {
|
||||
if (m_stored[index] != 0) {
|
||||
fn(SlotId {index}, (*this)[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
std::optional<T> value;
|
||||
uint32_t generation = 1;
|
||||
union Slot {
|
||||
Slot() noexcept {}
|
||||
~Slot() noexcept {}
|
||||
|
||||
T object;
|
||||
};
|
||||
|
||||
std::deque<Slot> m_values;
|
||||
std::vector<uint32_t> m_free_list;
|
||||
size_t m_size = 0;
|
||||
void Reserve() {
|
||||
const auto first = static_cast<uint32_t>(m_stored.size());
|
||||
m_chunks.push_back(std::make_unique<Slot[]>(ChunkSize));
|
||||
m_stored.resize(first + ChunkSize);
|
||||
m_free_list.resize(ChunkSize);
|
||||
std::iota(m_free_list.rbegin(), m_free_list.rend(), first);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Slot[]>> m_chunks;
|
||||
std::vector<uint8_t> m_stored;
|
||||
std::vector<uint32_t> m_free_list;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -125,7 +125,7 @@ private:
|
||||
template <>
|
||||
struct std::hash<Common::SlotId> {
|
||||
[[nodiscard]] size_t operator()(Common::SlotId id) const noexcept {
|
||||
return std::hash<uint64_t> {}((static_cast<uint64_t>(id.generation) << 32u) | id.index);
|
||||
return std::hash<uint32_t> {}(id.index);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -188,7 +188,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,40 +84,17 @@ 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);
|
||||
@@ -140,45 +111,34 @@ public:
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, std::span<const uint32_t> commands);
|
||||
void ProcessIndirectBuffer(std::span<const uint32_t> commands, bool chain);
|
||||
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
[[nodiscard]] bool IsAsyncComputeQueue() const { return m_interrupt_event_id >= 0x20; }
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
|
||||
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution);
|
||||
void SuspendPm4();
|
||||
uint32_t NumInstances();
|
||||
const void* IndexAddress(uint32_t first_index) const;
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::Context m_saved_ctx;
|
||||
bool m_context_state_pushed = false;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
HW::UserSgprType m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
uint32_t m_index_type_and_size = 0;
|
||||
uint32_t m_index_buffer_size = 0;
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
// Persistent draw state: indirect draws update it for subsequent draws.
|
||||
uint32_t m_num_instances = 1;
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::Context m_saved_ctx;
|
||||
bool m_context_state_pushed = false;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
uint32_t m_index_type_and_size = 0;
|
||||
uint32_t m_index_buffer_size = 0;
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
// Persistent draw state. Indirect draws leave the address of their last instance count,
|
||||
// read only when a later draw inherits it.
|
||||
uint32_t m_num_instances = 1;
|
||||
uint64_t m_num_instances_addr = 0;
|
||||
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = false;
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
FlipInfo m_flip;
|
||||
const int m_interrupt_event_id;
|
||||
uint64_t m_submit_id = 0;
|
||||
uint64_t m_synthetic_occlusion_counter = 0;
|
||||
|
||||
@@ -199,10 +199,9 @@ 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_REWIND] = CpOpRewind;
|
||||
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
|
||||
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
|
||||
@@ -211,12 +210,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 +224,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,15 @@ 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 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 +150,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,108 +2048,14 @@ 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;
|
||||
}
|
||||
@@ -2524,42 +2276,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 +3235,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 +3246,15 @@ void GraphicsInitJmpTablesShIndirect() {
|
||||
for (uint32_t slot = 0; slot < 32; slot++) {
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_PS_0 + slot] =
|
||||
[](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::SPI_SHADER_USER_DATA_PS_0;
|
||||
cp.GetShCtx().SetPsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetPsUserSgpr(cmd_offset - Pm4::SPI_SHADER_USER_DATA_PS_0, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_GS_0 + slot] =
|
||||
[](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::SPI_SHADER_USER_DATA_GS_0;
|
||||
cp.GetShCtx().SetGsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetGsUserSgpr(cmd_offset - Pm4::SPI_SHADER_USER_DATA_GS_0, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_HS_0 + slot] =
|
||||
[](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
auto sgpr = cmd_offset - Pm4::SPI_SHADER_USER_DATA_HS_0;
|
||||
cp.GetShCtx().SetHsUserSgpr(sgpr, value, cp.GetUserDataMarker());
|
||||
cp.SetUserDataMarker(HW::UserSgprType::Unknown);
|
||||
cp.GetShCtx().SetHsUserSgpr(cmd_offset - Pm4::SPI_SHADER_USER_DATA_HS_0, value);
|
||||
};
|
||||
}
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -39,21 +40,6 @@ static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||
|
||||
struct DrawIndirectArgs {
|
||||
uint32_t vertex_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
struct DrawIndexedIndirectArgs {
|
||||
uint32_t index_count_per_instance;
|
||||
uint32_t instance_count;
|
||||
uint32_t start_index_location;
|
||||
uint32_t base_vertex_location;
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
static bool GraphicsRunDebugDumpEnabled() {
|
||||
return Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
||||
@@ -106,6 +92,12 @@ void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::Wake() {
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
m_wake_pending = true;
|
||||
m_work_available.Signal();
|
||||
}
|
||||
|
||||
void GuestGpu::ProcessCommands() {
|
||||
EXIT_IF(!IsGpuThread());
|
||||
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
|
||||
@@ -135,16 +127,14 @@ void GuestGpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
|
||||
done.acquire();
|
||||
}
|
||||
|
||||
void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands) {
|
||||
if (draw_commands.empty()) {
|
||||
void GuestGpu::Submit(std::span<const uint32_t> commands) {
|
||||
if (commands.empty()) {
|
||||
return;
|
||||
}
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = draw_commands;
|
||||
submission.constant_commands = constant_commands;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = commands;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
@@ -206,12 +196,10 @@ void CommandProcessor::Reset() {
|
||||
m_index_buffer_size = 0;
|
||||
m_index_base_addr = 0;
|
||||
m_num_instances = 1;
|
||||
m_num_instances_addr = 0;
|
||||
m_predicate_skip = false;
|
||||
m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
m_draw_indirect_args_base_addr = 0;
|
||||
m_dispatch_indirect_args_base_addr = 0;
|
||||
|
||||
std::memset(m_const_ram, 0, sizeof(m_const_ram));
|
||||
}
|
||||
|
||||
void CommandProcessor::ApplyContextStateOperation(ContextStateOperation operation) {
|
||||
@@ -244,6 +232,8 @@ void CommandProcessor::BufferInit() {
|
||||
|
||||
void CommandProcessor::BufferFlush() {
|
||||
GetScheduler().Flush();
|
||||
// The occlusion counters reach a guest that polls them without waiting for a later fence.
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferWait() {
|
||||
@@ -251,47 +241,12 @@ void CommandProcessor::BufferWait() {
|
||||
GetScheduler().Finish();
|
||||
}
|
||||
|
||||
void CommandProcessor::ResetDeCe() {
|
||||
m_de_count = 0;
|
||||
m_ce_count = 0;
|
||||
m_ce_complete = false;
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitCe() {
|
||||
if (m_ce_count <= m_de_count && !m_ce_complete) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitDeDiff(uint32_t diff) {
|
||||
EXIT_IF(m_de_count > m_ce_count);
|
||||
if (m_ce_count - m_de_count >= diff) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitForRewind(bool valid) {
|
||||
if (!valid) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::IncrementDe() {
|
||||
m_de_count++;
|
||||
}
|
||||
|
||||
void CommandProcessor::IncrementCe() {
|
||||
m_ce_count++;
|
||||
}
|
||||
|
||||
void CommandProcessor::WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num) {
|
||||
memcpy(m_const_ram + offset / 4, src, static_cast<size_t>(dw_num) * 4);
|
||||
}
|
||||
|
||||
void CommandProcessor::DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num) {
|
||||
memcpy(dst, m_const_ram + offset / 4, static_cast<size_t>(dw_num) * 4);
|
||||
}
|
||||
|
||||
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func) {
|
||||
switch (func) {
|
||||
case 0: return true;
|
||||
@@ -478,7 +433,10 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
}
|
||||
if (selected_queue < 0) {
|
||||
gpu->m_processing = false;
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
// A wake that came before this wait must not be lost.
|
||||
if (!std::exchange(gpu->m_wake_pending, false)) {
|
||||
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
|
||||
}
|
||||
for (auto& queue: gpu->m_queues) {
|
||||
if (!queue.empty()) {
|
||||
queue.front().blocked = false;
|
||||
@@ -543,64 +501,34 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
if (first_slice) {
|
||||
submission.started = true;
|
||||
cp.SetSubmitId(++m_submit_id);
|
||||
cp.ResetDeCe();
|
||||
cp.SetFlip({});
|
||||
}
|
||||
|
||||
cp.BufferInit();
|
||||
bool complete = true;
|
||||
|
||||
switch (submission.type) {
|
||||
case SubmissionType::Graphics: {
|
||||
bool progressed = false;
|
||||
submission.constant_complete |= submission.constant_commands.empty();
|
||||
for (;;) {
|
||||
bool round_progress = false;
|
||||
if (!submission.constant_complete) {
|
||||
submission.constant_complete =
|
||||
cp.Process(submission.constant_execution, submission.constant_commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
round_progress |= submission.constant_execution.MadeProgress();
|
||||
}
|
||||
cp.SetCeComplete(submission.constant_complete);
|
||||
if (!submission.command_complete) {
|
||||
submission.command_complete =
|
||||
cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
round_progress |= submission.command_execution.MadeProgress();
|
||||
}
|
||||
progressed |= round_progress;
|
||||
complete = submission.command_complete && submission.constant_complete;
|
||||
if (complete || !round_progress) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (progressed) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::Compute: {
|
||||
const auto num_dw = static_cast<uint32_t>(submission.commands.size());
|
||||
const auto* buffer = submission.commands.data();
|
||||
static uint32_t compute_batch_log_count = 0;
|
||||
if (first_slice && num_dw <= 128 && compute_batch_log_count++ < 32) {
|
||||
LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(buffer), num_dw);
|
||||
for (uint32_t i = 0; i < std::min<uint32_t>(num_dw, 16); i++) {
|
||||
LOGF("\t compute[%02" PRIu32 "] = 0x%08" PRIx32 "\n", i, buffer[i]);
|
||||
}
|
||||
}
|
||||
case SubmissionType::Compute:
|
||||
if (first_slice) {
|
||||
const auto num_dw = static_cast<uint32_t>(submission.commands.size());
|
||||
const auto* buffer = submission.commands.data();
|
||||
static uint32_t compute_batch_log_count = 0;
|
||||
if (num_dw <= 128 && compute_batch_log_count++ < 32) {
|
||||
LOGF("compute direct batch: data=0x%016" PRIx64 ", num_dw=%" PRIu32 "\n",
|
||||
reinterpret_cast<uint64_t>(buffer), num_dw);
|
||||
for (uint32_t i = 0; i < std::min<uint32_t>(num_dw, 16); i++) {
|
||||
LOGF("\t compute[%02" PRIu32 "] = 0x%08" PRIx32 "\n", i, buffer[i]);
|
||||
}
|
||||
}
|
||||
GraphicsDbgDumpDcb("cc", num_dw, buffer);
|
||||
}
|
||||
complete = cp.Process(submission.command_execution, submission.commands) ==
|
||||
Pm4ProcessResult::Complete;
|
||||
if (submission.command_execution.MadeProgress()) {
|
||||
[[fallthrough]];
|
||||
case SubmissionType::Graphics:
|
||||
complete =
|
||||
cp.Process(submission.execution, submission.commands) == Pm4ProcessResult::Complete;
|
||||
if (submission.execution.MadeProgress()) {
|
||||
cp.BufferFlush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SubmissionType::FlipPreparation:
|
||||
cp.PrepareCpuFlip(submission.flip_request_id);
|
||||
break;
|
||||
@@ -703,18 +631,6 @@ void CommandProcessor::ProcessPm4(Pm4Execution& execution) {
|
||||
opcode, KYTY_PM4_R(packet_header), packet_dw,
|
||||
reinterpret_cast<uint64_t>(packet), packet_header);
|
||||
}
|
||||
if (opcode == Pm4::IT_NOP && KYTY_PM4_R(packet_header) == Pm4::R_RELEASE_MEM &&
|
||||
packet_dw >= 7) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 128) {
|
||||
const auto dst = packet[3] | (static_cast<uint64_t>(packet[4]) << 32u);
|
||||
const auto val = packet[5] | (static_cast<uint64_t>(packet[6]) << 32u);
|
||||
LOGF("\t predicated skip: R_RELEASE_MEM dst=0x%016" PRIx64
|
||||
", value=0x%016" PRIx64 ", action=0x%08" PRIx32
|
||||
", gcr/data/int=0x%08" PRIx32 "\n",
|
||||
dst, val, packet[1], packet[2]);
|
||||
}
|
||||
}
|
||||
cursor.offset_dw += packet_dw;
|
||||
execution.m_made_progress = true;
|
||||
continue;
|
||||
@@ -784,7 +700,16 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
|
||||
num_instances = 1;
|
||||
}
|
||||
|
||||
m_num_instances = num_instances;
|
||||
m_num_instances = num_instances;
|
||||
m_num_instances_addr = 0;
|
||||
}
|
||||
|
||||
uint32_t CommandProcessor::NumInstances() {
|
||||
if (m_num_instances_addr != 0) {
|
||||
m_num_instances = *reinterpret_cast<const uint32_t*>(m_num_instances_addr);
|
||||
m_num_instances_addr = 0;
|
||||
}
|
||||
return m_num_instances;
|
||||
}
|
||||
|
||||
void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
|
||||
@@ -798,6 +723,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
return;
|
||||
case 0x01: {
|
||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
// One begin/end pair per DB; bit 63 marks each counter ready.
|
||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
|
||||
@@ -841,16 +767,12 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) {
|
||||
LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n",
|
||||
args.base_vertex, args.first_instance);
|
||||
args.instance_count = NumInstances();
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
const void* CommandProcessor::IndexAddress(uint32_t first_index) const {
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
@@ -858,16 +780,16 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
case 2: index_size = 1; break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
|
||||
}
|
||||
return reinterpret_cast<const void*>(m_index_base_addr + first_index * index_size);
|
||||
}
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
DrawIndex({.index_count = index_count, .index_addr = IndexAddress(index_offset)});
|
||||
}
|
||||
|
||||
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers,
|
||||
uint32_t vertex_offset, uint32_t instance_offset,
|
||||
uint32_t first_index) {
|
||||
uint32_t first_index, uint32_t draw_index) {
|
||||
const auto write = [&](uint32_t location, uint32_t value) {
|
||||
if (location == Pm4::SH_NOP) {
|
||||
return;
|
||||
@@ -879,11 +801,13 @@ 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);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
const auto args_size =
|
||||
indexed ? sizeof(vk::DrawIndexedIndirectCommand) : sizeof(vk::DrawIndirectCommand);
|
||||
DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed);
|
||||
}
|
||||
|
||||
@@ -893,77 +817,66 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
uint32_t draw_initiator, bool indexed) {
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u));
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
// The guest records match Vulkan's indirect commands byte for byte.
|
||||
EXIT_NOT_IMPLEMENTED((stride_in_bytes & 3u) != 0 ||
|
||||
stride_in_bytes < (indexed ? sizeof(vk::DrawIndexedIndirectCommand)
|
||||
: sizeof(vk::DrawIndirectCommand)));
|
||||
|
||||
uint32_t draw_count = max_count_or_count;
|
||||
if (count_addr != nullptr) {
|
||||
draw_count = *count_addr;
|
||||
if (draw_count > max_count_or_count) {
|
||||
draw_count = max_count_or_count;
|
||||
}
|
||||
}
|
||||
|
||||
const auto draw_count = count_addr == nullptr
|
||||
? max_count_or_count
|
||||
: std::min(uint32_t {*count_addr}, max_count_or_count);
|
||||
if (draw_count == 0) {
|
||||
return;
|
||||
}
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::IndirectDraws, draw_count);
|
||||
|
||||
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs);
|
||||
EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size);
|
||||
|
||||
uint64_t index_size = 0;
|
||||
if (indexed) {
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
case 1: index_size = 4; break;
|
||||
case 2: index_size = 1; break;
|
||||
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
|
||||
}
|
||||
auto& executor = m_renderer.GetRenderExecutor();
|
||||
const auto args_addr = m_draw_indirect_args_base_addr + data_offset;
|
||||
// NUM_INSTANCES keeps the last draw's instance count.
|
||||
m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
|
||||
offsetof(vk::DrawIndirectCommand, instanceCount);
|
||||
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
|
||||
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
|
||||
// while they are zero.
|
||||
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
|
||||
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
|
||||
registers.index_offset == Pm4::SH_NOP && registers.draw_index == Pm4::SH_NOP &&
|
||||
(!indexed || m_index_buffer_size != 0) &&
|
||||
(indexed ? executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = m_index_buffer_size,
|
||||
.index_addr = IndexAddress(0),
|
||||
.index_type_and_size = m_index_type_and_size,
|
||||
.indirect = records})
|
||||
: executor.DrawAuto(m_submit_id, CurrentBuffer(), {.indirect = records}))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t max_indices = m_index_buffer_size != 0 ? m_index_buffer_size : UINT32_MAX;
|
||||
for (uint32_t i = 0; i < draw_count; i++) {
|
||||
const auto args_addr = m_draw_indirect_args_base_addr + data_offset +
|
||||
static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
|
||||
const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
|
||||
if (!indexed) {
|
||||
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location,
|
||||
args->start_instance_location, 0);
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance,
|
||||
.instance_count = args->instance_count,
|
||||
.first_vertex = args->start_vertex_location,
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0, i);
|
||||
executor.DrawAuto(m_submit_id, CurrentBuffer(),
|
||||
{.vertex_count = args.vertexCount,
|
||||
.instance_count = args.instanceCount,
|
||||
.first_vertex = args.firstVertex,
|
||||
.first_instance = args.firstInstance,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location,
|
||||
args->start_instance_location, args->start_index_location);
|
||||
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size);
|
||||
|
||||
const uint32_t index_count =
|
||||
(m_index_buffer_size != 0
|
||||
? std::min(args->index_count_per_instance, m_index_buffer_size)
|
||||
: args->index_count_per_instance);
|
||||
if (GraphicsRunDebugDumpEnabled() && index_count != args->index_count_per_instance) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
|
||||
LOGF("\t DrawIndexIndirectMulti: clamped index_count from %" PRIu32 " to %" PRIu32
|
||||
" using INDEX_BUFFER_SIZE\n",
|
||||
args->index_count_per_instance, index_count);
|
||||
}
|
||||
}
|
||||
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = args->instance_count,
|
||||
.base_vertex = static_cast<int32_t>(args->base_vertex_location),
|
||||
.first_instance = args->start_instance_location,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
|
||||
PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
|
||||
args.firstInstance, args.firstIndex, i);
|
||||
executor.DrawIndex(m_submit_id, CurrentBuffer(),
|
||||
{.index_count = std::min(args.indexCount, max_indices),
|
||||
.index_addr = IndexAddress(args.firstIndex),
|
||||
.instance_count = args.instanceCount,
|
||||
.index_type_and_size = m_index_type_and_size,
|
||||
.base_vertex = args.vertexOffset,
|
||||
.first_instance = args.firstInstance,
|
||||
.offset_source = DrawOffsetSource::IndirectArgs});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,221 +925,74 @@ void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
args.instance_count = NumInstances();
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
BufferFlush();
|
||||
|
||||
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index));
|
||||
// Stall only this command processor, not the host command lane or other queues.
|
||||
if (!m_renderer.GetVideoOut().IsSafeForRendering(static_cast<int>(video_out_handle),
|
||||
static_cast<int>(display_buffer_index))) {
|
||||
SuspendPm4();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action,
|
||||
uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id) {
|
||||
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
|
||||
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
void CommandProcessor::ReleaseMem(uint32_t data_sel, uint32_t interrupt_selector, void* address,
|
||||
uint64_t data, uint32_t interrupt_context_id) {
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
const auto bits = static_cast<unsigned>(sizeof(T) * 8u);
|
||||
const auto log_width = static_cast<int>(sizeof(T) * 2u);
|
||||
|
||||
LOGF("CommandProcessor::WriteAtEndOfPipe%u()\n"
|
||||
"\t cache_policy = 0x%08" PRIx32 "\n"
|
||||
"\t event_write_dest = 0x%08" PRIx32 "\n"
|
||||
"\t eop_event_type = 0x%08" PRIx32 "\n"
|
||||
"\t cache_action = 0x%08" PRIx32 "\n"
|
||||
"\t event_index = 0x%08" PRIx32 "\n"
|
||||
"\t event_write_source = 0x%08" PRIx32 "\n"
|
||||
"\t interrupt_selector = 0x%08" PRIx32 "\n"
|
||||
"\t interrupt_context = 0x%08" PRIx32 "\n"
|
||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||
"\t value = 0x%0*" PRIx64 "\n",
|
||||
bits, cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, interrupt_selector, interrupt_context_id,
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), log_width, static_cast<uint64_t>(value));
|
||||
LOGF("CommandProcessor::ReleaseMem()\n"
|
||||
"\t data_sel = %" PRIu32 "\n"
|
||||
"\t interrupt_selector = %" PRIu32 "\n"
|
||||
"\t address = 0x%016" PRIx64 "\n"
|
||||
"\t data = 0x%016" PRIx64 "\n"
|
||||
"\t interrupt_context = 0x%08" PRIx32 "\n",
|
||||
data_sel, interrupt_selector, reinterpret_cast<uint64_t>(address), data,
|
||||
interrupt_context_id);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cache_policy != 0x00000000);
|
||||
EXIT_NOT_IMPLEMENTED(event_write_dest != 0x00000000);
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
|
||||
// EndOfPipeData: kNone, kImmediate32b, kImmediate64b, kRefClk; 5 reads GDS.
|
||||
switch (data_sel) {
|
||||
case 0: break;
|
||||
case 1: {
|
||||
const auto value = static_cast<uint32_t>(data);
|
||||
std::memcpy(address, &value, sizeof(value));
|
||||
break;
|
||||
case 0x02: with_interrupt = true; break;
|
||||
default: EXIT("unknown interrupt_selector\n");
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (with_interrupt) {
|
||||
TriggerEopEventAtEndOfPipe(interrupt_context_id);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
case 3: data = Sync::ReadReferenceClock(); [[fallthrough]];
|
||||
case 2: std::memcpy(address, &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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
default: EXIT("unknown release_mem data selector: %" PRIu32 "\n", data_sel);
|
||||
}
|
||||
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
|
||||
void CommandProcessor::WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action,
|
||||
uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, uint32_t value,
|
||||
uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id) {
|
||||
WriteAtEndOfPipe(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, dst_gpu_addr, value, interrupt_selector,
|
||||
interrupt_context_id);
|
||||
}
|
||||
|
||||
void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action,
|
||||
uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, uint64_t value,
|
||||
uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id) {
|
||||
WriteAtEndOfPipe(cache_policy, event_write_dest, eop_event_type, cache_action, event_index,
|
||||
event_write_source, dst_gpu_addr, value, interrupt_selector,
|
||||
interrupt_context_id);
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||
(void)CurrentBuffer().Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
||||
renderer.TriggerInterrupt(event_id, interrupt_context_id);
|
||||
});
|
||||
// GuestGpu::Process's end-of-slice flush submits the work this interrupt waits on.
|
||||
if (interrupt) {
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
||||
renderer.TriggerInterrupt(event_id, interrupt_context_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
@@ -1279,15 +1045,19 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
"\n",
|
||||
event_index, event_address);
|
||||
}
|
||||
if (auto& queries = m_renderer.GetOcclusionQueries(); queries.Enabled()) {
|
||||
queries.Dump(event_address);
|
||||
break;
|
||||
}
|
||||
static std::once_flag warning_once;
|
||||
std::call_once(warning_once, [] {
|
||||
std::printf("Warning: game uses occlusion queries, which are currently treated as "
|
||||
"always visible; GPU usage may be higher and FPS may be lower.\n");
|
||||
std::printf("Warning: game uses occlusion queries, which are treated as always "
|
||||
"visible without --occlusion-queries; GPU usage may be higher and FPS "
|
||||
"may be lower.\n");
|
||||
});
|
||||
|
||||
// Until host occlusion queries are implemented, publish an always-visible result. The
|
||||
// PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result
|
||||
// ready.
|
||||
// Without host occlusion queries, publish an always-visible result. The PS5 layout
|
||||
// contains one interleaved begin/end pair per DB, and bit 63 marks a result ready.
|
||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||
constexpr uint64_t counter_mask = ready_bit - 1u;
|
||||
auto* results = reinterpret_cast<volatile uint64_t*>(event_address);
|
||||
@@ -1303,69 +1073,20 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip() {
|
||||
void CommandProcessor::Flip(int handle, int index, int flip_mode, int64_t flip_arg) {
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n");
|
||||
}
|
||||
m_renderer.GetOcclusionQueries().Publish();
|
||||
|
||||
auto& command = CurrentBuffer();
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
auto request = Sync::PrepareVideoOutFlip(command, handle, index, flip_mode, flip_arg);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::Flip()\n"
|
||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||
"\t value = 0x%08" PRIx32 "\n",
|
||||
reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
|
||||
void* dst_gpu_addr, uint32_t value) {
|
||||
auto& command = CurrentBuffer();
|
||||
|
||||
if (GraphicsRunDebugDumpEnabled()) {
|
||||
LOGF("CommandProcessor::FlipWithInterrupt()\n"
|
||||
"\t eop_event_type = 0x%08" PRIx32 "\n"
|
||||
"\t cache_action = 0x%08" PRIx32 "\n"
|
||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||
"\t value = 0x%08" PRIx32 "\n",
|
||||
eop_event_type, cache_action, reinterpret_cast<uint64_t>(dst_gpu_addr), value);
|
||||
}
|
||||
|
||||
if (eop_event_type != 0x00000004 || cache_action != 0x00000038) {
|
||||
EXIT("unknown event type\n");
|
||||
}
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||
m_flip.flip_arg);
|
||||
(void)command.Handle();
|
||||
GetScheduler().DeferPriorityOperation(
|
||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, request] {
|
||||
renderer.GetVideoOut().CompleteFlip(request);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
auto& command = CurrentBuffer();
|
||||
if (g_current_processor != nullptr) {
|
||||
@@ -1383,10 +1104,6 @@ void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::SynchronizeGpu() {
|
||||
GetScheduler().Finish();
|
||||
}
|
||||
|
||||
bool GuestGpu::IsGpuThread() noexcept {
|
||||
return g_gpu_thread;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@ public:
|
||||
[[nodiscard]] bool IsStopping();
|
||||
void SendCommand(Common::UniqueFunction<void>&& command);
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
// Retries blocked submissions now instead of after the 100 ms stall timeout.
|
||||
void Wake();
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void Submit(std::span<const uint32_t> commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
// Insert an ordered graphics drain; only a previous suspend point can block the caller.
|
||||
@@ -58,14 +59,10 @@ private:
|
||||
SubmissionType type = SubmissionType::Graphics;
|
||||
uint32_t queue_id = 0;
|
||||
std::span<const uint32_t> commands;
|
||||
std::span<const uint32_t> constant_commands;
|
||||
Pm4Execution command_execution;
|
||||
Pm4Execution constant_execution;
|
||||
bool started = false;
|
||||
bool command_complete = false;
|
||||
bool constant_complete = false;
|
||||
bool blocked = false;
|
||||
uint64_t flip_request_id = 0;
|
||||
Pm4Execution execution;
|
||||
bool started = false;
|
||||
bool blocked = false;
|
||||
uint64_t flip_request_id = 0;
|
||||
};
|
||||
|
||||
void Enqueue(Submission submission);
|
||||
@@ -87,6 +84,7 @@ private:
|
||||
bool m_processing = false;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_wake_pending = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
|
||||
@@ -597,14 +597,10 @@ struct ShaderRegisters {
|
||||
}
|
||||
};
|
||||
|
||||
enum class UserSgprType { Unknown, Region, Vsharp };
|
||||
|
||||
struct UserSgprInfo {
|
||||
static constexpr int SGPRS_MAX = 32;
|
||||
|
||||
uint32_t value[SGPRS_MAX] = {0};
|
||||
UserSgprType type[SGPRS_MAX] = {};
|
||||
uint32_t count = 0;
|
||||
uint32_t value[SGPRS_MAX] = {0};
|
||||
};
|
||||
|
||||
struct VertexShaderInfo {
|
||||
@@ -1056,30 +1052,10 @@ public:
|
||||
void SetCsShader(const CsStageRegisters& cs_regs) { m_cs.cs_regs = cs_regs; }
|
||||
void SetCsWaveSize(uint8_t wave_size) { m_cs.cs_regs.wave_size = wave_size; }
|
||||
|
||||
void SetPsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_ps.ps_user_sgpr.value[id] = value;
|
||||
m_ps.ps_user_sgpr.type[id] = type;
|
||||
m_ps.ps_user_sgpr.count =
|
||||
((id + 1) > m_ps.ps_user_sgpr.count ? (id + 1) : m_ps.ps_user_sgpr.count);
|
||||
}
|
||||
void SetCsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_cs.cs_user_sgpr.value[id] = value;
|
||||
m_cs.cs_user_sgpr.type[id] = type;
|
||||
m_cs.cs_user_sgpr.count =
|
||||
((id + 1) > m_cs.cs_user_sgpr.count ? (id + 1) : m_cs.cs_user_sgpr.count);
|
||||
}
|
||||
void SetHsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_vs.hs_user_sgpr.value[id] = value;
|
||||
m_vs.hs_user_sgpr.type[id] = type;
|
||||
m_vs.hs_user_sgpr.count =
|
||||
((id + 1) > m_vs.hs_user_sgpr.count ? (id + 1) : m_vs.hs_user_sgpr.count);
|
||||
}
|
||||
void SetGsUserSgpr(uint32_t id, uint32_t value, UserSgprType type) {
|
||||
m_vs.gs_user_sgpr.value[id] = value;
|
||||
m_vs.gs_user_sgpr.type[id] = type;
|
||||
m_vs.gs_user_sgpr.count =
|
||||
((id + 1) > m_vs.gs_user_sgpr.count ? (id + 1) : m_vs.gs_user_sgpr.count);
|
||||
}
|
||||
void SetPsUserSgpr(uint32_t id, uint32_t value) { m_ps.ps_user_sgpr.value[id] = value; }
|
||||
void SetCsUserSgpr(uint32_t id, uint32_t value) { m_cs.cs_user_sgpr.value[id] = value; }
|
||||
void SetHsUserSgpr(uint32_t id, uint32_t value) { m_vs.hs_user_sgpr.value[id] = value; }
|
||||
void SetGsUserSgpr(uint32_t id, uint32_t value) { m_vs.gs_user_sgpr.value[id] = value; }
|
||||
|
||||
[[nodiscard]] const PixelShaderInfo& GetPs() const { return m_ps; }
|
||||
[[nodiscard]] const VertexShaderInfo& GetVs() const { return m_vs; }
|
||||
|
||||
@@ -25,7 +25,6 @@ constexpr auto MakeRegisterNames() {
|
||||
names[R_POP_MARKER] = "R_POP_MARKER";
|
||||
names[R_WRITE_DATA] = "R_WRITE_DATA";
|
||||
names[R_FLIP] = "R_FLIP";
|
||||
names[R_RELEASE_MEM] = "R_RELEASE_MEM";
|
||||
names[R_DMA_DATA] = "R_DMA_DATA";
|
||||
names[R_CONTEXT_STATE] = "R_CONTEXT_STATE";
|
||||
return names;
|
||||
@@ -54,7 +53,6 @@ constexpr auto MakeOpcodeNames() {
|
||||
names[IT_DRAW_INDIRECT_MULTI] = "IT_DRAW_INDIRECT_MULTI";
|
||||
names[IT_DRAW_INDEX_AUTO] = "IT_DRAW_INDEX_AUTO";
|
||||
names[IT_NUM_INSTANCES] = "IT_NUM_INSTANCES";
|
||||
names[IT_INDIRECT_BUFFER_CNST] = "IT_INDIRECT_BUFFER_CNST";
|
||||
names[IT_DRAW_INDEX_OFFSET_2] = "IT_DRAW_INDEX_OFFSET_2";
|
||||
names[IT_WRITE_DATA] = "IT_WRITE_DATA";
|
||||
names[IT_MEM_SEMAPHORE] = "IT_MEM_SEMAPHORE";
|
||||
@@ -66,8 +64,6 @@ constexpr auto MakeOpcodeNames() {
|
||||
names[IT_PFP_SYNC_ME] = "IT_PFP_SYNC_ME";
|
||||
names[IT_SURFACE_SYNC] = "IT_SURFACE_SYNC";
|
||||
names[IT_EVENT_WRITE] = "IT_EVENT_WRITE";
|
||||
names[IT_EVENT_WRITE_EOP] = "IT_EVENT_WRITE_EOP";
|
||||
names[IT_EVENT_WRITE_EOS] = "IT_EVENT_WRITE_EOS";
|
||||
names[IT_RELEASE_MEM] = "IT_RELEASE_MEM";
|
||||
names[IT_DMA_DATA] = "IT_DMA_DATA";
|
||||
names[IT_ACQUIRE_MEM] = "IT_ACQUIRE_MEM";
|
||||
@@ -80,12 +76,6 @@ constexpr auto MakeOpcodeNames() {
|
||||
names[IT_SET_QUEUE_REG] = "IT_SET_QUEUE_REG";
|
||||
names[IT_SET_UCONFIG_REG] = "IT_SET_UCONFIG_REG";
|
||||
names[IT_SET_UCONFIG_REG_INDEX] = "IT_SET_UCONFIG_REG_INDEX";
|
||||
names[IT_WRITE_CONST_RAM] = "IT_WRITE_CONST_RAM";
|
||||
names[IT_DUMP_CONST_RAM] = "IT_DUMP_CONST_RAM";
|
||||
names[IT_INCREMENT_CE_COUNTER] = "IT_INCREMENT_CE_COUNTER";
|
||||
names[IT_INCREMENT_DE_COUNTER] = "IT_INCREMENT_DE_COUNTER";
|
||||
names[IT_WAIT_ON_CE_COUNTER] = "IT_WAIT_ON_CE_COUNTER";
|
||||
names[IT_WAIT_ON_DE_COUNTER_DIFF] = "IT_WAIT_ON_DE_COUNTER_DIFF";
|
||||
names[IT_DISPATCH_DRAW_PREAMBLE] = "IT_DISPATCH_DRAW_PREAMBLE";
|
||||
names[IT_DISPATCH_DRAW] = "IT_DISPATCH_DRAW";
|
||||
names[IT_GET_LOD_STATS] = "IT_GET_LOD_STATS";
|
||||
|
||||
@@ -40,7 +40,6 @@ constexpr uint32_t IT_INDEX_TYPE = 0x2A;
|
||||
constexpr uint32_t IT_DRAW_INDIRECT_MULTI = 0x2C;
|
||||
constexpr uint32_t IT_DRAW_INDEX_AUTO = 0x2D;
|
||||
constexpr uint32_t IT_NUM_INSTANCES = 0x2F;
|
||||
constexpr uint32_t IT_INDIRECT_BUFFER_CNST = 0x33;
|
||||
constexpr uint32_t IT_DRAW_INDEX_OFFSET_2 = 0x35;
|
||||
constexpr uint32_t IT_WRITE_DATA = 0x37;
|
||||
constexpr uint32_t IT_MEM_SEMAPHORE = 0x39;
|
||||
@@ -53,8 +52,6 @@ constexpr uint32_t IT_CP_DMA = 0x41;
|
||||
constexpr uint32_t IT_PFP_SYNC_ME = 0x42;
|
||||
constexpr uint32_t IT_SURFACE_SYNC = 0x43;
|
||||
constexpr uint32_t IT_EVENT_WRITE = 0x46;
|
||||
constexpr uint32_t IT_EVENT_WRITE_EOP = 0x47;
|
||||
constexpr uint32_t IT_EVENT_WRITE_EOS = 0x48;
|
||||
constexpr uint32_t IT_RELEASE_MEM = 0x49;
|
||||
constexpr uint32_t IT_DMA_DATA = 0x50;
|
||||
constexpr uint32_t IT_ACQUIRE_MEM = 0x58;
|
||||
@@ -67,12 +64,6 @@ constexpr uint32_t IT_SET_SH_REG = 0x76;
|
||||
constexpr uint32_t IT_SET_QUEUE_REG = 0x78;
|
||||
constexpr uint32_t IT_SET_UCONFIG_REG = 0x79;
|
||||
constexpr uint32_t IT_SET_UCONFIG_REG_INDEX = 0x7A;
|
||||
constexpr uint32_t IT_WRITE_CONST_RAM = 0x81;
|
||||
constexpr uint32_t IT_DUMP_CONST_RAM = 0x83;
|
||||
constexpr uint32_t IT_INCREMENT_CE_COUNTER = 0x84;
|
||||
constexpr uint32_t IT_INCREMENT_DE_COUNTER = 0x85;
|
||||
constexpr uint32_t IT_WAIT_ON_CE_COUNTER = 0x86;
|
||||
constexpr uint32_t IT_WAIT_ON_DE_COUNTER_DIFF = 0x88;
|
||||
constexpr uint32_t IT_DISPATCH_DRAW = 0x8D;
|
||||
constexpr uint32_t IT_GET_LOD_STATS = 0x8E;
|
||||
constexpr uint32_t IT_WAIT_REG_MEM_64 = 0x93;
|
||||
@@ -88,7 +79,6 @@ constexpr uint32_t R_PUSH_MARKER = 0x0B;
|
||||
constexpr uint32_t R_POP_MARKER = 0x0C;
|
||||
constexpr uint32_t R_WRITE_DATA = 0x15;
|
||||
constexpr uint32_t R_FLIP = 0x17;
|
||||
constexpr uint32_t R_RELEASE_MEM = 0x18;
|
||||
constexpr uint32_t R_DMA_DATA = 0x19;
|
||||
constexpr uint32_t R_CONTEXT_STATE = 0x1A;
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ struct GraphicContext {
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool maintenance8_enabled = false;
|
||||
bool conditional_rendering_enabled = false;
|
||||
bool occlusion_queries_enabled = false;
|
||||
bool occlusion_query_precise_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
bool mesh_shader_enabled = false;
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||
@@ -42,6 +45,8 @@ struct GraphicContext {
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
uint32_t max_push_descriptors = 0;
|
||||
uint32_t device_clock_period_ps = 0;
|
||||
vk::DescriptorSetLayout image_heap_layout = nullptr;
|
||||
vk::ShaderStageFlags required_subgroup_size_stages = {};
|
||||
Common::Mutex queue_mutex;
|
||||
uint32_t queue_family = static_cast<uint32_t>(-1);
|
||||
|
||||
@@ -66,18 +66,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void MemoryTracker::QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE);
|
||||
const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE);
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
m_bda_sync.Add(begin, end - begin);
|
||||
}
|
||||
|
||||
RangeSet MemoryTracker::TakeBdaSync() {
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
return std::exchange(m_bda_sync, RangeSet {});
|
||||
}
|
||||
|
||||
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
@@ -100,7 +88,6 @@ void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
|
||||
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
@@ -141,8 +128,6 @@ void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
locks.clear();
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -29,10 +29,6 @@ public:
|
||||
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemory(uint64_t vaddr, uint64_t size);
|
||||
// Queue after changing ownership; a concurrent drain must not consume an event
|
||||
// before the dirty pages it describes become visible.
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] RangeSet TakeBdaSync();
|
||||
// Removes protection from a range and flushes GPU-owned data when required.
|
||||
template <typename Flush>
|
||||
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) noexcept {
|
||||
@@ -55,7 +51,6 @@ public:
|
||||
on_flush();
|
||||
}
|
||||
});
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
#if KYTY_BUILD == KYTY_BUILD_DEBUG
|
||||
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
|
||||
@@ -154,8 +149,6 @@ private:
|
||||
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
|
||||
std::vector<std::unique_ptr<RegionManager>> m_region_storage;
|
||||
std::mutex m_region_mutex;
|
||||
std::mutex m_bda_sync_mutex;
|
||||
RangeSet m_bda_sync;
|
||||
PageManager& m_page_manager;
|
||||
};
|
||||
|
||||
|
||||
+38
-9
@@ -127,7 +127,7 @@ void BufferCache::ChangeRegister(BufferId id) {
|
||||
}
|
||||
WriteDataBuffer(m_bda_pagetable_buffer, table_offset,
|
||||
addresses.data(), addresses.size() * sizeof(vk::DeviceAddress));
|
||||
m_memory_tracker.QueueBdaSync(buffer.CpuAddress(), buffer.Size());
|
||||
QueueBdaSync(buffer.CpuAddress(), buffer.Size());
|
||||
} else {
|
||||
const auto found = m_buffers.find(buffer.CpuAddress());
|
||||
EXIT_IF(found == m_buffers.end() || found->second != id);
|
||||
@@ -174,6 +174,7 @@ bool BufferCache::DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t
|
||||
total_size += Common::AlignUp(end - start, 64);
|
||||
});
|
||||
m_gpu_modified_ranges.Subtract(address, bytes);
|
||||
ForgetColorMetadataChecks(address, bytes);
|
||||
});
|
||||
if (copies.empty()) {
|
||||
return false;
|
||||
@@ -298,6 +299,7 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_memory_tracker.InvalidateRegion(vaddr, size,
|
||||
[this, vaddr, size] { ReadMemory(vaddr, size, true); });
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
|
||||
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
|
||||
@@ -317,6 +319,7 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
|
||||
}
|
||||
if (is_write) {
|
||||
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
|
||||
QueueBdaSync(vaddr, size);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -519,6 +522,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t
|
||||
(void)SynchronizeBuffer(buffer, vaddr, size, is_written, is_texel_buffer);
|
||||
if (is_written) {
|
||||
m_gpu_modified_ranges.Add(vaddr, size);
|
||||
ForgetColorMetadataChecks(vaddr, size);
|
||||
}
|
||||
return {&buffer, buffer.Offset(vaddr)};
|
||||
}
|
||||
@@ -623,6 +627,29 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
|
||||
return address + m_slot_buffers[id].Size() > vaddr;
|
||||
}
|
||||
|
||||
void BufferCache::MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) {
|
||||
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||
if ((keys & (1u << slot)) != 0) {
|
||||
m_checked_color_metadata[slot].Add(vaddr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const {
|
||||
for (uint32_t slot = 0; slot < m_checked_color_metadata.size(); slot++) {
|
||||
if ((keys & (1u << slot)) != 0 && !m_checked_color_metadata[slot].Contains(vaddr, size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BufferCache::ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size) {
|
||||
for (auto& checked: m_checked_color_metadata) {
|
||||
checked.Subtract(vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
return m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
}
|
||||
@@ -694,14 +721,16 @@ void BufferCache::ProcessFaultBuffer() {
|
||||
m_fault_manager.ProcessFaultBuffer();
|
||||
}
|
||||
|
||||
void BufferCache::SynchronizeBda(const RangeSet& mapped) {
|
||||
// Take before walking: writes queued during synchronization remain pending.
|
||||
const auto pending = m_memory_tracker.TakeBdaSync();
|
||||
pending.ForEach([&](uint64_t begin, uint64_t end) {
|
||||
mapped.ForEachInRange(begin, end - begin, [this](uint64_t start, uint64_t finish) {
|
||||
SynchronizeBuffersInRange(start, finish - start);
|
||||
});
|
||||
});
|
||||
void BufferCache::QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
const auto begin = Common::AlignDown(vaddr, TRACKER_PAGE_SIZE);
|
||||
const auto end = Common::AlignUp(vaddr + size, TRACKER_PAGE_SIZE);
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
m_bda_sync.Add(begin, end - begin);
|
||||
}
|
||||
|
||||
RangeSet BufferCache::TakeBdaSync() {
|
||||
std::lock_guard lock(m_bda_sync_mutex);
|
||||
return std::exchange(m_bda_sync, {});
|
||||
}
|
||||
|
||||
void BufferCache::SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size) {
|
||||
|
||||
+16
-9
@@ -11,7 +11,9 @@
|
||||
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -79,20 +81,21 @@ public:
|
||||
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
// Color metadata the GPU found free of the clear keys in `keys` (bits of code >> 5) stays so
|
||||
// until a GPU write or download.
|
||||
void MarkColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys);
|
||||
[[nodiscard]] bool IsColorMetadataChecked(uint64_t vaddr, uint64_t size, uint32_t keys) const;
|
||||
void ProcessFaultBuffer();
|
||||
void SynchronizeBda(const RangeSet& mapped);
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size) {
|
||||
m_memory_tracker.QueueBdaSync(vaddr, size);
|
||||
}
|
||||
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
|
||||
// Queue after the range's state change becomes visible, or a drain could consume it first.
|
||||
void QueueBdaSync(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] RangeSet TakeBdaSync();
|
||||
void RunGarbageCollector();
|
||||
|
||||
private:
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
bool IsBufferInvalid(BufferId id) const {
|
||||
const auto* buffer = m_slot_buffers.try_get(id);
|
||||
return buffer == nullptr || buffer->is_deleted;
|
||||
}
|
||||
bool IsBufferInvalid(BufferId id) const { return !id || m_slot_buffers[id].is_deleted; }
|
||||
|
||||
using BufferMap = std::map<uint64_t, BufferId>;
|
||||
struct OverlapResult {
|
||||
@@ -117,13 +120,13 @@ private:
|
||||
void DeleteBuffer(BufferId id);
|
||||
[[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
|
||||
bool is_written, bool is_texel_buffer);
|
||||
void SynchronizeBuffersInRange(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] vk::Buffer UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> copies,
|
||||
uint64_t total_size);
|
||||
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
// Synchronous downloads publish before returning; asynchronous callers wait before reuse.
|
||||
template <bool async>
|
||||
[[nodiscard]] bool DownloadBufferMemory(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
void ForgetColorMetadataChecks(uint64_t vaddr, uint64_t size);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
@@ -135,7 +138,11 @@ private:
|
||||
BufferMap m_buffers;
|
||||
PageTable m_page_table;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
// One per TileManager::ColorClearSlots key slot.
|
||||
std::array<RangeSet, 8> m_checked_color_metadata;
|
||||
MemoryTracker m_memory_tracker;
|
||||
std::mutex m_bda_sync_mutex;
|
||||
RangeSet m_bda_sync;
|
||||
StreamBuffer m_staging_buffer;
|
||||
StreamBuffer m_stream_buffer;
|
||||
StreamBuffer m_download_buffer;
|
||||
|
||||
+191
-104
@@ -229,6 +229,7 @@ void TextureCache::UnregisterImage(ImageId id) {
|
||||
if (!image.registered) {
|
||||
return;
|
||||
}
|
||||
image.HeapChanged();
|
||||
UntrackImage(id);
|
||||
ImagePageTable::PageRange pages {};
|
||||
if (!ImagePageTable::TryGetPageRange(image.info.data.address, image.info.data.size, pages)) {
|
||||
@@ -250,24 +251,24 @@ void TextureCache::UnregisterImage(ImageId id) {
|
||||
}
|
||||
|
||||
void TextureCache::DeleteImage(ImageId id) {
|
||||
auto* image = m_slot_images.try_get(id);
|
||||
if (image == nullptr || !image->registered) {
|
||||
auto& image = m_slot_images[id];
|
||||
if (!image.registered) {
|
||||
return;
|
||||
}
|
||||
if (image->depth_id) {
|
||||
std::erase(m_slot_images[image->depth_id].stencil_ids, id);
|
||||
if (image.depth_id) {
|
||||
std::erase(m_slot_images[image.depth_id].stencil_ids, id);
|
||||
} else {
|
||||
// Detach first: deleting each association also unlinks it from its depth owner.
|
||||
for (const auto association: std::exchange(image->stencil_ids, {})) {
|
||||
for (const auto association: std::exchange(image.stencil_ids, {})) {
|
||||
FreeImage(association);
|
||||
}
|
||||
}
|
||||
if (image->IsGpuModified()) {
|
||||
if (image.IsGpuModified()) {
|
||||
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
|
||||
}
|
||||
m_download_images.erase(id);
|
||||
if (image->info.metadata.kind == ImageMetadataKind::Htile) {
|
||||
m_surface_metas.erase(image->info.metadata.range.address);
|
||||
if (image.info.metadata.kind == ImageMetadataKind::Htile) {
|
||||
m_surface_metas.erase(image.info.metadata.range.address);
|
||||
}
|
||||
UnregisterImage(id);
|
||||
if (m_scheduler.Active()) {
|
||||
@@ -378,6 +379,7 @@ void TextureCache::UntrackImageHead(ImageId id) {
|
||||
const auto address = Common::AlignDown(begin + TRACKER_PAGE_SIZE, TRACKER_PAGE_SIZE);
|
||||
const auto size = address - begin;
|
||||
image.track_addr = address;
|
||||
image.HeapChanged();
|
||||
if (image.track_addr == image.track_addr_end) {
|
||||
MarkAsMaybeDirty(id, image);
|
||||
}
|
||||
@@ -395,6 +397,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
|
||||
const auto address = Common::AlignDown(end, TRACKER_PAGE_SIZE);
|
||||
const auto size = end - address;
|
||||
image.track_addr_end = address;
|
||||
image.HeapChanged();
|
||||
if (image.track_addr == image.track_addr_end) {
|
||||
MarkAsMaybeDirty(id, image);
|
||||
}
|
||||
@@ -426,12 +429,6 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64
|
||||
return {};
|
||||
}
|
||||
|
||||
uint32_t query_epoch = ++m_image_query_epoch;
|
||||
if (query_epoch == 0) {
|
||||
m_slot_images.ForEach([](ImageId, const Image& image) { image.query_epoch = 0; });
|
||||
query_epoch = ++m_image_query_epoch;
|
||||
}
|
||||
|
||||
ImageIds result;
|
||||
ForEachPage(address, size, [&](uint64_t page) {
|
||||
const auto* owners = m_image_page_table.Find(page);
|
||||
@@ -439,19 +436,17 @@ TextureCache::ImageIds TextureCache::FindImagesInRegion(uint64_t address, uint64
|
||||
return;
|
||||
}
|
||||
owners->ForEach([&](ImageId id) {
|
||||
auto* image = m_slot_images.try_get(id);
|
||||
if (image == nullptr) {
|
||||
const auto& image = m_slot_images[id];
|
||||
if (image.picked || !image.Overlaps(address, size, page_overlap)) {
|
||||
return;
|
||||
}
|
||||
if (image->query_epoch == query_epoch) {
|
||||
return;
|
||||
}
|
||||
image->query_epoch = query_epoch;
|
||||
if (image->Overlaps(address, size, page_overlap)) {
|
||||
result.push_back(id);
|
||||
}
|
||||
image.picked = true;
|
||||
result.push_back(id);
|
||||
});
|
||||
});
|
||||
for (const auto id: result) {
|
||||
m_slot_images[id].picked = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -784,18 +779,26 @@ void Resolve(TextureCache& cache, ImageId cached_id, ImageId merged_id) {
|
||||
TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& requested,
|
||||
BindingType binding, ImageId cached_id,
|
||||
ImageId merged_id) {
|
||||
auto owner = m_slot_images.try_get(cached_id);
|
||||
if (owner == nullptr || !owner->registered) {
|
||||
auto& cached = m_slot_images[cached_id];
|
||||
if (!cached.registered) {
|
||||
return {merged_id};
|
||||
}
|
||||
if (BcAlias::Matches(requested, owner->info) && !owner->IsCpuDirty()) {
|
||||
// PS5: image heaps keep BC images and raw block views of their layers resident together, so a
|
||||
// raw read of a layer subset with the same levels views the BC image instead of evicting it.
|
||||
const bool raw_view = binding == BindingType::Texture && !requested.IsBlock() &&
|
||||
requested.type == cached.info.type &&
|
||||
requested.data.address == cached.info.data.address &&
|
||||
requested.BlockExtent() == cached.info.BlockExtent() &&
|
||||
requested.resources.levels == cached.info.resources.levels &&
|
||||
requested.resources.layers < cached.info.resources.layers;
|
||||
if (!raw_view && BcAlias::Matches(requested, cached.info) && !cached.IsCpuDirty()) {
|
||||
BcAlias::Resolve(*this, cached_id, merged_id);
|
||||
return {merged_id};
|
||||
}
|
||||
auto& cached = *owner;
|
||||
const auto current_tick = m_scheduler.CurrentTick();
|
||||
const bool safe_to_delete =
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval;
|
||||
current_tick - std::min(current_tick, cached.tick_accessed_last) > NumFramesBeforeRemoval &&
|
||||
cached.heap_refs.load(std::memory_order_relaxed) == 0u;
|
||||
|
||||
const uint32_t requested_block = requested.bytes_per_block * requested.samples;
|
||||
const uint32_t cached_block = cached.info.bytes_per_block * cached.info.samples;
|
||||
@@ -865,9 +868,8 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
|
||||
if (requested.pixel_format != cached.info.pixel_format ||
|
||||
requested.data.size <= cached.info.data.size) {
|
||||
const auto result_id = merged_id ? merged_id : cached_id;
|
||||
const auto result = m_slot_images.try_get(result_id);
|
||||
return {result != nullptr && ImageViewOps::FormatsCompatible(result->info.pixel_format,
|
||||
requested.pixel_format)
|
||||
return {ImageViewOps::FormatsCompatible(m_slot_images[result_id].info.pixel_format,
|
||||
requested.pixel_format)
|
||||
? result_id
|
||||
: ImageId {}};
|
||||
}
|
||||
@@ -1067,12 +1069,78 @@ void TextureCache::MaterializeColorClear(ImageId id, const ImageDesc& desc,
|
||||
if (first >= layers || count > layers - first) {
|
||||
EXIT("TextureCache: color view exceeds its native metadata slices\n");
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the scheduler,
|
||||
// so discovery runs before final draw uploads and never holds the texture lock across it.
|
||||
if (m_buffer_cache.IsRegionGpuModified(range.address, range.size)) {
|
||||
m_buffer_cache.ReadMemory(range.address, range.size, false);
|
||||
const auto slice_size = range.size / layers;
|
||||
const GuestRange viewed {range.address + slice_size * first, slice_size * count};
|
||||
if (m_buffer_cache.IsRegionGpuModified(viewed.address, viewed.size)) {
|
||||
std::array<vk::ClearAttachment, TileManager::ColorClearSlots> clears {};
|
||||
uint32_t keys = 0;
|
||||
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||
clears[key].aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||
if (DecodeColorClear(desc, static_cast<uint8_t>(key << 5u),
|
||||
clears[key].clearValue.color)) {
|
||||
keys |= 1u << key;
|
||||
}
|
||||
}
|
||||
// No selected key to clear, or none left since the last check.
|
||||
if (m_buffer_cache.IsColorMetadataChecked(viewed.address, viewed.size, keys)) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::scoped_lock lock {m_lock};
|
||||
auto& image = m_slot_images[id];
|
||||
// Compressed images cannot upload, so they must already hold native contents.
|
||||
const bool native_current = (image.usage.render_target || image.IsGpuModified()) &&
|
||||
!image.IsBufferModified() && !image.IsCpuDirty();
|
||||
// 2D-array attachment views select 2D layers or, of a volume, depth slices.
|
||||
if (m_graphics.conditional_rendering_enabled &&
|
||||
image.backing.image_type != vk::ImageType::e1D &&
|
||||
count <= m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers &&
|
||||
(image.backing.usage & vk::ImageUsageFlagBits::eColorAttachment) &&
|
||||
(m_graphics.GetFormatProperties(view.format).optimalTilingFeatures &
|
||||
vk::FormatFeatureFlagBits::eColorAttachment) &&
|
||||
(desc.info.metadata.compression == VideoOutCompression::Uncompressed ||
|
||||
native_current)) {
|
||||
// The GPU selects each slice's clear, so the CPU never waits for the metadata.
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::ColorMetadataChecks);
|
||||
InitializeImage(id);
|
||||
const bool expand = desc.type != BindingType::VideoOut;
|
||||
const auto [buffer, offset] =
|
||||
m_buffer_cache.ObtainBuffer(viewed.address, viewed.size, expand);
|
||||
const auto predicates = m_tiler.CheckColorClear(
|
||||
{buffer->Handle(), offset, viewed.size}, count, keys, expand);
|
||||
if (expand) {
|
||||
m_buffer_cache.MarkColorMetadataChecked(viewed.address, viewed.size, keys);
|
||||
}
|
||||
const auto command = m_scheduler.Current().Handle();
|
||||
const auto area = BeginColorRendering(image, view.format, view.base_level,
|
||||
image_first, count, nullptr, command);
|
||||
vk::ClearRect rect {.rect = area, .layerCount = 1};
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
rect.baseArrayLayer = slice;
|
||||
for (uint32_t key = 0; key < clears.size(); key++) {
|
||||
if ((keys & (1u << key)) != 0) {
|
||||
const vk::ConditionalRenderingBeginInfoEXT predicate {
|
||||
.buffer = predicates,
|
||||
.offset = (slice * clears.size() + key) * sizeof(uint32_t)};
|
||||
command.beginConditionalRenderingEXT(&predicate);
|
||||
command.clearAttachments(1, &clears[key], 1, &rect);
|
||||
command.endConditionalRenderingEXT();
|
||||
}
|
||||
}
|
||||
}
|
||||
command.endRendering();
|
||||
CommitGpuWrite(image);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Finish native metadata writes before reading backing bytes. This can submit the
|
||||
// scheduler, so discovery runs before final draw uploads and never holds the texture
|
||||
// lock across it.
|
||||
Common::DebugCounters::ScopedTimer timer(
|
||||
Common::DebugCounters::Counter::ColorMetadataReadbacks,
|
||||
Common::DebugCounters::Counter::ColorMetadataReadbackNs);
|
||||
m_buffer_cache.ReadMemory(viewed.address, viewed.size, false);
|
||||
}
|
||||
const auto slice_size = range.size / layers;
|
||||
for (uint32_t slice = 0; slice < count; slice++) {
|
||||
const auto address = range.address + slice_size * (first + slice);
|
||||
uint8_t code = 0;
|
||||
@@ -1148,9 +1216,8 @@ ImageId TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
|
||||
|
||||
ImageId association {};
|
||||
for (const auto id: FindImagesInRegion(stencil.address, stencil.size, false)) {
|
||||
const auto owner = m_slot_images.try_get(id);
|
||||
if (owner != nullptr && owner->info.data == stencil &&
|
||||
owner->info.extent == depth.info.extent) {
|
||||
const auto& owner = m_slot_images[id];
|
||||
if (owner.info.data == stencil && owner.info.extent == depth.info.extent) {
|
||||
association = id;
|
||||
}
|
||||
}
|
||||
@@ -1243,12 +1310,24 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
||||
view_layer = overlap.layer;
|
||||
}
|
||||
}
|
||||
// PS5: image heaps hold a descriptor per dynamic-resolution extent of a render target,
|
||||
// and only one extent at an address holds data, so heap lookups create no second one.
|
||||
if (!result && desc.heap &&
|
||||
std::any_of(candidates.begin(), candidates.end(), [&](ImageId id) {
|
||||
const auto* image = m_slot_images.try_get(id);
|
||||
return image != nullptr && image->registered &&
|
||||
image->heap_refs.load(std::memory_order_relaxed) != 0u &&
|
||||
image->info.data.address == desc.info.data.address &&
|
||||
image->info.type == desc.info.type &&
|
||||
image->info.extent != desc.info.extent;
|
||||
})) {
|
||||
return {};
|
||||
}
|
||||
} else if (block_alias) {
|
||||
for (const auto candidate: candidates) {
|
||||
const auto* owner = m_slot_images.try_get(candidate);
|
||||
if (candidate != result && owner != nullptr && owner->registered &&
|
||||
!owner->IsCpuDirty() &&
|
||||
BcAlias::Matches(m_slot_images[result].info, owner->info)) {
|
||||
const auto& owner = m_slot_images[candidate];
|
||||
if (candidate != result && owner.registered && !owner.IsCpuDirty() &&
|
||||
BcAlias::Matches(m_slot_images[result].info, owner.info)) {
|
||||
BcAlias::Resolve(*this, candidate, result);
|
||||
}
|
||||
}
|
||||
@@ -1311,14 +1390,14 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
|
||||
std::scoped_lock lock {m_lock};
|
||||
ImageIds matches;
|
||||
for (const auto id: FindImagesInRegion(address, size, false)) {
|
||||
auto owner = m_slot_images.try_get(id);
|
||||
if (owner == nullptr || owner->info.data.address != address) {
|
||||
const auto* owner = &m_slot_images[id];
|
||||
if (owner->info.data.address != address) {
|
||||
continue;
|
||||
}
|
||||
if (ensure_valid && owner->depth_id) {
|
||||
owner = m_slot_images.try_get(owner->depth_id);
|
||||
owner = &m_slot_images[owner->depth_id];
|
||||
}
|
||||
if (owner == nullptr || (ensure_valid && !owner->SafeToDownload())) {
|
||||
if (ensure_valid && !owner->SafeToDownload()) {
|
||||
continue;
|
||||
}
|
||||
matches.push_back(id);
|
||||
@@ -1335,11 +1414,8 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selected && ensure_valid) {
|
||||
const auto owner = m_slot_images.try_get(selected);
|
||||
if (owner != nullptr && owner->depth_id) {
|
||||
selected = owner->depth_id;
|
||||
}
|
||||
if (selected && ensure_valid && m_slot_images[selected].depth_id) {
|
||||
selected = m_slot_images[selected].depth_id;
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
@@ -1360,9 +1436,8 @@ vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
|
||||
desc.info.data.End() <= image.info.stencil.End()) {
|
||||
for (const auto stencil_id:
|
||||
FindImagesInRegion(image.info.stencil.address, image.info.stencil.size, false)) {
|
||||
const auto* stencil = m_slot_images.try_get(stencil_id);
|
||||
if (stencil != nullptr && stencil->depth_id == id &&
|
||||
stencil->info.data == image.info.stencil) {
|
||||
const auto& stencil = m_slot_images[stencil_id];
|
||||
if (stencil.depth_id == id && stencil.info.data == image.info.stencil) {
|
||||
RefreshImage(stencil_id);
|
||||
break;
|
||||
}
|
||||
@@ -1473,27 +1548,24 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
|
||||
vk::ImageAspectFlags aspect {};
|
||||
ImageSubresourceRange subresources;
|
||||
for (const auto id: FindImagesInRegion(address, size, false)) {
|
||||
auto owner = m_slot_images.try_get(id);
|
||||
if (owner == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto& owner = m_slot_images[id];
|
||||
vk::ImageAspectFlags candidate {};
|
||||
ImageId candidate_id = id;
|
||||
ImageSubresourceRange candidate_subresources {
|
||||
0, owner->info.resources.levels, 0, owner->info.TransferLayers()};
|
||||
if (owner->depth_id && owner->info.data.address == address &&
|
||||
owner->info.data.size == size) {
|
||||
0, owner.info.resources.levels, 0, owner.info.TransferLayers()};
|
||||
if (owner.depth_id && owner.info.data.address == address &&
|
||||
owner.info.data.size == size) {
|
||||
candidate = vk::ImageAspectFlagBits::eStencil;
|
||||
candidate_id = owner->depth_id;
|
||||
candidate_subresources = owner->stencil_subresources;
|
||||
owner = m_slot_images.try_get(candidate_id);
|
||||
if (owner == nullptr || owner->backing.image == nullptr || !owner->info.HasStencil()) {
|
||||
candidate_id = owner.depth_id;
|
||||
candidate_subresources = owner.stencil_subresources;
|
||||
const auto& depth = m_slot_images[candidate_id];
|
||||
if (depth.backing.image == nullptr || !depth.info.HasStencil()) {
|
||||
continue;
|
||||
}
|
||||
} else if (!owner->depth_id && owner->info.data.address == address &&
|
||||
owner->info.data.size == size) {
|
||||
candidate = owner->info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor;
|
||||
} else if (!owner.depth_id && owner.info.data.address == address &&
|
||||
owner.info.data.size == size) {
|
||||
candidate = owner.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth
|
||||
: vk::ImageAspectFlagBits::eColor;
|
||||
}
|
||||
if (!candidate) {
|
||||
continue;
|
||||
@@ -1574,29 +1646,8 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
if (format != image.backing.format || (image.info.IsVolume() && !full_image)) {
|
||||
EXIT_NOT_IMPLEMENTED(range.aspectMask != vk::ImageAspectFlagBits::eColor ||
|
||||
range.levelCount != 1);
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.type = range.layerCount == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = range.baseMipLevel;
|
||||
view.base_layer = range.baseArrayLayer;
|
||||
view.layer_count = range.layerCount;
|
||||
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite, {}, command.Handle());
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = image.FindView(view);
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachment.clearValue = clear;
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = {
|
||||
std::max(image.info.extent.width >> range.baseMipLevel, 1u),
|
||||
std::max(image.info.extent.height >> range.baseMipLevel, 1u)};
|
||||
rendering.layerCount = range.layerCount;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.Handle().beginRendering(&rendering);
|
||||
BeginColorRendering(image, format, range.baseMipLevel, range.baseArrayLayer,
|
||||
range.layerCount, &clear, command.Handle());
|
||||
command.Handle().endRendering();
|
||||
CommitGpuWrite(image);
|
||||
return;
|
||||
@@ -1619,6 +1670,40 @@ void TextureCache::ClearImage(CommandBuffer& command, ImageId id, vk::Format for
|
||||
CommitGpuWrite(image);
|
||||
}
|
||||
|
||||
vk::Rect2D TextureCache::BeginColorRendering(Image& image, vk::Format format, uint32_t level,
|
||||
uint32_t layer, uint32_t layers,
|
||||
const vk::ClearValue* clear,
|
||||
vk::CommandBuffer command) {
|
||||
ImageViewInfo view {};
|
||||
view.format = format;
|
||||
view.type = layers == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
view.base_level = level;
|
||||
view.base_layer = layer;
|
||||
view.layer_count = layers;
|
||||
view.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
image.Transit(vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
{}, command);
|
||||
vk::RenderingAttachmentInfo attachment {};
|
||||
attachment.imageView = image.FindView(view);
|
||||
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eLoad;
|
||||
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
if (clear != nullptr) {
|
||||
attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
attachment.clearValue = *clear;
|
||||
}
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.renderArea.extent = {std::max(image.info.extent.width >> level, 1u),
|
||||
std::max(image.info.extent.height >> level, 1u)};
|
||||
rendering.layerCount = layers;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
command.beginRendering(&rendering);
|
||||
return rendering.renderArea;
|
||||
}
|
||||
|
||||
void TextureCache::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
if (!GuestRange {address, size}.Valid()) {
|
||||
EXIT("TextureCache: invalid memory-invalidation range\n");
|
||||
@@ -1752,6 +1837,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
|
||||
copy_size != image.info.data.size)) {
|
||||
return false;
|
||||
}
|
||||
ForgetColorMetadataChecks(image.info.data.address, copy_size);
|
||||
m_texture_cache.DownloadImage(image, buffer, buf_offset, copy_size, levels);
|
||||
return true;
|
||||
}
|
||||
@@ -1820,23 +1906,20 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
|
||||
const auto page_begin = Common::AlignDown(address, TRACKER_PAGE_SIZE);
|
||||
const auto page_end = Common::AlignUp(address + size, TRACKER_PAGE_SIZE);
|
||||
for (const auto id: FindImagesInRegion(address, size, true)) {
|
||||
auto owner = m_slot_images.try_get(id);
|
||||
if (owner == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (owner->Overlaps(address, size)) {
|
||||
owner->MarkCpuDirty();
|
||||
auto& owner = m_slot_images[id];
|
||||
if (owner.Overlaps(address, size)) {
|
||||
owner.MarkCpuDirty();
|
||||
UntrackImage(id);
|
||||
continue;
|
||||
}
|
||||
const auto image_begin = owner->info.data.address;
|
||||
const auto image_end = owner->info.data.End();
|
||||
const auto image_begin = owner.info.data.address;
|
||||
const auto image_end = owner.info.data.End();
|
||||
if (page_end < image_end) {
|
||||
UntrackImageHead(id);
|
||||
} else if (image_begin < page_begin) {
|
||||
UntrackImageTail(id);
|
||||
} else {
|
||||
MarkAsMaybeDirty(id, *owner);
|
||||
MarkAsMaybeDirty(id, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1935,6 +2018,11 @@ void TextureCache::RunGarbageCollector() {
|
||||
if (owner == nullptr || !owner->registered || owner->depth_id) {
|
||||
continue;
|
||||
}
|
||||
if (owner->heap_refs.load(std::memory_order_relaxed) != 0u) {
|
||||
// Resident heaps sample it without touching it.
|
||||
TouchImage(*owner);
|
||||
continue;
|
||||
}
|
||||
if (owner->IsGpuModified()) {
|
||||
const bool safe = owner->SafeToDownload();
|
||||
if (safe && owner->info.IsTiled()) {
|
||||
@@ -1971,8 +2059,7 @@ void TextureCache::ProcessDownloadImages() {
|
||||
}
|
||||
std::scoped_lock lock {m_lock};
|
||||
for (const auto id: m_download_images) {
|
||||
const auto owner = m_slot_images.try_get(id);
|
||||
if (owner != nullptr && owner->registered && owner->IsGpuModified()) {
|
||||
if (m_slot_images[id].IsGpuModified()) {
|
||||
(void)DownloadImageMemory(id);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -43,6 +43,7 @@ public:
|
||||
ImageInfo info;
|
||||
ImageViewInfo view_info;
|
||||
BindingType type = BindingType::Texture;
|
||||
bool heap = false; // PS5: resolved for a resident image heap.
|
||||
};
|
||||
|
||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
@@ -150,6 +151,12 @@ private:
|
||||
// Caller holds m_lock. Volume layer ranges select depth slices.
|
||||
void ClearImage(CommandBuffer& command, ImageId id, vk::Format format,
|
||||
const vk::ImageSubresourceRange& range, const vk::ClearValue& clear);
|
||||
// Caller holds m_lock and ends rendering. Begins rendering `layers` layers of one color mip
|
||||
// through a `format` view, clearing them or, when `clear` is null, loading them; returns the
|
||||
// render area.
|
||||
vk::Rect2D BeginColorRendering(Image& image, vk::Format format, uint32_t level,
|
||||
uint32_t layer, uint32_t layers, const vk::ClearValue* clear,
|
||||
vk::CommandBuffer command);
|
||||
void PrepareImageCopy(Image& image);
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
@@ -178,13 +185,13 @@ private:
|
||||
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
|
||||
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
|
||||
uint64_t m_gc_tick = 0;
|
||||
mutable uint32_t m_image_query_epoch = 0;
|
||||
bool m_readback_linear_images = false;
|
||||
bool m_readback_tiled_images = false;
|
||||
|
||||
friend struct TextureCacheTestAccess;
|
||||
friend class BufferCache;
|
||||
friend class RenderExecutor;
|
||||
friend class ImageHeap;
|
||||
friend bool BcAlias::Matches(const ImageInfo&, const ImageInfo&);
|
||||
friend bool BcAlias::Publish(TextureCache&, Image&);
|
||||
friend void BcAlias::Resolve(TextureCache&, ImageId, ImageId);
|
||||
|
||||
@@ -595,13 +595,6 @@ void LogDrawPhase(const char* draw_name, const char* phase) {
|
||||
}
|
||||
}
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: %s VS=%" PRIu64 " PS=%" PRIu64 "\n", phase, vertex_program_id,
|
||||
pixel_program_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void VpPrint(const char* func, const HW::ScreenViewport& vp,
|
||||
const HW::ScanModeControl& smc) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
@@ -90,7 +90,8 @@ ImageCreateFlags(const GraphicContext& graphics, const ImageInfo& info, vk::Imag
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
|
||||
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||
}
|
||||
if (info.samples == 1) {
|
||||
if (graphics.GetPhysicalDeviceProperties().limits.storageImageSampleCounts &
|
||||
static_cast<vk::SampleCountFlagBits>(info.samples)) {
|
||||
usage |= vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
return usage;
|
||||
@@ -211,6 +212,13 @@ Image::Barriers Image::GetBarriers(vk::ImageLayout destinat
|
||||
barriers.push_back(barrier);
|
||||
}
|
||||
|
||||
// Resident heaps sample their images in the read state; only leaving it invalidates them.
|
||||
if (std::ranges::any_of(barriers, [&](const vk::ImageMemoryBarrier2& barrier) {
|
||||
return barrier.oldLayout == ReadLayout() &&
|
||||
barrier.srcAccessMask == vk::AccessFlagBits2::eShaderRead;
|
||||
})) {
|
||||
HeapChanged();
|
||||
}
|
||||
state = {destination_stage, destination_access, destination_layout};
|
||||
return barriers;
|
||||
}
|
||||
@@ -417,15 +425,15 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const auto destination_height =
|
||||
std::max(backing.extent.height >> destination_range.base_level, 1u);
|
||||
const bool copy = source.backing.samples == 1;
|
||||
EXIT_IF(layers == 0 || info.extent.width > source_width || info.extent.height > source_height ||
|
||||
info.extent.width > destination_width || info.extent.height > destination_height ||
|
||||
EXIT_IF(layers == 0 ||
|
||||
(copy ? !ImageViewOps::FormatsCompatible(source.backing.format, backing.format)
|
||||
: source.backing.format != backing.format));
|
||||
auto resolved_source_range = source_range;
|
||||
auto resolved_destination_range = destination_range;
|
||||
resolved_source_range.layer_count = layers;
|
||||
resolved_destination_range.layer_count = layers;
|
||||
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
|
||||
const vk::Extent3D resolve_extent {std::min(source_width, destination_width),
|
||||
std::min(source_height, destination_height), 1};
|
||||
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/image/imageInfo.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <compare>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
@@ -71,6 +72,7 @@ public:
|
||||
void MarkCpuDirty() {
|
||||
m_cpu_dirty = true;
|
||||
m_maybe_cpu_dirty = false;
|
||||
HeapChanged();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCpuDirty() const { return m_cpu_dirty || m_maybe_cpu_dirty; }
|
||||
@@ -80,6 +82,7 @@ public:
|
||||
if (!IsCpuDirty()) {
|
||||
m_maybe_cpu_hash = HashGuestEdges();
|
||||
m_maybe_cpu_dirty = true;
|
||||
HeapChanged();
|
||||
}
|
||||
}
|
||||
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
|
||||
@@ -104,7 +107,10 @@ public:
|
||||
void ClearGpuModified() noexcept { m_gpu_modified = false; }
|
||||
|
||||
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
|
||||
void MarkBufferModified() noexcept { m_buffer_modified = true; }
|
||||
void MarkBufferModified() noexcept {
|
||||
m_buffer_modified = true;
|
||||
HeapChanged();
|
||||
}
|
||||
void ClearBufferModified() noexcept { m_buffer_modified = false; }
|
||||
|
||||
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size,
|
||||
@@ -120,14 +126,30 @@ public:
|
||||
return backing.image == nullptr ? 0 : Common::AlignUp(info.data.size, 1024);
|
||||
}
|
||||
[[nodiscard]] uint64_t HashGuestEdges() const;
|
||||
[[nodiscard]] vk::ImageLayout ReadLayout() const noexcept {
|
||||
return info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
// Resident image heaps sample an image without rediscovering it until one changes its
|
||||
// contents, tracking, identity or layout.
|
||||
void HeapChanged() const noexcept {
|
||||
if (heap_refs.load(std::memory_order_relaxed) != 0u) {
|
||||
heap_epoch.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::atomic<uint64_t> heap_epoch {0};
|
||||
static inline std::atomic<uint64_t> serials {0};
|
||||
|
||||
// Unique per image, unlike ImageId: freed slots are reused.
|
||||
const uint64_t serial = serials.fetch_add(1u, std::memory_order_relaxed) + 1u;
|
||||
ImageInfo info;
|
||||
VulkanImage backing;
|
||||
std::vector<CachedImageView> views;
|
||||
ImageUsage usage;
|
||||
ImageBinding binding;
|
||||
bool registered = false;
|
||||
mutable uint32_t query_epoch = 0;
|
||||
mutable bool picked = false; // Set only while FindImagesInRegion collects it.
|
||||
uint64_t track_addr = 0;
|
||||
uint64_t track_addr_end = 0;
|
||||
ImageId depth_id {};
|
||||
@@ -137,6 +159,8 @@ public:
|
||||
ImageSubresourceRange stencil_subresources;
|
||||
uint64_t tick_accessed_last = 0;
|
||||
size_t lru_id = 0;
|
||||
// Resident image heap elements that select this image.
|
||||
std::atomic<uint32_t> heap_refs {0};
|
||||
|
||||
private:
|
||||
friend struct ImageTestAccess;
|
||||
|
||||
@@ -378,6 +378,9 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
||||
const auto encoded = static_cast<float>(value & 0xffu) / 255.0f;
|
||||
return encoded <= 0.04045f ? encoded / 12.92f : std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
||||
};
|
||||
const auto unorm16 = [](uint64_t value) {
|
||||
return static_cast<float>(value & 0xffffu) / 65535.0f;
|
||||
};
|
||||
const auto float16 = [](uint32_t value) {
|
||||
const auto sign = (value & 0x8000u) << 16u;
|
||||
const auto exponent = (value >> 10u) & 0x1fu;
|
||||
@@ -403,19 +406,49 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
||||
next.float32[1] = float16(packed >> 16u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Sfloat: next.float32[0] = float16(packed); break;
|
||||
case vk::Format::eR16G16Unorm:
|
||||
next.float32[1] = static_cast<float>(packed >> 16u) / 65535.0f;
|
||||
case vk::Format::eR16G16B16A16Unorm:
|
||||
next.float32[2] = unorm16(clear_words >> 32u);
|
||||
next.float32[3] = unorm16(clear_words >> 48u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Unorm:
|
||||
next.float32[0] = static_cast<float>(packed & 0xffffu) / 65535.0f;
|
||||
case vk::Format::eR16G16Unorm:
|
||||
next.float32[1] = unorm16(packed >> 16u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR16Unorm: next.float32[0] = unorm16(packed); break;
|
||||
case vk::Format::eR16G16B16A16Uint:
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
next.uint32[i] = static_cast<uint16_t>(clear_words >> (16u * i));
|
||||
}
|
||||
break;
|
||||
// Each clear register stores one raw 32-bit float channel.
|
||||
case vk::Format::eR16G16B16A16Sint:
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
next.int32[i] = static_cast<int16_t>(clear_words >> (16u * i));
|
||||
}
|
||||
break;
|
||||
// Each clear register stores one raw 32-bit channel.
|
||||
case vk::Format::eR32G32Sfloat:
|
||||
next.float32[1] = std::bit_cast<float>(static_cast<uint32_t>(clear_words >> 32u));
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR32Sfloat: next.float32[0] = std::bit_cast<float>(packed); break;
|
||||
case vk::Format::eR32Uint: next.uint32[0] = packed; break;
|
||||
case vk::Format::eR32Sint: next.int32[0] = static_cast<int32_t>(packed); break;
|
||||
case vk::Format::eR32G32Uint:
|
||||
case vk::Format::eR32G32Sint:
|
||||
next.uint32[1] = static_cast<uint32_t>(clear_words >> 32u);
|
||||
[[fallthrough]];
|
||||
case vk::Format::eR32Uint:
|
||||
case vk::Format::eR32Sint: next.uint32[0] = packed; break;
|
||||
// Narrow targets hold their channels in the low bits of the first word. UFC 6 (PPSA23566)
|
||||
// fast-clears an R8_UINT map through CMASK every frame and then writes only part of it;
|
||||
// without these the clear was dropped and old contents piled up.
|
||||
case vk::Format::eR8Uint: next.uint32[0] = packed & 0xffu; break;
|
||||
case vk::Format::eR8Unorm: next.float32[0] = unorm8(packed); break;
|
||||
case vk::Format::eR8G8Uint:
|
||||
next.uint32[0] = packed & 0xffu;
|
||||
next.uint32[1] = (packed >> 8u) & 0xffu;
|
||||
break;
|
||||
case vk::Format::eR8G8Unorm:
|
||||
next.float32[0] = unorm8(packed);
|
||||
next.float32[1] = unorm8(packed >> 8u);
|
||||
break;
|
||||
case vk::Format::eR16Uint: next.uint32[0] = packed & 0xffffu; break;
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
next.float32[0] = srgb8(packed);
|
||||
next.float32[1] = srgb8(packed >> 8u);
|
||||
@@ -462,10 +495,16 @@ inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {
|
||||
// Keep this separate: the first register word alone cannot describe a 64-bit color.
|
||||
[[nodiscard]] inline bool DecodeColorDwordFill(vk::Format format, uint32_t packed,
|
||||
vk::ClearColorValue& clear) {
|
||||
if ((format == vk::Format::eR16Sfloat || format == vk::Format::eR16Unorm) &&
|
||||
if ((format == vk::Format::eR16Sfloat || format == vk::Format::eR16Unorm ||
|
||||
format == vk::Format::eR16Uint || format == vk::Format::eR8G8Unorm ||
|
||||
format == vk::Format::eR8G8Uint) &&
|
||||
(packed & 0xffffu) != (packed >> 16u)) {
|
||||
return false;
|
||||
}
|
||||
if ((format == vk::Format::eR8Unorm || format == vk::Format::eR8Uint) &&
|
||||
packed != (packed & 0xffu) * 0x01010101u) {
|
||||
return false;
|
||||
}
|
||||
if (format == vk::Format::eR16G16B16A16Sfloat) {
|
||||
if (packed != 0) {
|
||||
return false;
|
||||
|
||||
@@ -101,6 +101,7 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_color_transform_spv.h"
|
||||
#include "gpu_tiler_shaders/gpu_tiler_color_clear_spv.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
@@ -71,20 +72,11 @@ TileManager::~TileManager() {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_d16_to_d24 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d24, nullptr);
|
||||
}
|
||||
if (m_d16_to_d32 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d16_to_d32, nullptr);
|
||||
}
|
||||
if (m_d24_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d24_to_d16, nullptr);
|
||||
}
|
||||
if (m_d32_to_d16 != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_d32_to_d16, nullptr);
|
||||
}
|
||||
if (m_color_transform != nullptr) {
|
||||
m_graphics.device.destroyPipeline(m_color_transform, nullptr);
|
||||
for (auto pipeline: {m_d16_to_d24, m_d16_to_d32, m_d24_to_d16, m_d32_to_d16, m_color_transform,
|
||||
m_color_clear}) {
|
||||
if (pipeline != nullptr) {
|
||||
m_graphics.device.destroyPipeline(pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_pipeline_layout != nullptr) {
|
||||
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
|
||||
@@ -220,42 +212,37 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
if (m_pipelines[slot] != nullptr) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
struct Shader {
|
||||
const uint32_t* code;
|
||||
size_t words;
|
||||
};
|
||||
static constexpr std::array<Shader, FamilyCount> shaders {{
|
||||
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
|
||||
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
|
||||
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
|
||||
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
|
||||
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
|
||||
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
|
||||
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
|
||||
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
|
||||
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
|
||||
}};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
static constexpr std::array<std::span<const uint32_t>, FamilyCount> shaders {
|
||||
GPU_TILER_STANDARD256_SPV, GPU_TILER_STANDARD4_SPV, GPU_TILER_STANDARD4_3D_SPV,
|
||||
GPU_TILER_STANDARD64_SPV, GPU_TILER_STANDARD64_3D_SPV, GPU_TILER_PRT_SPV,
|
||||
GPU_TILER_PRT_3D_SPV, GPU_TILER_RENDER_TARGET_SPV, GPU_TILER_DEPTH_SPV};
|
||||
const uint32_t element_index = slot % BytesPerElementCount;
|
||||
const uint32_t direction_index = slot / (FamilyCount * BytesPerElementCount);
|
||||
const uint32_t family_index = (slot / BytesPerElementCount) % FamilyCount;
|
||||
const uint32_t values[] {1u << element_index, direction_index};
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
const auto module =
|
||||
CompileSPV({shaders[family_index].code, shaders[family_index].words}, m_graphics.device);
|
||||
m_pipelines[slot] = CreatePipeline(shaders[family_index], &specialization);
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
vk::Pipeline TileManager::CreatePipeline(std::span<const uint32_t> code,
|
||||
const vk::SpecializationInfo* specialization) const {
|
||||
const auto module = CompileSPV(code, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
stage.pSpecializationInfo = specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_pipelines[slot]);
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create TileManager pipeline");
|
||||
return m_pipelines[slot];
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
void TileManager::Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
@@ -434,39 +421,21 @@ uint32_t TileManager::ConversionRows(uint64_t offset, uint64_t row_stride, uint6
|
||||
|
||||
void TileManager::ConvertD16(Result source, Result target, D16Direction direction, bool d32,
|
||||
const D16Layout& layout) {
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
vk::Pipeline* pipeline_pointer = nullptr;
|
||||
std::span<const uint32_t> code;
|
||||
if (direction == D16Direction::Promote) {
|
||||
pipeline_pointer = d32 ? &m_d16_to_d32 : &m_d16_to_d24;
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
} else {
|
||||
pipeline_pointer = d32 ? &m_d32_to_d16 : &m_d24_to_d16;
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
}
|
||||
auto& pipeline = *pipeline_pointer;
|
||||
if (pipeline == nullptr) {
|
||||
const uint32_t value = d32 ? 1u : 0u;
|
||||
const vk::SpecializationMapEntry entry {0, 0, sizeof(value)};
|
||||
const vk::SpecializationInfo specialization {1, &entry, sizeof(value), &value};
|
||||
const uint32_t* code = nullptr;
|
||||
size_t words = 0;
|
||||
if (direction == D16Direction::Promote) {
|
||||
code = GPU_TILER_PROMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_PROMOTE_D16_SPV);
|
||||
} else {
|
||||
code = GPU_TILER_DEMOTE_D16_SPV;
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
const auto module = CompileSPV({code, words}, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create D16 conversion pipeline");
|
||||
pipeline = CreatePipeline(code, &specialization);
|
||||
}
|
||||
|
||||
const uint64_t source_element =
|
||||
@@ -599,18 +568,7 @@ void TileManager::TransformColor(Result input, Result output, ColorTransform tra
|
||||
output.size < input.size);
|
||||
const auto pixels = static_cast<uint32_t>(input.size / element_bytes);
|
||||
if (m_color_transform == nullptr) {
|
||||
const auto module = CompileSPV(GPU_TILER_COLOR_TRANSFORM_SPV, m_graphics.device);
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_color_transform);
|
||||
m_graphics.device.destroyShaderModule(module, nullptr);
|
||||
RequireVulkanSuccess(result, "create color transform pipeline");
|
||||
m_color_transform = CreatePipeline(GPU_TILER_COLOR_TRANSFORM_SPV, nullptr);
|
||||
}
|
||||
const auto input_binding = BindStorage(input, input.size);
|
||||
const auto output_binding = BindStorage(output, input.size);
|
||||
@@ -670,4 +628,60 @@ TileManager::Result TileManager::TransformColor(Result input, ColorTransform tra
|
||||
return result;
|
||||
}
|
||||
|
||||
vk::Buffer TileManager::CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand) {
|
||||
struct Check {
|
||||
uint32_t metadata_base;
|
||||
uint32_t words;
|
||||
uint32_t keys;
|
||||
uint32_t expand;
|
||||
};
|
||||
static_assert(sizeof(Check) <= sizeof(Conversion));
|
||||
const uint32_t max_slices =
|
||||
m_graphics.GetPhysicalDeviceProperties().limits.maxFramebufferLayers;
|
||||
EXIT_IF(slices == 0 || slices > max_slices || metadata.size % (4ull * slices) != 0 ||
|
||||
metadata.size / 4 > UINT32_MAX);
|
||||
if (m_color_clear == nullptr) {
|
||||
m_color_clear = CreatePipeline(GPU_TILER_COLOR_CLEAR_SPV, nullptr);
|
||||
m_clear_predicates = std::make_unique<Buffer>(
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eConditionalRenderingEXT,
|
||||
uint64_t {max_slices} * ColorClearSlots * sizeof(uint32_t));
|
||||
}
|
||||
const auto source = BindStorage(metadata, metadata.size);
|
||||
const vk::DescriptorBufferInfo infos[] {source.info,
|
||||
{m_clear_predicates->Handle(), 0, VK_WHOLE_SIZE}};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
writes[index].pBufferInfo = &infos[index];
|
||||
}
|
||||
// Orders the metadata writers and the previous check's predicate reads before this check.
|
||||
vk::MemoryBarrier barrier {
|
||||
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
|
||||
.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, nullptr,
|
||||
0, nullptr);
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_color_clear);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
|
||||
static_cast<uint32_t>(writes.size()), writes.data());
|
||||
const Check push {source.base, static_cast<uint32_t>(metadata.size / 4 / slices), keys,
|
||||
expand};
|
||||
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
|
||||
&push);
|
||||
command.dispatch(slices, 1, 1);
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::PipelineStageFlagBits::eAllCommands, {}, 1, &barrier, 0, nullptr,
|
||||
0, nullptr);
|
||||
return m_clear_predicates->Handle();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -54,6 +54,13 @@ public:
|
||||
const D16Layout& layout);
|
||||
[[nodiscard]] Result TransformColor(Result input, ColorTransform transform, bool to_host);
|
||||
void TransformColor(Result input, Result output, ColorTransform transform, bool to_host);
|
||||
// Color-clear keys are multiples of 0x20, so key >> 5 selects one of these slots.
|
||||
static constexpr uint32_t ColorClearSlots = 8;
|
||||
// Returns the buffer whose dword slice * ColorClearSlots + (key >> 5) is set when every byte
|
||||
// of that metadata slice equals a key whose bit `keys` selects; a hit expands to 0xff when
|
||||
// `expand` is set.
|
||||
[[nodiscard]] vk::Buffer CheckColorClear(Result metadata, uint32_t slices, uint32_t keys,
|
||||
bool expand);
|
||||
|
||||
private:
|
||||
friend struct TileManagerTestAccess;
|
||||
@@ -117,6 +124,8 @@ private:
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
const Tiling& tiling, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
[[nodiscard]] vk::Pipeline CreatePipeline(std::span<const uint32_t> code,
|
||||
const vk::SpecializationInfo* specialization) const;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
@@ -130,6 +139,8 @@ private:
|
||||
vk::Pipeline m_d24_to_d16 = nullptr;
|
||||
vk::Pipeline m_d32_to_d16 = nullptr;
|
||||
vk::Pipeline m_color_transform = nullptr;
|
||||
vk::Pipeline m_color_clear = nullptr;
|
||||
std::unique_ptr<Buffer> m_clear_predicates;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
// Queries no dump has read yet; past them a draw counts one sample.
|
||||
constexpr uint32_t QUERY_COUNT = 16384;
|
||||
|
||||
OcclusionQueries::OcclusionQueries(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
: m_scheduler(scheduler) {
|
||||
if (!graphics.occlusion_queries_enabled) {
|
||||
return;
|
||||
}
|
||||
m_device = graphics.device;
|
||||
const vk::QueryPoolCreateInfo info {.queryType = vk::QueryType::eOcclusion,
|
||||
.queryCount = QUERY_COUNT};
|
||||
RequireVulkanSuccess(m_device.createQueryPool(&info, nullptr, &m_pool), "vkCreateQueryPool");
|
||||
m_device.resetQueryPool(m_pool, 0, QUERY_COUNT);
|
||||
m_results.resize(QUERY_COUNT);
|
||||
if (graphics.occlusion_query_precise_enabled) {
|
||||
m_control = vk::QueryControlFlagBits::ePrecise;
|
||||
}
|
||||
}
|
||||
|
||||
OcclusionQueries::~OcclusionQueries() {
|
||||
if (m_pool) {
|
||||
m_device.destroyQueryPool(m_pool, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t OcclusionQueries::BeginQuery(vk::CommandBuffer command) {
|
||||
if (m_begun - m_read == QUERY_COUNT) {
|
||||
m_uncounted++;
|
||||
return -1;
|
||||
}
|
||||
const auto query = static_cast<uint32_t>(m_begun++ % QUERY_COUNT);
|
||||
command.beginQuery(m_pool, query, m_control);
|
||||
return static_cast<int32_t>(query);
|
||||
}
|
||||
|
||||
void OcclusionQueries::Dump(uint64_t address) {
|
||||
// Begins dump to the results, ends 8 bytes into them.
|
||||
if ((address & 8u) == 0) {
|
||||
m_open++;
|
||||
} else if (m_open != 0) {
|
||||
m_open--;
|
||||
}
|
||||
m_dumps.push_back({address, m_scheduler.CurrentTick(), m_begun, m_uncounted});
|
||||
}
|
||||
|
||||
void OcclusionQueries::PublishDumps() {
|
||||
if (const auto last = m_dumps.back().queries; last != m_read) {
|
||||
m_scheduler.Wait(m_dumps.back().tick);
|
||||
// The unread queries wrap around the pool at most once.
|
||||
for (uint64_t read = m_read; read < last;) {
|
||||
const auto first = static_cast<uint32_t>(read % QUERY_COUNT);
|
||||
const auto count =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(last - read, QUERY_COUNT - first));
|
||||
RequireVulkanSuccess(
|
||||
m_device.getQueryPoolResults(m_pool, first, count, count * sizeof(uint64_t),
|
||||
m_results.data() + first, sizeof(uint64_t),
|
||||
vk::QueryResultFlagBits::e64),
|
||||
"vkGetQueryPoolResults");
|
||||
m_device.resetQueryPool(m_pool, first, count);
|
||||
read += count;
|
||||
}
|
||||
}
|
||||
for (const auto& dump: m_dumps) {
|
||||
for (; m_read < dump.queries; m_read++) {
|
||||
m_samples += m_results[m_read % QUERY_COUNT];
|
||||
}
|
||||
WriteZPassCounters(reinterpret_cast<volatile uint64_t*>(dump.address),
|
||||
m_samples + dump.uncounted, (dump.address & 8u) == 0);
|
||||
}
|
||||
m_dumps.clear();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,85 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandScheduler;
|
||||
struct GraphicContext;
|
||||
|
||||
// The guest's query results (16-byte aligned): a begin/end pair of running z-pass counters per DB,
|
||||
// 16 bytes apart; bit 63 marks a written counter. A query's samples are the sum of end - begin.
|
||||
constexpr uint32_t OCCLUSION_QUERY_DBS = 16;
|
||||
constexpr uint64_t OCCLUSION_QUERY_READY = 1ull << 63u;
|
||||
|
||||
// A dump of the running count, all of it in DB 0. A begin also clears its end counters.
|
||||
inline void WriteZPassCounters(volatile uint64_t* results, uint64_t samples, bool begin) {
|
||||
for (uint32_t db = 0; db < OCCLUSION_QUERY_DBS; db++) {
|
||||
results[db * 2] = OCCLUSION_QUERY_READY | (db == 0 ? samples : 0);
|
||||
if (begin) {
|
||||
results[db * 2 + 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The guest's z-pass counters from host occlusion queries: a draw counts its samples while a guest
|
||||
// query is open, and the dumps' counters are written before the guest can see the GPU ran them.
|
||||
class OcclusionQueries {
|
||||
public:
|
||||
OcclusionQueries(GraphicContext& graphics, CommandScheduler& scheduler);
|
||||
~OcclusionQueries();
|
||||
KYTY_CLASS_NO_COPY(OcclusionQueries);
|
||||
|
||||
[[nodiscard]] bool Enabled() const { return m_pool != nullptr; }
|
||||
|
||||
// In the draw's rendering: the query that counts its samples, or -1.
|
||||
[[nodiscard]] int32_t Begin(vk::CommandBuffer command) {
|
||||
return m_open == 0 ? -1 : BeginQuery(command);
|
||||
}
|
||||
void End(vk::CommandBuffer command, int32_t query) const {
|
||||
if (query >= 0) {
|
||||
command.endQuery(m_pool, static_cast<uint32_t>(query));
|
||||
}
|
||||
}
|
||||
|
||||
// A PIXEL_PIPE_STAT_DUMP to address, after the draws recorded so far.
|
||||
void Dump(uint64_t address);
|
||||
// Waits for the GPU to run the dumps and writes their counters.
|
||||
void Publish() {
|
||||
if (!m_dumps.empty()) {
|
||||
PublishDumps();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct PendingDump {
|
||||
uint64_t address;
|
||||
uint64_t tick;
|
||||
uint64_t queries;
|
||||
uint64_t uncounted;
|
||||
};
|
||||
|
||||
int32_t BeginQuery(vk::CommandBuffer command);
|
||||
void PublishDumps();
|
||||
|
||||
CommandScheduler& m_scheduler;
|
||||
vk::Device m_device;
|
||||
vk::QueryPool m_pool;
|
||||
vk::QueryControlFlags m_control;
|
||||
std::vector<PendingDump> m_dumps;
|
||||
std::vector<uint64_t> m_results;
|
||||
uint32_t m_open = 0;
|
||||
uint64_t m_begun = 0;
|
||||
uint64_t m_read = 0;
|
||||
uint64_t m_samples = 0;
|
||||
uint64_t m_uncounted = 0;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||
@@ -254,6 +254,10 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor3D &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
const bool is_2d_msaa =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa &&
|
||||
descriptor.Type() == Prospero::ImageType::kColor2DMsaa && descriptor.Depth() == 0 &&
|
||||
descriptor.BaseArray5() == 0;
|
||||
TileTextureBlockLayout tile_layout {};
|
||||
bool supported_tile = false;
|
||||
switch (tile) {
|
||||
@@ -284,8 +288,8 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
|
||||
const bool supported_swizzle =
|
||||
IsValidImageSwizzle(swizzle) &&
|
||||
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
|
||||
descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d || is_2d_msaa) &&
|
||||
supported_tile && descriptor.BaseLevel() <= descriptor.LastLevel() &&
|
||||
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
|
||||
!descriptor.MsaaDepth();
|
||||
}
|
||||
@@ -356,7 +360,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
}
|
||||
view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled;
|
||||
view.mapping =
|
||||
storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
storage || resource.conversion_format != Prospero::BufferFormat::kInvalid
|
||||
? vk::ComponentMapping {}
|
||||
: TextureGetComponentMapping(descriptor.DstSelXYZW(), surface_format.host_to_storage);
|
||||
switch (resource.dimension) {
|
||||
@@ -515,7 +519,9 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
}
|
||||
const auto samples = multisampled ? 1u << last_level : 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto format = descriptor.Format();
|
||||
const auto format = ShaderRecompiler::IR::ReadsDepthBits(resource)
|
||||
? Prospero::BufferFormat::k16UNorm
|
||||
: descriptor.Format();
|
||||
const auto surface_format = TextureGetSurfaceFormatInfo(format);
|
||||
const bool shader_conversion =
|
||||
surface_format.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
@@ -579,6 +585,10 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
const auto alignment = desc.info.DataAlignment();
|
||||
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
|
||||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
|
||||
// Image heaps keep the descriptors of unmapped textures, which PS5 never samples.
|
||||
if (resource.heap && !m_context.IsMapped(address, desc.info.data.size)) {
|
||||
return {.layout = vk::ImageLayout::eGeneral};
|
||||
}
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, desc.info.data.size);
|
||||
}
|
||||
@@ -594,8 +604,13 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
view_levels, desc.info.resources.layers);
|
||||
desc.view_info.base_level = view_base;
|
||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||
desc.heap = resource.heap;
|
||||
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||
if (!id) {
|
||||
// Another extent at the address holds it; the heap retries once an image is created.
|
||||
return {.desc = std::move(desc), .layout = vk::ImageLayout::eGeneral};
|
||||
}
|
||||
auto* image = &texture_cache.GetImage(id);
|
||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||
if (stencil_association) {
|
||||
@@ -662,9 +677,7 @@ void RenderExecutor::BindRenderTarget(ImageId id) {
|
||||
|
||||
void RenderExecutor::ResetBindings() {
|
||||
for (const auto id: m_bound_images) {
|
||||
if (auto* image = m_context.GetTextureCache().m_slot_images.try_get(id); image != nullptr) {
|
||||
image->binding = {};
|
||||
}
|
||||
m_context.GetTextureCache().m_slot_images[id].binding = {};
|
||||
}
|
||||
m_bound_images.clear();
|
||||
}
|
||||
@@ -673,7 +686,7 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& program = runtime.module->info;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
prepared.runtime = &runtime;
|
||||
prepared.gds = {nullptr, 0, VK_WHOLE_SIZE};
|
||||
@@ -684,6 +697,10 @@ void RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
|
||||
prepared.samplers.clear();
|
||||
prepared.shader_data.clear();
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
if (program.info.images[i].heap) {
|
||||
prepared.images[i] = {};
|
||||
continue;
|
||||
}
|
||||
auto binding = ResolveTexture(program.info.images[i], snapshot.images[i]);
|
||||
BindImage(binding.image_id, binding.desc.type == TextureCache::BindingType::Storage);
|
||||
binding.mip_views.swap(prepared.images[i].mip_views);
|
||||
@@ -711,7 +728,7 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
for (auto* stage: stages) {
|
||||
auto& prepared = *stage;
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& program = prepared.runtime->module->info;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
prepared.buffer_sources.resize(layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
@@ -768,7 +785,7 @@ void RenderExecutor::FindBuffers(std::span<PreparedBindings* const> stages) {
|
||||
void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& program = prepared.runtime->module->info;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_sources.size() != layout.descriptor_counts[static_cast<size_t>(BindingKind::Buffers)]);
|
||||
@@ -789,6 +806,9 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
prepared.buffers[index] = NativeStorageBuffer(m_context, prepared.buffer_sources[index],
|
||||
resource, buffer_offset);
|
||||
pack_memory_offset(index, buffer_offset);
|
||||
// Null bindings allow 0 bytes, not the shared null buffer's 16, so no store reaches it.
|
||||
prepared.shader_data[layout.MemorySizeDword() + index] =
|
||||
static_cast<uint32_t>(prepared.buffer_sources[index].size + buffer_offset);
|
||||
}
|
||||
if (layout.descriptor_counts[static_cast<size_t>(BindingKind::FlattenedSrt)] != 0) {
|
||||
prepared.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||
@@ -801,7 +821,7 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(prepared.runtime == nullptr || !*prepared.runtime);
|
||||
const auto& program = *prepared.runtime->program;
|
||||
const auto& program = prepared.runtime->module->info;
|
||||
const auto& snapshot = *prepared.runtime->resources;
|
||||
auto& images = prepared.images;
|
||||
EXIT_IF(images.size() != program.info.images.size());
|
||||
@@ -809,12 +829,9 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
// Acquire each view before a later overlapping descriptor can replace its image.
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
if (!images[i].image_id) continue;
|
||||
const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id);
|
||||
if (old_image == nullptr || !old_image->registered ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
auto& old_image = texture_cache.m_slot_images[images[i].image_id];
|
||||
if (!old_image.registered || old_image.binding.needs_rebind) {
|
||||
old_image.binding = {};
|
||||
images[i] = ResolveTexture(program.info.images[i], snapshot.images[i]);
|
||||
BindImage(images[i].image_id,
|
||||
images[i].desc.type == TextureCache::BindingType::Storage);
|
||||
@@ -846,26 +863,28 @@ void RenderExecutor::RebindImages(PreparedBindings& prepared) {
|
||||
|
||||
void RenderExecutor::PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors) {
|
||||
bool uses_dma = false;
|
||||
bool uses_dma = false;
|
||||
bool uses_image_heap = false;
|
||||
FindBuffers(stages);
|
||||
for (auto* stage: stages) {
|
||||
uses_dma |= stage->runtime->program->info.uses_dma;
|
||||
uses_dma |= stage->runtime->module->info.info.uses_dma;
|
||||
uses_image_heap |= stage->runtime->module->info.info.uses_image_heap;
|
||||
}
|
||||
if (uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
if (uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
for (auto* stage: stages) {
|
||||
RebindImages(*stage);
|
||||
}
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
for (auto& target: colors) {
|
||||
EXIT_IF(!target.image_id);
|
||||
const auto old_image = cache.m_slot_images.try_get(target.image_id);
|
||||
if (old_image == nullptr || !old_image->registered ||
|
||||
old_image->binding.needs_rebind) {
|
||||
if (old_image != nullptr) {
|
||||
old_image->binding = {};
|
||||
}
|
||||
auto& old_image = cache.m_slot_images[target.image_id];
|
||||
if (!old_image.registered || old_image.binding.needs_rebind) {
|
||||
old_image.binding = {};
|
||||
target.desc.view_info.base_level = target.guest_mip_level;
|
||||
target.desc.view_info.base_layer = target.guest_array_layer;
|
||||
target.image_id = cache.FindImage(target.desc);
|
||||
@@ -898,7 +917,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
: vk::ShaderStageFlags {};
|
||||
for (const auto* prepared: prepared_bindings) {
|
||||
EXIT_IF(prepared == nullptr || prepared->runtime == nullptr || !*prepared->runtime);
|
||||
const auto& program = *prepared->runtime->program;
|
||||
const auto& program = prepared->runtime->module->info;
|
||||
write_count += std::popcount(program.bindings.descriptor_mask);
|
||||
for (auto mask = program.bindings.descriptor_mask; mask != 0; mask &= mask - 1) {
|
||||
const auto index = std::countr_zero(mask);
|
||||
@@ -919,17 +938,16 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
const auto& reads = reader->runtime->resources->specialization_reads;
|
||||
if (reads.empty()) continue;
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
if (writer->runtime->program->has_address_writes) {
|
||||
if (writer->runtime->module->info.has_address_writes) {
|
||||
EXIT("scalar resource reads cannot be proven disjoint from shader address writes\n");
|
||||
}
|
||||
}
|
||||
for (const auto [address, size]: reads) {
|
||||
for (const auto id: m_bound_images) {
|
||||
const auto* image = m_context.GetTextureCache().m_slot_images.try_get(id);
|
||||
if (image == nullptr ||
|
||||
(!image->binding.shader_write && !image->binding.is_target)) continue;
|
||||
for (const auto written: {image->info.data, image->info.stencil,
|
||||
image->info.metadata.range}) {
|
||||
const auto& image = m_context.GetTextureCache().m_slot_images[id];
|
||||
if (!image.binding.shader_write && !image.binding.is_target) continue;
|
||||
for (const auto written: {image.info.data, image.info.stencil,
|
||||
image.info.metadata.range}) {
|
||||
if (written.size != 0 && ImageRangeOverlaps(address, size,
|
||||
written.address, written.size)) {
|
||||
EXIT("scalar resource reads overlap an image or attachment write\n");
|
||||
@@ -937,7 +955,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
for (const auto* writer: prepared_bindings) {
|
||||
const auto& program = *writer->runtime->program;
|
||||
const auto& program = writer->runtime->module->info;
|
||||
for (const auto& resource: program.info.buffers) {
|
||||
if (!resource.written || resource.descriptor_index == UINT32_MAX) continue;
|
||||
const auto& written = writer->buffer_sources[resource.descriptor_index];
|
||||
@@ -953,6 +971,9 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pipeline.uses_image_heap) {
|
||||
m_context.GetImageHeap().Commit(buffer, pipeline_bind_point, pipeline.pipeline_layout);
|
||||
}
|
||||
m_descriptor_images.resize(image_descriptor_count);
|
||||
m_descriptor_writes.clear();
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
@@ -960,7 +981,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
std::array<vk::DescriptorBufferInfo, 2> address_buffers;
|
||||
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
const auto& program = *prepared->runtime->program;
|
||||
const auto& program = prepared->runtime->module->info;
|
||||
auto& descriptors = *prepared;
|
||||
const auto shader_stage = NativeShaderStage(program.stage);
|
||||
const auto shader_stages = ShaderPipelineStages(shader_stage);
|
||||
@@ -1010,9 +1031,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
|
||||
range, vk_buffer);
|
||||
} else {
|
||||
image.Transit(image.info.IsDepth() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
|
||||
: vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
image.Transit(image.ReadLayout(), vk::AccessFlagBits2::eShaderRead, range, vk_buffer);
|
||||
}
|
||||
binding.layout = image.backing.state.layout;
|
||||
}
|
||||
@@ -1083,6 +1102,7 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
for (uint32_t i = 0; i < program.info.images.size(); ++i) {
|
||||
const auto& resource = program.info.images[i];
|
||||
const auto& texture = descriptors.images[i];
|
||||
if (resource.heap) continue;
|
||||
EXIT_IF(texture.image_id &&
|
||||
resource.mip_count != (texture.mip_views.empty() ? 1u : texture.mip_views.size()));
|
||||
const auto kind = *ShaderRecompiler::IR::DescriptorBindingForImage(resource);
|
||||
|
||||
@@ -0,0 +1,618 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
namespace IR = ShaderRecompiler::IR;
|
||||
|
||||
// Smaller tables are cheap to materialize on each draw and are often transient.
|
||||
constexpr uint64_t MinTableBytes = 64ull * 1024ull;
|
||||
constexpr uint32_t ElementMask = (1u << IR::ImageHeapKindShift) - 1u;
|
||||
|
||||
template <typename Pred, typename Fn>
|
||||
void ForEachRun(size_t begin, size_t end, Pred&& pred, Fn&& fn) {
|
||||
while (begin < end) {
|
||||
if (!pred(begin)) {
|
||||
++begin;
|
||||
continue;
|
||||
}
|
||||
auto run = begin + 1u;
|
||||
while (run < end && pred(run)) ++run;
|
||||
fn(begin, run);
|
||||
begin = run;
|
||||
}
|
||||
}
|
||||
|
||||
// Grows [begin, end) to cover [first, last).
|
||||
void Widen(size_t& begin, size_t& end, size_t first, size_t last) {
|
||||
const bool empty = begin == end;
|
||||
begin = empty ? first : std::min(begin, first);
|
||||
end = empty ? last : std::max(end, last);
|
||||
}
|
||||
|
||||
// Probes at first + k * step whose first dword lies within a table of size bytes.
|
||||
uint64_t Probes(uint64_t size, uint64_t first, uint64_t step) {
|
||||
return size >= first + 4u ? (size - first - 4u) / step + 1u : 0u;
|
||||
}
|
||||
|
||||
// Tables grow a little on most frames, so their slot words get room to double.
|
||||
std::unique_ptr<Buffer> SlotBuffer(RenderContext& context, uint64_t size) {
|
||||
return std::make_unique<Buffer>(
|
||||
context.GetGraphics(), context.GetCommandScheduler(), MemoryUsage::DeviceLocal, 0,
|
||||
vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer |
|
||||
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||
std::bit_ceil(size + 4u));
|
||||
}
|
||||
|
||||
// Guest bytes of a table that its tracker pages [begin, end) cover.
|
||||
std::pair<uint64_t, uint64_t> PageRange(uint64_t base, uint64_t size, size_t begin, size_t end) {
|
||||
const auto origin = Common::AlignDown(base, TRACKER_PAGE_SIZE);
|
||||
return {std::max(base, origin + begin * TRACKER_PAGE_SIZE),
|
||||
std::min(base + size, origin + end * TRACKER_PAGE_SIZE)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t ImageHeap::DescriptorHash::operator()(const DescriptorValue& value) const noexcept {
|
||||
return XXH3_64bits(value.dwords.data(), sizeof(value.dwords));
|
||||
}
|
||||
|
||||
ImageHeap::ImageHeap(RenderContext& context, PageManager& page_manager)
|
||||
: m_context(context), m_page_manager(page_manager) {
|
||||
auto& graphics = context.GetGraphics();
|
||||
vk::PhysicalDeviceVulkan12Properties limits {};
|
||||
vk::PhysicalDeviceProperties2 properties {};
|
||||
properties.pNext = &limits;
|
||||
graphics.physical_device.getProperties2(&properties);
|
||||
EXIT_NOT_IMPLEMENTED(std::min({limits.maxDescriptorSetUpdateAfterBindSampledImages,
|
||||
limits.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
||||
limits.maxPerStageUpdateAfterBindResources}) <
|
||||
IR::ImageHeapCapacity);
|
||||
|
||||
const vk::DescriptorBindingFlags binding_flags =
|
||||
vk::DescriptorBindingFlagBits::ePartiallyBound |
|
||||
vk::DescriptorBindingFlagBits::eUpdateAfterBind |
|
||||
vk::DescriptorBindingFlagBits::eUpdateUnusedWhilePending;
|
||||
vk::DescriptorSetLayoutBindingFlagsCreateInfo flags_info {};
|
||||
flags_info.bindingCount = 1;
|
||||
flags_info.pBindingFlags = &binding_flags;
|
||||
const vk::DescriptorSetLayoutBinding binding {0, vk::DescriptorType::eSampledImage,
|
||||
IR::ImageHeapCapacity,
|
||||
vk::ShaderStageFlagBits::eAll, nullptr};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info {};
|
||||
layout_info.pNext = &flags_info;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::eUpdateAfterBindPool;
|
||||
layout_info.bindingCount = 1;
|
||||
layout_info.pBindings = &binding;
|
||||
EXIT_IF(graphics.device.createDescriptorSetLayout(&layout_info, nullptr, &m_layout) !=
|
||||
vk::Result::eSuccess);
|
||||
const vk::DescriptorPoolSize pool_size {vk::DescriptorType::eSampledImage,
|
||||
IR::ImageHeapCapacity};
|
||||
vk::DescriptorPoolCreateInfo pool_info {};
|
||||
pool_info.flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind;
|
||||
pool_info.maxSets = 1;
|
||||
pool_info.poolSizeCount = 1;
|
||||
pool_info.pPoolSizes = &pool_size;
|
||||
EXIT_IF(graphics.device.createDescriptorPool(&pool_info, nullptr, &m_pool) !=
|
||||
vk::Result::eSuccess);
|
||||
vk::DescriptorSetAllocateInfo allocate {};
|
||||
allocate.descriptorPool = m_pool;
|
||||
allocate.descriptorSetCount = 1;
|
||||
allocate.pSetLayouts = &m_layout;
|
||||
EXIT_IF(graphics.device.allocateDescriptorSets(&allocate, &m_set) != vk::Result::eSuccess);
|
||||
graphics.image_heap_layout = m_layout;
|
||||
// Slot word 0 selects element 0, which stays a null image.
|
||||
m_elements.push_back({.binding = {.layout = vk::ImageLayout::eShaderReadOnlyOptimal}});
|
||||
m_writes.push_back(0u);
|
||||
WriteDescriptors();
|
||||
}
|
||||
|
||||
ImageHeap::~ImageHeap() {
|
||||
for (const auto& memory: m_memories) Unwatch(*memory);
|
||||
auto& graphics = m_context.GetGraphics();
|
||||
graphics.image_heap_layout = nullptr;
|
||||
graphics.device.destroyDescriptorPool(m_pool, nullptr);
|
||||
graphics.device.destroyDescriptorSetLayout(m_layout, nullptr);
|
||||
}
|
||||
|
||||
bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView& result) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto size = request.size & ~uint64_t {3};
|
||||
if (size < MinTableBytes || 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; });
|
||||
// A table follows its descriptor's size, as the first program of a draw acquires it.
|
||||
if (memory == m_memories.end() || ((*memory)->size != size && (*memory)->draw != m_draw)) {
|
||||
if (!m_context.IsMapped(request.base, size)) return false;
|
||||
if (memory == m_memories.end()) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
memory = m_memories.insert(m_memories.end(),
|
||||
std::make_unique<Memory>(Memory {.base = request.base}));
|
||||
}
|
||||
Resize(**memory, size);
|
||||
}
|
||||
// A draw refreshes a table and a view takes its changes once, so all programs of the draw see
|
||||
// one snapshot and one set of kinds.
|
||||
if ((*memory)->draw != m_draw) {
|
||||
if (!Refresh(**memory)) return false;
|
||||
(*memory)->draw = m_draw;
|
||||
}
|
||||
auto& views = (*memory)->views;
|
||||
const auto bytes = (*memory)->size;
|
||||
const auto profile = IR::ImageHeapResource(*request.root, 0u);
|
||||
const auto probes = Probes(size, request.first, request.step);
|
||||
const auto limit = request.count < probes ? request.count : UINT64_MAX;
|
||||
auto view = std::ranges::find_if(views, [&](const auto& candidate) {
|
||||
return candidate->step == request.step && candidate->first == request.first &&
|
||||
candidate->limit == limit && candidate->profile == profile;
|
||||
});
|
||||
if (view == views.end()) {
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
const auto count = std::min(limit, Probes(bytes, request.first, request.step));
|
||||
auto created = std::make_unique<View>(View {.memory = **memory,
|
||||
.step = request.step,
|
||||
.first = request.first,
|
||||
.count = count,
|
||||
.limit = limit,
|
||||
.profile = profile,
|
||||
.resolve_epoch = epoch,
|
||||
.layout_epoch = epoch});
|
||||
created->slots.resize(bytes / 4u + 1u);
|
||||
created->buffer = SlotBuffer(m_context, bytes);
|
||||
UpdateProbes(*created, 0u, bytes);
|
||||
// The first upload also clears the slots no probe selects.
|
||||
created->dirty_begin = 0;
|
||||
created->dirty_end = created->slots.size();
|
||||
view = views.insert(views.end(), std::move(created));
|
||||
} else {
|
||||
// Empty after the first acquire in a draw, since the table refreshes once per draw.
|
||||
UpdateProbes(**view, (*view)->changed_begin, (*view)->changed_end);
|
||||
(*view)->changed_begin = (*view)->changed_end = 0;
|
||||
}
|
||||
if (std::ranges::find(m_active, view->get()) == m_active.end()) m_active.push_back(view->get());
|
||||
const auto& selected = **view;
|
||||
result = {.slots = selected.buffer->BufferDeviceAddress(),
|
||||
.slot_count = static_cast<uint32_t>(selected.slots.size() - 1u),
|
||||
.kinds = selected.kinds};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageHeap::Refresh(Memory& memory) {
|
||||
// GPU writes leave no fault; reading the table downloads them.
|
||||
const bool gpu_dirty = m_context.GetBufferCache().HasGpuDirtyBytes(memory.base, memory.size);
|
||||
const auto pages = memory.dirty_pages.size();
|
||||
m_runs.clear();
|
||||
{
|
||||
std::scoped_lock lock {m_mutex};
|
||||
// Watch before reading, so a write during the read faults the page again.
|
||||
ForEachRun(
|
||||
0, pages, [&](size_t page) { return memory.dirty_pages[page] != 0u; },
|
||||
[&](size_t begin, size_t end) {
|
||||
std::fill(memory.dirty_pages.begin() + begin, memory.dirty_pages.begin() + end,
|
||||
uint8_t {0});
|
||||
Watch<true>(memory, begin, end);
|
||||
m_runs.emplace_back(begin, end);
|
||||
});
|
||||
}
|
||||
if (gpu_dirty) m_runs.assign(1u, {0u, pages});
|
||||
for (size_t run = 0; run < m_runs.size(); ++run) {
|
||||
const auto [address, end] = PageRange(memory.base, memory.size, m_runs[run].first,
|
||||
m_runs[run].second);
|
||||
m_scratch.resize((end - address) / 4u);
|
||||
if (!LibKernel::Memory::TryReadBufferBacking(address, m_scratch.data(), end - address)) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (; run < m_runs.size(); ++run) {
|
||||
MarkDirty(memory, m_runs[run].first, m_runs[run].second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto words = std::span(memory.words).subspan((address - memory.base) / 4u,
|
||||
m_scratch.size());
|
||||
size_t first = 0;
|
||||
size_t last = words.size();
|
||||
while (first < last && words[first] == m_scratch[first]) ++first;
|
||||
while (first < last && words[last - 1u] == m_scratch[last - 1u]) --last;
|
||||
if (first == last) continue;
|
||||
std::copy(m_scratch.begin() + first, m_scratch.begin() + last, words.begin() + first);
|
||||
const auto offset = address - memory.base;
|
||||
for (auto& view: memory.views) {
|
||||
Widen(view->changed_begin, view->changed_end, offset + first * 4u, offset + last * 4u);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImageHeap::MarkDirty(Memory& memory, size_t begin, size_t end) {
|
||||
ForEachRun(
|
||||
begin, end, [&](size_t page) { return memory.dirty_pages[page] == 0u; },
|
||||
[&](size_t first, size_t last) {
|
||||
std::fill(memory.dirty_pages.begin() + first, memory.dirty_pages.begin() + last,
|
||||
uint8_t {1});
|
||||
Watch<false>(memory, first, last);
|
||||
});
|
||||
}
|
||||
|
||||
template <bool track>
|
||||
void ImageHeap::Watch(const Memory& memory, size_t begin, size_t end) {
|
||||
const auto [address, limit] = PageRange(memory.base, memory.size, begin, end);
|
||||
m_page_manager.UpdatePageWatchers<track>(address, limit - address);
|
||||
}
|
||||
|
||||
void ImageHeap::Unwatch(const Memory& memory) {
|
||||
ForEachRun(
|
||||
0, memory.dirty_pages.size(), [&](size_t page) { return memory.dirty_pages[page] == 0u; },
|
||||
[&](size_t begin, size_t end) { Watch<false>(memory, begin, end); });
|
||||
}
|
||||
|
||||
void ImageHeap::InvalidateMemory(uint64_t address, uint64_t size) {
|
||||
std::scoped_lock lock {m_mutex};
|
||||
for (auto& memory: m_memories) {
|
||||
// Watches cover whole pages, so a write next to the table must unwatch its page too.
|
||||
const auto origin = Common::AlignDown(memory->base, TRACKER_PAGE_SIZE);
|
||||
const auto begin = std::max(address, origin);
|
||||
const auto end = std::min(address + size,
|
||||
origin + memory->dirty_pages.size() * TRACKER_PAGE_SIZE);
|
||||
if (begin >= end) continue;
|
||||
MarkDirty(*memory, (begin - origin) / TRACKER_PAGE_SIZE,
|
||||
(end - 1u - origin) / TRACKER_PAGE_SIZE + 1u);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::UpdateProbes(View& view, uint64_t begin, uint64_t end) {
|
||||
// A probe reads its descriptor from first + probe * step.
|
||||
const uint64_t bytes = view.profile.r128 ? 16u : 32u;
|
||||
auto probe = begin < view.first + bytes ? 0u : (begin - view.first - bytes) / view.step + 1u;
|
||||
for (; probe < view.count && view.first + probe * view.step < end; ++probe) {
|
||||
UpdateProbe(view, probe);
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::UpdateProbe(View& view, uint64_t probe) {
|
||||
const auto& words = view.memory.words;
|
||||
const auto offset = (view.first + probe * view.step) / 4u;
|
||||
DescriptorValue value {.dword_count = 8u};
|
||||
std::copy_n(words.begin() + offset,
|
||||
std::min<size_t>(view.profile.r128 ? 4u : 8u, words.size() - offset),
|
||||
value.dwords.begin());
|
||||
// A buried word stays null in every view until its descriptor changes.
|
||||
auto& dead = view.memory.dead[offset];
|
||||
if (dead != 0u && dead != DescriptorHash {}(value)) dead = 0u;
|
||||
const auto kind = dead != 0u ? 0u : IR::ImageHeapKind(view.profile, value);
|
||||
Assign(view, offset, kind == 0u ? 0u : Intern(view, kind, value));
|
||||
}
|
||||
|
||||
void ImageHeap::Assign(View& view, size_t slot, uint32_t entry) {
|
||||
const auto previous = std::exchange(view.slots[slot], entry);
|
||||
Release(view, previous);
|
||||
if (previous != entry) Widen(view.dirty_begin, view.dirty_end, slot, slot + 1u);
|
||||
}
|
||||
|
||||
void ImageHeap::Bury() {
|
||||
if (m_unmapped.empty()) return;
|
||||
for (const auto& memory: m_memories) {
|
||||
for (const auto& view: memory->views) {
|
||||
bool buried = false;
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (binding.image_id && std::ranges::any_of(m_unmapped, [&](GuestRange range) {
|
||||
return ImageRangeOverlaps(binding.desc.info.data, range);
|
||||
})) {
|
||||
m_elements[index].stale = buried = true;
|
||||
}
|
||||
}
|
||||
if (!buried) continue;
|
||||
for (uint64_t probe = 0; probe < view->count; ++probe) {
|
||||
const auto slot = (view->first + probe * view->step) / 4u;
|
||||
const auto& element = m_elements[view->slots[slot] & ElementMask];
|
||||
if (!element.stale) continue;
|
||||
memory->dead[slot] = DescriptorHash {}(element.value);
|
||||
Assign(*view, slot, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_unmapped.clear();
|
||||
}
|
||||
|
||||
uint32_t ImageHeap::Intern(View& view, uint32_t kind, const DescriptorValue& value) {
|
||||
const auto arm = static_cast<uint32_t>(std::ranges::find(view.kind_keys, kind) -
|
||||
view.kind_keys.begin());
|
||||
if (arm == view.kind_keys.size()) {
|
||||
view.kind_keys.push_back(kind);
|
||||
view.kinds.push_back(value);
|
||||
}
|
||||
auto [found, inserted] = view.elements.try_emplace(value, 0u);
|
||||
if (inserted) {
|
||||
found->second = Allocate();
|
||||
auto& element = m_elements[found->second];
|
||||
element.value = value;
|
||||
element.kind = kind;
|
||||
view.pending.push_back(found->second);
|
||||
}
|
||||
++m_elements[found->second].refs;
|
||||
return arm << IR::ImageHeapKindShift | found->second;
|
||||
}
|
||||
|
||||
void ImageHeap::Release(View& view, uint32_t entry) {
|
||||
const auto index = entry & ElementMask;
|
||||
if (index == 0u || --m_elements[index].refs != 0u) return;
|
||||
auto& element = m_elements[index];
|
||||
if (const auto found = view.elements.find(element.value);
|
||||
found != view.elements.end() && found->second == index) {
|
||||
view.elements.erase(found);
|
||||
}
|
||||
Unreference(element);
|
||||
view.released.push_back(index);
|
||||
}
|
||||
|
||||
void ImageHeap::Unreference(Element& element) {
|
||||
if (auto* image = ImageOf(element)) {
|
||||
image->heap_refs.fetch_sub(1u, std::memory_order_relaxed);
|
||||
image->tick_accessed_last = m_context.GetCommandScheduler().CurrentTick();
|
||||
}
|
||||
}
|
||||
|
||||
Image* ImageHeap::ImageOf(const Element& element) const {
|
||||
auto* image = m_context.GetTextureCache().m_slot_images.try_get(element.binding.image_id);
|
||||
return image != nullptr && image->serial == element.serial ? image : nullptr;
|
||||
}
|
||||
|
||||
uint32_t ImageHeap::Allocate() {
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
while (!m_retired.empty() && scheduler.IsFree(m_retired.front().first)) {
|
||||
m_free.push_back(m_retired.front().second);
|
||||
m_retired.pop_front();
|
||||
}
|
||||
if (m_free.empty()) {
|
||||
if (m_elements.size() == IR::ImageHeapCapacity) {
|
||||
EXIT("image heaps exceed %u resident images\n", IR::ImageHeapCapacity);
|
||||
}
|
||||
m_elements.emplace_back();
|
||||
return static_cast<uint32_t>(m_elements.size() - 1u);
|
||||
}
|
||||
const auto index = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_elements[index] = {};
|
||||
return index;
|
||||
}
|
||||
|
||||
void ImageHeap::Retire(View& view) {
|
||||
if (view.released.empty()) return;
|
||||
// A retired index is reused once its tick ends, so no held entry may keep it.
|
||||
std::erase_if(view.held, [&](uint32_t index) { return m_elements[index].refs == 0u; });
|
||||
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||
view.released.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Resize(Memory& memory, uint64_t size) {
|
||||
{
|
||||
std::scoped_lock lock {m_mutex};
|
||||
Unwatch(memory);
|
||||
memory.size = size;
|
||||
// The next refresh rereads the whole table.
|
||||
memory.dirty_pages.assign((Common::AlignUp(memory.base + size, TRACKER_PAGE_SIZE) -
|
||||
Common::AlignDown(memory.base, TRACKER_PAGE_SIZE)) /
|
||||
TRACKER_PAGE_SIZE,
|
||||
uint8_t {1});
|
||||
}
|
||||
memory.words.resize(size / 4u);
|
||||
memory.dead.resize(size / 4u);
|
||||
auto& scheduler = m_context.GetCommandScheduler();
|
||||
for (auto& view: memory.views) {
|
||||
const auto count = std::min(view->limit, Probes(size, view->first, view->step));
|
||||
for (auto probe = count; probe < view->count; ++probe) {
|
||||
Assign(*view, (view->first + probe * view->step) / 4u, 0u);
|
||||
}
|
||||
view->count = count;
|
||||
const auto slots = view->slots.size();
|
||||
view->slots.resize(size / 4u + 1u);
|
||||
view->dirty_end = std::min(view->dirty_end, view->slots.size());
|
||||
view->dirty_begin = std::min(view->dirty_begin, view->dirty_end);
|
||||
if (view->slots.size() * sizeof(uint32_t) > view->buffer->Size()) {
|
||||
if (scheduler.Active()) {
|
||||
scheduler.DeferOperation([buffer = std::move(view->buffer)] {});
|
||||
}
|
||||
view->buffer = SlotBuffer(m_context, size);
|
||||
Widen(view->dirty_begin, view->dirty_end, 0u, view->slots.size());
|
||||
} else if (slots < view->slots.size()) {
|
||||
// Device words past the old end may hold stale slots.
|
||||
Widen(view->dirty_begin, view->dirty_end, slots, view->slots.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageHeap::Rebind(RenderExecutor& executor) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto& textures = m_context.GetTextureCache();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
const auto serial = Image::serials.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
bool stale = false;
|
||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||
if (view->resolve_epoch != epoch) {
|
||||
view->resolve_epoch = epoch;
|
||||
for (const auto& [value, index]: view->elements) {
|
||||
const auto& binding = m_elements[index].binding;
|
||||
if (!binding.image_id) continue;
|
||||
const auto* image = ImageOf(m_elements[index]);
|
||||
if (image == nullptr || !image->registered || image->depth_id ||
|
||||
image->binding.needs_rebind) {
|
||||
mark(index);
|
||||
} else {
|
||||
(void)textures.FindTexture(binding.image_id, binding.desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Discovery applies the color metadata clears written since the last draw.
|
||||
std::erase_if(view->dcc, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
if (element.refs == 0u || element.stale) return true;
|
||||
if (executor.ResolveTexture(IR::ImageHeapResource(view->profile, element.kind),
|
||||
element.value).image_id == element.binding.image_id) {
|
||||
return false;
|
||||
}
|
||||
mark(index);
|
||||
return true;
|
||||
});
|
||||
// Held elements retry once an image is created, such as a render target of their extent.
|
||||
if (view->held_serial != serial) {
|
||||
view->held_serial = serial;
|
||||
std::erase_if(view->held, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
if (element.refs == 0u || element.stale) return true;
|
||||
const auto binding = executor.ResolveTexture(
|
||||
IR::ImageHeapResource(view->profile, element.kind), element.value);
|
||||
if (binding.image_id) mark(index);
|
||||
// An unmapped texture stays null.
|
||||
return binding.image_id || binding.desc.info.data.Empty();
|
||||
});
|
||||
}
|
||||
if (stale) {
|
||||
// A changed image needs a new element; pending draws still read the old one. The new
|
||||
// element keeps the old value, since a view takes table changes only when acquired.
|
||||
std::erase_if(view->elements,
|
||||
[&](const auto& item) { return m_elements[item.second].stale; });
|
||||
for (uint64_t probe = 0; probe < view->count; ++probe) {
|
||||
const auto slot = (view->first + probe * view->step) / 4u;
|
||||
const auto& element = m_elements[view->slots[slot] & ElementMask];
|
||||
if (!element.stale) continue;
|
||||
const auto value = element.value; // Intern may grow m_elements.
|
||||
Assign(*view, slot, Intern(*view, element.kind, value));
|
||||
}
|
||||
}
|
||||
for (const auto index: view->pending) {
|
||||
if (m_elements[index].refs == 0u) continue;
|
||||
auto binding = executor.ResolveTexture(
|
||||
IR::ImageHeapResource(view->profile, m_elements[index].kind),
|
||||
m_elements[index].value);
|
||||
if (binding.image_id) {
|
||||
auto& image = textures.GetImage(binding.image_id);
|
||||
image.heap_refs.fetch_add(1u, std::memory_order_relaxed);
|
||||
m_elements[index].serial = image.serial;
|
||||
binding.layout = image.ReadLayout();
|
||||
binding.image_view = textures.FindTexture(binding.image_id, binding.desc);
|
||||
if (binding.desc.info.metadata.kind == ImageMetadataKind::Dcc) {
|
||||
view->dcc.push_back(index);
|
||||
}
|
||||
view->away.push_back(index);
|
||||
} else if (!binding.desc.info.data.Empty()) {
|
||||
view->held.push_back(index);
|
||||
}
|
||||
m_elements[index].binding = std::move(binding);
|
||||
m_writes.push_back(index);
|
||||
}
|
||||
view->pending.clear();
|
||||
}
|
||||
WriteDescriptors();
|
||||
}
|
||||
|
||||
void ImageHeap::WriteDescriptors() {
|
||||
if (m_writes.empty()) return;
|
||||
std::vector<vk::DescriptorImageInfo> infos;
|
||||
std::vector<vk::WriteDescriptorSet> writes(m_writes.size());
|
||||
infos.reserve(m_writes.size());
|
||||
for (size_t write = 0; write < m_writes.size(); ++write) {
|
||||
const auto& binding = m_elements[m_writes[write]].binding;
|
||||
infos.push_back({nullptr, binding.image_view, binding.layout});
|
||||
writes[write].dstSet = m_set;
|
||||
writes[write].dstArrayElement = m_writes[write];
|
||||
writes[write].descriptorCount = 1;
|
||||
writes[write].descriptorType = vk::DescriptorType::eSampledImage;
|
||||
writes[write].pImageInfo = &infos.back();
|
||||
}
|
||||
m_context.GetGraphics().device.updateDescriptorSets(static_cast<uint32_t>(writes.size()),
|
||||
writes.data(), 0, nullptr);
|
||||
m_writes.clear();
|
||||
}
|
||||
|
||||
void ImageHeap::Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point,
|
||||
vk::PipelineLayout layout) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||
for (auto* view: m_active) {
|
||||
if (view->dirty_begin != view->dirty_end) {
|
||||
const auto slots = std::span(view->slots).subspan(view->dirty_begin,
|
||||
view->dirty_end - view->dirty_begin);
|
||||
auto& stream = m_context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
|
||||
const auto offset = stream.Copy(slots.data(), slots.size_bytes(), sizeof(uint32_t));
|
||||
view->buffer->CopyFrom(buffer, stream, offset, view->dirty_begin * sizeof(uint32_t),
|
||||
slots.size_bytes());
|
||||
view->dirty_begin = view->dirty_end = 0;
|
||||
}
|
||||
Retire(*view);
|
||||
if (view->layout_epoch != epoch) {
|
||||
view->layout_epoch = epoch;
|
||||
view->away.clear();
|
||||
for (const auto& [value, index]: view->elements) view->away.push_back(index);
|
||||
}
|
||||
// This draw's own bindings own the layouts of its attachments and storage images. An image
|
||||
// this draw's discovery replaced lives until the tick ends, and its slots still select it.
|
||||
std::erase_if(view->away, [&](uint32_t index) {
|
||||
const auto& element = m_elements[index];
|
||||
auto* image = ImageOf(element);
|
||||
if (element.refs == 0u || image == nullptr) return true;
|
||||
if (image->binding.is_target || image->binding.shader_write ||
|
||||
image->binding.force_general) {
|
||||
return false;
|
||||
}
|
||||
const auto& view_info = element.binding.desc.view_info;
|
||||
image->Transit(image->ReadLayout(), vk::AccessFlagBits2::eShaderRead,
|
||||
ImageSubresourceRange {view_info.base_level, view_info.level_count,
|
||||
view_info.base_layer, view_info.layer_count},
|
||||
buffer.Handle());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
m_active.clear();
|
||||
buffer.Handle().bindDescriptorSets(bind_point, layout, IR::ImageHeapSet, 1, &m_set, 0,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
|
||||
// Descriptors of textures in the range stay null even once it is mapped again.
|
||||
if (!m_unmapped.empty() && m_unmapped.back().End() == address) {
|
||||
m_unmapped.back().size += size;
|
||||
} else {
|
||||
m_unmapped.push_back({address, size});
|
||||
}
|
||||
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)] {});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,145 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptors.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class Buffer;
|
||||
class CommandBuffer;
|
||||
class PageManager;
|
||||
class RenderContext;
|
||||
class RenderExecutor;
|
||||
struct ImageHeapTestAccess;
|
||||
|
||||
// Guest image tables that shaders index by GPU-computed offsets, kept resident in one bindless
|
||||
// sampled-image array. A table is keyed by its base and follows its descriptor's size. Each view
|
||||
// of a table owns device slot words, one per table dword, that select an array element. CPU
|
||||
// writes to a table fault in its snapshot, which each draw refreshes once; a view takes the
|
||||
// changes when a draw first acquires it. Changes to the images behind elements bump
|
||||
// Image::heap_epoch.
|
||||
class ImageHeap final: public ShaderRecompiler::IR::ImageHeapSource {
|
||||
public:
|
||||
ImageHeap(RenderContext& context, PageManager& page_manager);
|
||||
~ImageHeap();
|
||||
KYTY_CLASS_NO_COPY(ImageHeap);
|
||||
|
||||
// Starts a draw or dispatch, before its programs acquire views.
|
||||
void BeginDraw() {
|
||||
++m_draw;
|
||||
Bury();
|
||||
}
|
||||
bool Acquire(const ShaderRecompiler::IR::ImageHeapRequest& request,
|
||||
ShaderRecompiler::IR::ImageHeapView& result) override;
|
||||
// Image discovery: resolves new elements and rediscovers changed images.
|
||||
void Rebind(RenderExecutor& executor);
|
||||
// Recording: uploads slots, moves resident images to their read layout and binds the array.
|
||||
void Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point, vk::PipelineLayout layout);
|
||||
// CPU write fault, on any thread.
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
|
||||
private:
|
||||
using DescriptorValue = ShaderRecompiler::IR::DescriptorValue;
|
||||
|
||||
struct DescriptorHash {
|
||||
size_t operator()(const DescriptorValue& value) const noexcept;
|
||||
};
|
||||
|
||||
struct Element {
|
||||
DescriptorValue value;
|
||||
TextureBinding binding;
|
||||
uint32_t kind = 0;
|
||||
uint32_t refs = 0; // Slots of the owning view that select it.
|
||||
bool stale = false;
|
||||
uint64_t serial = 0; // Image::serial of binding.image_id.
|
||||
};
|
||||
|
||||
struct Memory;
|
||||
|
||||
struct View {
|
||||
Memory& memory;
|
||||
uint64_t step = 0;
|
||||
uint64_t first = 0;
|
||||
uint64_t count = 0;
|
||||
uint64_t limit = 0; // Probes a key domain allows, if fewer.
|
||||
ShaderRecompiler::IR::ImageResource profile;
|
||||
std::vector<uint32_t> slots; // Plus a trailing null slot.
|
||||
std::vector<uint32_t> kind_keys;
|
||||
std::vector<DescriptorValue> kinds; // One representative per kind_keys entry.
|
||||
std::unordered_map<DescriptorValue, uint32_t, DescriptorHash> elements;
|
||||
std::vector<uint32_t> pending; // Not yet resolved.
|
||||
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
||||
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
||||
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
||||
std::vector<uint32_t> held; // Another extent holds their address.
|
||||
std::unique_ptr<Buffer> buffer;
|
||||
size_t dirty_begin = 0; // Slots to upload.
|
||||
size_t dirty_end = 0;
|
||||
size_t changed_begin = 0; // Table bytes changed since it was probed.
|
||||
size_t changed_end = 0;
|
||||
uint64_t resolve_epoch = 0;
|
||||
uint64_t layout_epoch = 0;
|
||||
uint64_t held_serial = 0; // Image::serials when held was last retried.
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
uint64_t base = 0;
|
||||
uint64_t size = 0;
|
||||
std::vector<uint32_t> words;
|
||||
std::vector<uint8_t> dirty_pages; // Unwatched, so unread since a write.
|
||||
std::vector<uint64_t> dead; // Per word, a buried descriptor's hash.
|
||||
uint64_t draw = 0; // Last draw that refreshed it.
|
||||
std::vector<std::unique_ptr<View>> views;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool Refresh(Memory& memory);
|
||||
void MarkDirty(Memory& memory, size_t begin, size_t end);
|
||||
template <bool track>
|
||||
void Watch(const Memory& memory, size_t begin, size_t end);
|
||||
void Unwatch(const Memory& memory);
|
||||
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
|
||||
void UpdateProbe(View& view, uint64_t probe);
|
||||
void Assign(View& view, size_t slot, uint32_t entry);
|
||||
void Bury();
|
||||
uint32_t Intern(View& view, uint32_t kind, const DescriptorValue& value);
|
||||
void Release(View& view, uint32_t entry);
|
||||
void Unreference(Element& element);
|
||||
Image* ImageOf(const Element& element) const;
|
||||
uint32_t Allocate();
|
||||
void Retire(View& view);
|
||||
void Resize(Memory& memory, uint64_t size);
|
||||
void WriteDescriptors();
|
||||
|
||||
RenderContext& m_context;
|
||||
PageManager& m_page_manager;
|
||||
std::mutex m_mutex; // Fault-visible Memory state and m_memories.
|
||||
std::vector<std::unique_ptr<Memory>> m_memories;
|
||||
std::vector<View*> m_active; // Acquired since the last commit.
|
||||
uint64_t m_draw = 1;
|
||||
std::vector<Element> m_elements;
|
||||
std::vector<uint32_t> m_free;
|
||||
std::deque<std::pair<uint64_t, uint32_t>> m_retired; // Tick, element.
|
||||
std::vector<std::pair<size_t, size_t>> m_runs;
|
||||
std::vector<uint32_t> m_scratch;
|
||||
std::vector<uint32_t> m_writes; // Elements whose descriptors changed.
|
||||
std::vector<GuestRange> m_unmapped; // Since the last Bury.
|
||||
vk::DescriptorSetLayout m_layout = nullptr;
|
||||
vk::DescriptorPool m_pool = nullptr;
|
||||
vk::DescriptorSet m_set = nullptr;
|
||||
|
||||
friend struct ImageHeapTestAccess;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEHEAP_H_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,31 +4,48 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/file.h"
|
||||
#include "common/uniqueFunction.h"
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct RenderColorInfo;
|
||||
struct RenderDepthInfo;
|
||||
struct ShaderParams;
|
||||
class CommandBuffer;
|
||||
|
||||
namespace HW {
|
||||
class Context;
|
||||
class Shader;
|
||||
class UserConfig;
|
||||
struct ComputeShaderInfo;
|
||||
} // namespace HW
|
||||
namespace ShaderRecompiler {
|
||||
struct CompileOptions;
|
||||
struct ShaderSource;
|
||||
struct TranslateResult;
|
||||
} // namespace ShaderRecompiler
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
class ImageHeapSource;
|
||||
} // namespace ShaderRecompiler::IR
|
||||
|
||||
namespace ShaderRecompiler::Spirv {
|
||||
struct Profile;
|
||||
} // namespace ShaderRecompiler::Spirv
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
@@ -36,6 +53,8 @@ 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.
|
||||
uint8_t patch_control_points = 0;
|
||||
bool primitive_restart_enable = false;
|
||||
uint32_t samples = 1;
|
||||
bool sample_shading_enable = false;
|
||||
@@ -54,57 +73,108 @@ 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;
|
||||
|
||||
[[nodiscard]] std::span<const ShaderVertexInputInfo> VertexStages() const {
|
||||
return {vertex_info.data(), vertex_info[1].stage ? 3u : 1u};
|
||||
}
|
||||
};
|
||||
|
||||
// Writes the shader store on its own thread, running jobs in the order they were posted.
|
||||
class ShaderStoreWriter {
|
||||
public:
|
||||
// Keeps the first `kept` bytes of the store at `path`, or starts it over with `header`. The
|
||||
// thread runs only if that succeeded.
|
||||
void Open(const std::filesystem::path& path, std::span<const uint8_t> header, uint64_t kept);
|
||||
explicit operator bool() const { return m_thread.joinable(); }
|
||||
// Compresses `record` and appends it on the store thread, in call order.
|
||||
void Append(std::vector<uint8_t>&& record);
|
||||
|
||||
private:
|
||||
friend struct PipelineCacheTestAccess;
|
||||
|
||||
void Post(Common::UniqueFunction<void>&& job);
|
||||
|
||||
Common::File m_file;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable_any m_ready;
|
||||
std::queue<Common::UniqueFunction<void>> m_jobs;
|
||||
// Declared last, so it drains the queue and joins before the rest is destroyed.
|
||||
std::jthread m_thread;
|
||||
};
|
||||
|
||||
// The owning renderer serializes access, including saves while the GPU is running.
|
||||
class PipelineCache {
|
||||
public:
|
||||
explicit PipelineCache(GraphicContext& graphics);
|
||||
// Without a shader store path nothing is kept across runs.
|
||||
PipelineCache(GraphicContext& graphics, ShaderRecompiler::IR::ImageHeapSource* image_heap,
|
||||
const std::filesystem::path& shader_store = {});
|
||||
~PipelineCache();
|
||||
KYTY_CLASS_NO_COPY(PipelineCache);
|
||||
void Save();
|
||||
@@ -114,89 +184,109 @@ public:
|
||||
vk::Pipeline pipeline = nullptr;
|
||||
vk::DescriptorSetLayout descriptor_set_layout = nullptr;
|
||||
bool uses_push_descriptors = false;
|
||||
bool uses_image_heap = false;
|
||||
};
|
||||
|
||||
struct GraphicsPrograms {
|
||||
std::array<ShaderProgram, 3> vertex;
|
||||
ShaderProgram pixel;
|
||||
// Reads the shaders and registers bound in `command` into the state's shader stages.
|
||||
void GetGraphicsPrograms(const CommandBuffer& command, DrawRenderState& state);
|
||||
// Null skips the dispatch.
|
||||
const ShaderModule* GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
[[nodiscard]] uint32_t VertexStageCount() const { return vertex[1] ? 3u : 1u; }
|
||||
};
|
||||
|
||||
GraphicsPrograms
|
||||
GetGraphicsPrograms(const HW::VertexShaderInfo& vertex_regs,
|
||||
const HW::PixelShaderInfo& pixel_regs, const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context, const HW::UserConfig& user_config,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
bool pixel_active, std::array<ShaderVertexInputInfo, 3>& vertex_info,
|
||||
ShaderPixelInputInfo& pixel_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
Pipeline& GetGraphicsPipeline(std::span<const RenderColorInfo> colors,
|
||||
const RenderDepthInfo& depth,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
CommandBuffer& command, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::PrimitiveTopology topology, bool primitive_restart_enable,
|
||||
const GraphicsPrograms& programs);
|
||||
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
Pipeline& GetGraphicsPipeline(const CommandBuffer& command, const DrawRenderState& state);
|
||||
Pipeline& GetComputePipeline(const ShaderComputeInputInfo& input_info);
|
||||
|
||||
private:
|
||||
struct ProgramCache;
|
||||
friend struct PipelineCacheTestAccess;
|
||||
|
||||
struct GraphicsPipelineKey {
|
||||
PipelineRenderingState rendering;
|
||||
std::array<uint64_t, 3> vertex_shader_ids {};
|
||||
uint64_t ps_shader_id = 0;
|
||||
PipelineVertexInputState vertex_input;
|
||||
PipelineStaticParameters static_params;
|
||||
struct Program {
|
||||
std::unique_ptr<ShaderRecompiler::ShaderSource> call_source;
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
std::vector<ShaderModule> modules;
|
||||
bool skip_dispatch = false;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
return rendering == other.rendering && vertex_shader_ids == other.vertex_shader_ids &&
|
||||
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
|
||||
static_params == other.static_params;
|
||||
}
|
||||
[[nodiscard]] std::span<const uint32_t> FunctionCode() const;
|
||||
bool RefreshCallSource(const ShaderRecompiler::IR::SrtRuntime& runtime);
|
||||
template <bool HasCalls>
|
||||
ShaderModule* FindModule(const ShaderRecompiler::IR::SrtRuntime& runtime,
|
||||
uint32_t push_data_cursor);
|
||||
};
|
||||
|
||||
struct PipelineKeyHash {
|
||||
static void Mix(std::size_t& hash, std::size_t value) {
|
||||
hash ^= value + static_cast<std::size_t>(0x9e3779b97f4a7c15ull) + (hash << 6u) +
|
||||
(hash >> 2u);
|
||||
}
|
||||
struct ProgramKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
uint32_t user_data_count = 0;
|
||||
std::vector<uint32_t> static_state;
|
||||
|
||||
bool operator==(const ProgramKey&) const = default;
|
||||
};
|
||||
|
||||
// Variants of one source share a bucket; ProgramKey equality compares the state.
|
||||
struct ProgramKeyHash {
|
||||
std::size_t operator()(const ProgramKey& key) const { return key.hash; }
|
||||
};
|
||||
|
||||
struct GraphicsPipelineKeyHash {
|
||||
std::size_t operator()(const GraphicsPipelineKey& key) const;
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
std::unique_ptr<ProgramCache> m_program_cache;
|
||||
using ModuleMap = std::unordered_map<uint64_t, const ShaderModule*>;
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
ShaderRecompiler::IR::ImageHeapSource* m_image_heap;
|
||||
std::unordered_map<ProgramKey, Program, ProgramKeyHash> m_program_cache;
|
||||
// Modules compiled per stage.
|
||||
std::array<uint32_t, static_cast<size_t>(ShaderType::TessellationEvaluation) + 1> m_compiled {};
|
||||
ProgramKey m_lookup_key;
|
||||
vk::PipelineCache m_driver_cache = nullptr;
|
||||
std::filesystem::path m_driver_cache_path;
|
||||
std::chrono::steady_clock::time_point m_next_driver_cache_save;
|
||||
bool m_driver_cache_dirty = false;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
|
||||
std::unordered_map<GraphicsPipelineKey, Pipeline, GraphicsPipelineKeyHash> m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, Pipeline> m_compute_pipelines;
|
||||
std::unordered_map<uint64_t, ShaderTessellationInputInfo> m_tess_layouts;
|
||||
// Records everything first created; closed without a shader store.
|
||||
ShaderStoreWriter m_store;
|
||||
|
||||
template <typename InputInfo>
|
||||
const ShaderModule* GetProgram(const ShaderParams& params, InputInfo& input_info,
|
||||
uint32_t& push_data_cursor);
|
||||
ShaderModule& CompileModule(Program& program, const ProgramKey& key,
|
||||
ShaderRecompiler::CompileOptions options,
|
||||
ShaderRecompiler::TranslateResult translated,
|
||||
uint32_t push_data_cursor);
|
||||
void InitializeDriverCache();
|
||||
void CheckpointDriverCache();
|
||||
|
||||
// pipelineSerialization.cpp
|
||||
void WarmUp(const std::filesystem::path& path);
|
||||
bool LoadRecord(std::span<const uint8_t>& records, std::vector<GraphicsPipelineKey>& pipelines);
|
||||
void StoreModule(const ProgramKey& key, const Program& program, const ShaderModule& module,
|
||||
const std::vector<uint32_t>& spirv);
|
||||
void StorePipeline(const GraphicsPipelineKey& key);
|
||||
void StoreLayout(uint64_t key, const ShaderTessellationInputInfo& layout);
|
||||
// Every module by id, with id 0 naming an absent stage.
|
||||
[[nodiscard]] ModuleMap MapModules() const;
|
||||
// False when a stage id of the key is not in `modules`.
|
||||
bool CreateGraphicsPipeline(Pipeline& pipeline, const GraphicsPipelineKey& key,
|
||||
const ModuleMap& modules);
|
||||
};
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
// The shader store of the running title; empty unless --shader-cache is on in a Release build
|
||||
// without debug dumps, which change the SPIR-V.
|
||||
std::filesystem::path ShaderStorePath();
|
||||
// Host capabilities that change the emitted SPIR-V.
|
||||
ShaderRecompiler::Spirv::Profile SpirvProfile(const GraphicContext& graphics);
|
||||
|
||||
// Stages are indexed like GraphicsPipelineKey::stage_ids.
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const PipelineVertexInputState& vertex_input,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
const PipelineCache::GraphicsPrograms& programs,
|
||||
const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache);
|
||||
const GraphicsPipelineKey& key,
|
||||
const std::array<const ShaderModule*, 4>& stages,
|
||||
vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
const ShaderModule& module, vk::PipelineCache driver_cache);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/ir/Serialize.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <ranges>
|
||||
#include <xxhash.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
// The shader store: a header, then zstd-compressed records of everything a run created first.
|
||||
// Records are only appended, so a store cut short by a crash keeps every complete record.
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
using ShaderRecompiler::IR::IoAll;
|
||||
using ShaderRecompiler::IR::Reader;
|
||||
using ShaderRecompiler::IR::Writer;
|
||||
|
||||
enum class Record : uint8_t { Module, GraphicsPipeline, TessellationLayout };
|
||||
|
||||
// Precedes each record's zstd frame.
|
||||
struct Frame {
|
||||
uint32_t compressed_size = 0;
|
||||
uint32_t raw_size = 0;
|
||||
uint64_t checksum = 0; // XXH3 of the compressed bytes.
|
||||
};
|
||||
|
||||
// Changes with every build, so records never meet code that encodes them differently. Zero when
|
||||
// the executable cannot be found.
|
||||
uint64_t BuildStamp() {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
std::array<wchar_t, 32768> name {};
|
||||
GetModuleFileNameW(nullptr, name.data(), static_cast<DWORD>(name.size()));
|
||||
const std::filesystem::path path = name.data();
|
||||
#elif defined(__APPLE__)
|
||||
std::array<char, 4096> name {};
|
||||
auto size = static_cast<uint32_t>(name.size());
|
||||
_NSGetExecutablePath(name.data(), &size);
|
||||
const std::filesystem::path path = name.data();
|
||||
#else
|
||||
const std::filesystem::path path = "/proc/self/exe";
|
||||
#endif
|
||||
std::error_code error;
|
||||
const auto time = std::filesystem::last_write_time(path, error).time_since_epoch().count();
|
||||
return error ? 0 : static_cast<uint64_t>(time);
|
||||
}
|
||||
|
||||
// What every record depends on besides its own fields; empty when the build is unknown.
|
||||
std::vector<uint8_t> StoreHeader(const GraphicContext& graphics) {
|
||||
const auto stamp = BuildStamp();
|
||||
if (stamp == 0) {
|
||||
return {};
|
||||
}
|
||||
Writer header;
|
||||
IoAll(header, stamp, SpirvProfile(graphics),
|
||||
graphics.GetPhysicalDeviceProperties().limits.maxComputeSharedMemorySize);
|
||||
return std::move(header.bytes);
|
||||
}
|
||||
|
||||
// Record fields, shared by writing and reading.
|
||||
template <class S, class Key> void IoKey(S& io, Key& key) {
|
||||
auto& [stage, hash, user_data_count, static_state] = key;
|
||||
IoAll(io, stage, hash, user_data_count, static_state);
|
||||
}
|
||||
|
||||
template <class S, class Module> void IoModule(S& io, Module& stage) {
|
||||
// The handle comes from the SPIR-V; stored programs have no function code.
|
||||
auto& [id, handle, info, specialization, function_code, vertex_formats, rect_parameters] = stage;
|
||||
IoAll(io, id, info, specialization, vertex_formats, rect_parameters);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShaderStoreWriter::Open(const std::filesystem::path& path, std::span<const uint8_t> header,
|
||||
uint64_t kept) {
|
||||
Common::File::CreateDirectories(path.parent_path());
|
||||
if (kept == 0) {
|
||||
if (m_file.Create(path)) {
|
||||
m_file.Write(header.data(), static_cast<uint32_t>(header.size()));
|
||||
}
|
||||
} else if (m_file.Open(path, Common::File::Mode::ReadWrite)) {
|
||||
m_file.Truncate(kept);
|
||||
m_file.Seek(kept);
|
||||
}
|
||||
if (m_file.IsInvalid()) {
|
||||
return;
|
||||
}
|
||||
m_thread = std::jthread([this](std::stop_token stop) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
// Once stop is requested the wait returns false only when the queue is empty.
|
||||
while (m_ready.wait(lock, stop, [this] { return !m_jobs.empty(); })) {
|
||||
const auto job = std::move(m_jobs.front());
|
||||
m_jobs.pop();
|
||||
lock.unlock();
|
||||
job();
|
||||
lock.lock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ShaderStoreWriter::Append(std::vector<uint8_t>&& record) {
|
||||
Post([this, record = std::move(record)] {
|
||||
std::vector<uint8_t> bytes(sizeof(Frame) + ZSTD_compressBound(record.size()));
|
||||
const auto size = ZSTD_compress(bytes.data() + sizeof(Frame), bytes.size() - sizeof(Frame),
|
||||
record.data(), record.size(), 3);
|
||||
EXIT_IF(ZSTD_isError(size));
|
||||
const Frame frame {.compressed_size = static_cast<uint32_t>(size),
|
||||
.raw_size = static_cast<uint32_t>(record.size()),
|
||||
.checksum = XXH3_64bits(bytes.data() + sizeof(Frame), size)};
|
||||
std::memcpy(bytes.data(), &frame, sizeof(frame));
|
||||
// A failed write leaves a partial record; the next run drops it and everything after it.
|
||||
m_file.Write(bytes.data(), static_cast<uint32_t>(sizeof(Frame) + size));
|
||||
});
|
||||
}
|
||||
|
||||
void ShaderStoreWriter::Post(Common::UniqueFunction<void>&& job) {
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_jobs.push(std::move(job));
|
||||
}
|
||||
m_ready.notify_one();
|
||||
}
|
||||
|
||||
void PipelineCache::WarmUp(const std::filesystem::path& path) {
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto header = StoreHeader(m_graphics);
|
||||
if (header.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
std::vector<uint8_t> bytes;
|
||||
if (Common::File file;
|
||||
Common::File::IsFileExisting(path) && file.Open(path, Common::File::Mode::Read)) {
|
||||
bytes.resize(file.Size());
|
||||
uint32_t read = 0;
|
||||
file.Read(bytes.data(), static_cast<uint32_t>(bytes.size()), &read);
|
||||
bytes.resize(read);
|
||||
}
|
||||
Log::WriteToConsoleAndLog(
|
||||
fmt::format("Shader cache: compiling shaders and pipelines from {} ({:.1f} MB)\n",
|
||||
Common::PathToString(path), static_cast<double>(bytes.size()) / 1'000'000.0));
|
||||
std::vector<GraphicsPipelineKey> pipelines;
|
||||
auto records = std::span<const uint8_t>(bytes);
|
||||
const bool reusable =
|
||||
records.size() >= header.size() && std::ranges::equal(records.first(header.size()), header);
|
||||
if (reusable) {
|
||||
records = records.subspan(header.size());
|
||||
while (LoadRecord(records, pipelines)) {
|
||||
}
|
||||
}
|
||||
// A damaged record ends the store; the records before it stay.
|
||||
m_store.Open(path, header, reusable ? bytes.size() - records.size() : 0);
|
||||
|
||||
const auto modules = MapModules();
|
||||
for (const auto& key: pipelines) {
|
||||
Pipeline pipeline;
|
||||
if (CreateGraphicsPipeline(pipeline, key, modules)) {
|
||||
m_graphics_pipelines.emplace(key, pipeline);
|
||||
}
|
||||
}
|
||||
// A compute pipeline depends only on its module.
|
||||
for (const auto* module: modules | std::views::values) {
|
||||
if (module != nullptr && module->info.stage == ShaderType::Compute) {
|
||||
CreatePipelineInternal(m_graphics, m_compute_pipelines[module->id], *module,
|
||||
m_driver_cache);
|
||||
}
|
||||
}
|
||||
if (!m_graphics_pipelines.empty() || !m_compute_pipelines.empty()) {
|
||||
CheckpointDriverCache();
|
||||
}
|
||||
Log::WriteToConsoleAndLog(fmt::format(
|
||||
"Shader cache: loaded {} programs, {} modules, {} pipelines in {} ms\n",
|
||||
m_program_cache.size(), modules.size() - 1,
|
||||
m_graphics_pipelines.size() + m_compute_pipelines.size(),
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
|
||||
start)
|
||||
.count()));
|
||||
}
|
||||
|
||||
bool PipelineCache::LoadRecord(std::span<const uint8_t>& records,
|
||||
std::vector<GraphicsPipelineKey>& pipelines) {
|
||||
Frame frame;
|
||||
if (records.size() < sizeof(frame)) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(&frame, records.data(), sizeof(frame));
|
||||
if (records.size() - sizeof(frame) < frame.compressed_size) {
|
||||
return false;
|
||||
}
|
||||
const auto compressed = records.subspan(sizeof(frame), frame.compressed_size);
|
||||
if (XXH3_64bits(compressed.data(), compressed.size()) != frame.checksum ||
|
||||
ZSTD_getFrameContentSize(compressed.data(), compressed.size()) != frame.raw_size) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> raw(frame.raw_size);
|
||||
if (ZSTD_decompress(raw.data(), raw.size(), compressed.data(), compressed.size()) != raw.size()) {
|
||||
return false;
|
||||
}
|
||||
Reader reader {raw};
|
||||
auto type = Record::Module;
|
||||
const auto complete = [&] { return reader.ok && reader.bytes.empty(); };
|
||||
IoAll(reader, type);
|
||||
switch (type) {
|
||||
case Record::Module: {
|
||||
ProgramKey key;
|
||||
std::vector<uint8_t> plan;
|
||||
ShaderModule module;
|
||||
std::vector<uint32_t> spirv;
|
||||
IoKey(reader, key);
|
||||
IoAll(reader, plan);
|
||||
IoModule(reader, module);
|
||||
IoAll(reader, spirv);
|
||||
if (!complete()) {
|
||||
return false;
|
||||
}
|
||||
const auto [program, inserted] = m_program_cache.try_emplace(std::move(key));
|
||||
if (inserted &&
|
||||
!ShaderRecompiler::IR::LoadResourcePlan(plan, program->second.resource_plan)) {
|
||||
m_program_cache.erase(program);
|
||||
return false;
|
||||
}
|
||||
module.module = CompileSPV(spirv, m_graphics.device);
|
||||
if (module.module == nullptr) {
|
||||
return false;
|
||||
}
|
||||
program->second.modules.push_back(std::move(module));
|
||||
break;
|
||||
}
|
||||
case Record::GraphicsPipeline: {
|
||||
GraphicsPipelineKey key;
|
||||
reader.Raw(&key, sizeof(key));
|
||||
if (!complete()) {
|
||||
return false;
|
||||
}
|
||||
pipelines.push_back(key);
|
||||
break;
|
||||
}
|
||||
case Record::TessellationLayout: {
|
||||
uint64_t key = 0;
|
||||
ShaderTessellationInputInfo layout;
|
||||
IoAll(reader, key, layout);
|
||||
if (!complete()) {
|
||||
return false;
|
||||
}
|
||||
m_tess_layouts.try_emplace(key, layout);
|
||||
break;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
records = records.subspan(sizeof(frame) + compressed.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Records are encoded here, since programs and modules change later.
|
||||
void PipelineCache::StoreModule(const ProgramKey& key, const Program& program,
|
||||
const ShaderModule& module, const std::vector<uint32_t>& spirv) {
|
||||
if (!m_store) {
|
||||
return;
|
||||
}
|
||||
Writer record;
|
||||
IoAll(record, Record::Module);
|
||||
IoKey(record, key);
|
||||
IoAll(record, ShaderRecompiler::IR::SaveResourcePlan(program.resource_plan));
|
||||
IoModule(record, module);
|
||||
IoAll(record, spirv);
|
||||
m_store.Append(std::move(record.bytes));
|
||||
}
|
||||
|
||||
void PipelineCache::StorePipeline(const GraphicsPipelineKey& key) {
|
||||
if (!m_store) {
|
||||
return;
|
||||
}
|
||||
Writer record;
|
||||
IoAll(record, Record::GraphicsPipeline);
|
||||
record.Raw(&key, sizeof(key));
|
||||
m_store.Append(std::move(record.bytes));
|
||||
}
|
||||
|
||||
void PipelineCache::StoreLayout(uint64_t key, const ShaderTessellationInputInfo& layout) {
|
||||
if (!m_store) {
|
||||
return;
|
||||
}
|
||||
Writer record;
|
||||
IoAll(record, Record::TessellationLayout, key, layout);
|
||||
m_store.Append(std::move(record.bytes));
|
||||
}
|
||||
|
||||
PipelineCache::ModuleMap PipelineCache::MapModules() const {
|
||||
ModuleMap modules {{0, nullptr}};
|
||||
for (const auto& program: m_program_cache | std::views::values) {
|
||||
for (const auto& module: program.modules) {
|
||||
modules.emplace(module.id, &module);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
bool PipelineCache::CreateGraphicsPipeline(Pipeline& pipeline, const GraphicsPipelineKey& key,
|
||||
const ModuleMap& modules) {
|
||||
std::array<const ShaderModule*, 4> stages {};
|
||||
for (size_t i = 0; i < stages.size(); i++) {
|
||||
const auto found = modules.find(key.stage_ids[i]);
|
||||
if (found == modules.end()) {
|
||||
return false;
|
||||
}
|
||||
stages[i] = found->second;
|
||||
}
|
||||
CreatePipelineInternal(m_graphics, pipeline, key, stages, m_driver_cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
@@ -52,6 +53,7 @@ vk::MemoryBarrier MakeShaderWriteDependency() {
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
|
||||
vk::AccessFlagBits::eVertexAttributeRead |
|
||||
vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead |
|
||||
vk::AccessFlagBits::eIndirectCommandRead |
|
||||
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
@@ -89,7 +91,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& program = runtime.module->info;
|
||||
const auto& resources = *runtime.resources;
|
||||
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
|
||||
// Address writes reach memory outside the bound buffer descriptors.
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/rectListShader.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
@@ -21,124 +20,11 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
// IDK: maybe we can remove it?
|
||||
constexpr uint8_t kTemporaryVertexAttribFormat113 =
|
||||
static_cast<uint8_t>(Prospero::VertexAttribFormat::k16_16SInt);
|
||||
constexpr uint32_t kTemporaryPs5BufferFormat121 = 121u;
|
||||
|
||||
static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_components) {
|
||||
if (used_components == 0 || used_components >= size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case vk::Format::eR32G32B32A32Sfloat:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR32Sfloat; break;
|
||||
case 2: format = vk::Format::eR32G32Sfloat; break;
|
||||
case 3: format = vk::Format::eR32G32B32Sfloat; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR32G32B32Sfloat:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR32Sfloat; break;
|
||||
case 2: format = vk::Format::eR32G32Sfloat; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR16G16B16A16Sfloat:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR16Sfloat; break;
|
||||
case 2: format = vk::Format::eR16G16Sfloat; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR8Unorm; break;
|
||||
case 2: format = vk::Format::eR8G8Unorm; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
case vk::Format::eR8G8B8A8Snorm:
|
||||
if (used_components != 2) {
|
||||
return false;
|
||||
}
|
||||
format = vk::Format::eR8G8Snorm;
|
||||
size = 2;
|
||||
return true;
|
||||
case vk::Format::eR8G8B8A8Uint:
|
||||
switch (used_components) {
|
||||
case 1: format = vk::Format::eR8Uint; break;
|
||||
case 2: format = vk::Format::eR8G8Uint; break;
|
||||
default: return false;
|
||||
}
|
||||
size = used_components;
|
||||
return true;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format, uint32_t& size,
|
||||
uint32_t used_components) {
|
||||
const auto fmt = res.Format();
|
||||
const auto raw_format = res.RawFormat();
|
||||
if (raw_format == kTemporaryVertexAttribFormat113) {
|
||||
static bool logged_113 = false;
|
||||
if (!logged_113) {
|
||||
LOGF("InputFormat: temporary: accepting invalid PS5 buffer format 113 as "
|
||||
"vk::Format::eR32G32B32A32Sfloat\n");
|
||||
logged_113 = true;
|
||||
}
|
||||
format = vk::Format::eR32G32B32A32Sfloat;
|
||||
size = 4;
|
||||
if (NarrowInputFormat(format, size, used_components)) {
|
||||
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
|
||||
vk::to_string(format).c_str(), used_components);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (raw_format == kTemporaryPs5BufferFormat121) {
|
||||
static bool logged_121 = false;
|
||||
if (!logged_121) {
|
||||
LOGF("InputFormat: accepting PS5 buffer format 121 as vk::Format::eR16G16Sfloat\n");
|
||||
logged_121 = true;
|
||||
}
|
||||
format = vk::Format::eR16G16Sfloat;
|
||||
size = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
format = VulkanFormat(fmt);
|
||||
size = ShaderRecompiler::Format::GetFormatInfo(fmt).component_count;
|
||||
if (format == vk::Format::eUndefined || size == 0) {
|
||||
EXIT("unknown vertex format: fmt = %u\n", raw_format);
|
||||
}
|
||||
|
||||
if (NarrowInputFormat(format, size, used_components)) {
|
||||
static std::atomic<uint64_t> log_count = 0;
|
||||
auto log_id = log_count.fetch_add(1);
|
||||
if (log_id < 32) {
|
||||
LOGF("VertexInput: narrowed vertex format to %" PRIu32
|
||||
" component(s) for shader fetch\n",
|
||||
used_components);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static vk::BlendFactor GetBlendFactor(uint32_t factor) {
|
||||
switch (static_cast<Prospero::BlendFactor>(factor)) {
|
||||
case Prospero::BlendFactor::kZero: return vk::BlendFactor::eZero;
|
||||
@@ -242,33 +128,27 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering,
|
||||
const PipelineVertexInputState& vertex_input,
|
||||
std::span<const ShaderVertexInputInfo> vertex_info,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
const PipelineCache::GraphicsPrograms& programs,
|
||||
const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const auto& vs_input_info = vertex_info.front();
|
||||
const auto& vertex_program = programs.vertex[0];
|
||||
const auto& pixel_program = programs.pixel;
|
||||
const bool tessellation = vertex_info.size() == 3;
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(!vertex_program || (ps_active && !pixel_program));
|
||||
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
|
||||
rendering.stencil_format != vk::Format::eUndefined;
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const bool mesh = vs_input_info.stage.program->stage == ShaderType::Mesh;
|
||||
const GraphicsPipelineKey& key,
|
||||
const std::array<const ShaderModule*, 4>& stages,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const auto& rendering = key.rendering;
|
||||
const auto& vertex_input = key.vertex_input;
|
||||
const auto& static_params = key.static_params;
|
||||
const auto& vertex = *stages[0];
|
||||
const auto* pixel = stages[3];
|
||||
const bool with_depth = rendering.depth_format != vk::Format::eUndefined ||
|
||||
rendering.stencil_format != vk::Format::eUndefined;
|
||||
const bool mesh = vertex.info.stage == ShaderType::Mesh;
|
||||
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
|
||||
const bool rect_list =
|
||||
!mesh && !tessellation && static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
const bool rect_list = static_params.patch_control_points != 0 && stages[1] == nullptr;
|
||||
|
||||
vk::ShaderModule tess_control_shader_module = nullptr;
|
||||
vk::ShaderModule tess_eval_shader_module = nullptr;
|
||||
|
||||
if (rect_list) {
|
||||
const auto shaders =
|
||||
BuildRectListShaders(vs_input_info, ps_active ? ps_input_info : nullptr);
|
||||
std::span<const RectListParameter> parameters;
|
||||
if (pixel != nullptr) parameters = pixel->rect_parameters;
|
||||
const auto shaders = BuildRectListShaders(parameters, vertex.info.param_export_mask);
|
||||
tess_control_shader_module = CompileSPV(shaders.control, graphics.device);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done module=%p\n",
|
||||
@@ -287,11 +167,12 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
vk::PipelineShaderStageCreateInfo shader_stages[4] {};
|
||||
uint32_t shader_stage_count = 0;
|
||||
for (uint32_t i = 0; i < vertex_info.size(); i++) {
|
||||
shader_stages[shader_stage_count++] = {.stage =
|
||||
NativeShaderStage(vertex_info[i].logical_stage),
|
||||
.module = programs.vertex[i].module,
|
||||
.pName = "main"};
|
||||
for (const auto* stage: stages) {
|
||||
if (stage != nullptr) {
|
||||
shader_stages[shader_stage_count++] = {.stage = NativeShaderStage(stage->info.stage),
|
||||
.module = stage->module,
|
||||
.pName = "main"};
|
||||
}
|
||||
}
|
||||
if (rect_list) {
|
||||
shader_stages[shader_stage_count++] = {.stage =
|
||||
@@ -303,11 +184,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
.module = tess_eval_shader_module,
|
||||
.pName = "main"};
|
||||
}
|
||||
if (ps_active) {
|
||||
shader_stages[shader_stage_count++] = {.stage = vk::ShaderStageFlagBits::eFragment,
|
||||
.module = pixel_program.module,
|
||||
.pName = "main"};
|
||||
}
|
||||
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX] {};
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX] {};
|
||||
@@ -323,46 +199,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
input_attr[index].binding = vertex_input.attributes[index].binding;
|
||||
input_attr[index].location = index;
|
||||
input_attr[index].offset = vertex_input.attributes[index].offset;
|
||||
|
||||
uint32_t attr_size = 4;
|
||||
const auto registers_num = vs_input_info.resources_dst[index].registers_num;
|
||||
const auto compiled_components =
|
||||
vs_input_info.stage.program->info.vertex_fetch_components[index];
|
||||
const auto used_components =
|
||||
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
|
||||
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
|
||||
static_cast<uint32_t>(used_components));
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (log_id < 128) {
|
||||
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
|
||||
"src_fmt=%u dst=v%u regs=%u"
|
||||
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
|
||||
log_id, index, input_attr[index].binding, input_attr[index].offset,
|
||||
input_desc[input_attr[index].binding].stride,
|
||||
static_cast<int>(input_attr[index].format),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
|
||||
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
|
||||
static_cast<uint32_t>(registers_num), static_cast<uint32_t>(used_components),
|
||||
attr_size, static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
|
||||
}
|
||||
}
|
||||
|
||||
if (vs_input_info.resources[index].OutOfBounds() != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8 "\n",
|
||||
vs_input_info.resources[index].OutOfBounds());
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_IF(registers_num < 1 || registers_num > 4);
|
||||
input_attr[index].format = vertex.vertex_formats[index];
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_info {};
|
||||
@@ -470,23 +307,22 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
vk::ShaderStageFlags graphics_stages = vk::ShaderStageFlagBits::eFragment;
|
||||
for (const auto& stage: vertex_info) {
|
||||
const auto native_stage = NativeShaderStage(stage.logical_stage);
|
||||
AddLayoutBindings(descriptor_bindings, *stage.stage.program, native_stage);
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
for (const auto* stage: stages) {
|
||||
if (stage != nullptr) {
|
||||
const auto native_stage = NativeShaderStage(stage->info.stage);
|
||||
AddLayoutBindings(descriptor_bindings, stage->info, native_stage);
|
||||
pipeline.uses_image_heap |= stage->info.info.uses_image_heap;
|
||||
graphics_stages |= native_stage;
|
||||
}
|
||||
}
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {graphics_stages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const std::array set_layouts {pipeline.descriptor_set_layout, graphics.image_heap_layout};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.setLayoutCount = pipeline.uses_image_heap ? 2u : 1u;
|
||||
pipeline_layout_info.pSetLayouts = set_layouts.data();
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
@@ -495,7 +331,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" set_layouts=1 push_constants=%" PRIu32 "\n",
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
|
||||
key.stage_ids[0], key.stage_ids[3], 1u);
|
||||
}
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
@@ -505,8 +341,6 @@ 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 {
|
||||
@@ -552,9 +386,9 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
|
||||
vk::PipelineTessellationStateCreateInfo tessellation_state {};
|
||||
tessellation_state.patchControlPoints =
|
||||
tessellation ? vs_input_info.tess.input_control_points : 3u;
|
||||
pipeline_info.pTessellationState = (rect_list || tessellation) ? &tessellation_state : nullptr;
|
||||
tessellation_state.patchControlPoints = static_params.patch_control_points;
|
||||
pipeline_info.pTessellationState =
|
||||
static_params.patch_control_points != 0 ? &tessellation_state : nullptr;
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
@@ -570,7 +404,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
|
||||
" depth=%s blend=%s dyn_states=%" PRIu32 "\n",
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0,
|
||||
key.stage_ids[0], key.stage_ids[3],
|
||||
static_cast<uint32_t>(static_params.topology), static_params.color_mask[0],
|
||||
(with_depth ? "true" : "false"), (static_params.blend_enable[0] ? "true" : "false"),
|
||||
dynamic_state.dynamicStateCount);
|
||||
@@ -583,29 +417,18 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
|
||||
if (tess_control_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
|
||||
}
|
||||
if (tess_eval_shader_module != nullptr) {
|
||||
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
|
||||
}
|
||||
graphics.device.destroyShaderModule(tess_control_shader_module, nullptr);
|
||||
graphics.device.destroyShaderModule(tess_eval_shader_module, nullptr);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
|
||||
EXIT_IF(compute_module == nullptr);
|
||||
|
||||
const ShaderModule& module, vk::PipelineCache driver_cache) {
|
||||
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
|
||||
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
comp_shader_stage_info.module = compute_module;
|
||||
comp_shader_stage_info.module = module.module;
|
||||
comp_shader_stage_info.pName = "main";
|
||||
EXIT_IF(!input_info.stage);
|
||||
const auto wave_size = input_info.stage.program->wave_size;
|
||||
const auto wave_size = module.info.wave_size;
|
||||
if (graphics.compute_subgroup_size_control_enabled &&
|
||||
wave_size >= graphics.min_subgroup_size && wave_size <= graphics.max_subgroup_size) {
|
||||
comp_subgroup_size.requiredSubgroupSize = wave_size;
|
||||
@@ -613,32 +436,27 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
}
|
||||
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eCompute);
|
||||
AddLayoutBindings(descriptor_bindings, module.info, vk::ShaderStageFlagBits::eCompute);
|
||||
pipeline.uses_image_heap = module.info.info.uses_image_heap;
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
const vk::PushConstantRange push_constants {vk::ShaderStageFlagBits::eCompute, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const std::array set_layouts {pipeline.descriptor_set_layout, graphics.image_heap_layout};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.setLayoutCount = pipeline.uses_image_heap ? 2u : 1u;
|
||||
pipeline_layout_info.pSetLayouts = set_layouts.data();
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
pipeline_layout_info.pPushConstantRanges = &push_constants;
|
||||
|
||||
EXIT_IF(pipeline.pipeline_layout != nullptr);
|
||||
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS begin set_layouts=1 push_constants=%u\n",
|
||||
1u);
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline_layout == nullptr);
|
||||
|
||||
vk::ComputePipelineCreateInfo info {};
|
||||
if (input_info.stage.program->info.uses_gds) {
|
||||
if (module.info.info.uses_gds) {
|
||||
info.flags = vk::PipelineCreateFlagBits::eDispatchBase;
|
||||
}
|
||||
info.stage = comp_shader_stage_info;
|
||||
@@ -647,15 +465,9 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& p
|
||||
|
||||
EXIT_IF(pipeline.pipeline != nullptr);
|
||||
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines begin layout=%p\n",
|
||||
static_cast<void*>(pipeline.pipeline_layout));
|
||||
result = graphics.device.createComputePipelines(driver_cache, 1, &info, nullptr,
|
||||
&pipeline.pipeline);
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -23,14 +23,9 @@ class Shader;
|
||||
} // namespace HW
|
||||
|
||||
struct GraphicContext;
|
||||
struct ShaderBufferResource;
|
||||
struct ShaderComputeInputInfo;
|
||||
struct RenderDepthInfo;
|
||||
struct RenderColorInfo;
|
||||
struct DrawCallInfo;
|
||||
struct DrawEmitInfo;
|
||||
struct DrawIndexBufferSource;
|
||||
struct DrawRenderState;
|
||||
class RenderContext;
|
||||
class CommandScheduler;
|
||||
struct RenderExecutorTestAccess;
|
||||
@@ -48,6 +43,13 @@ enum class DrawOffsetSource : uint8_t {
|
||||
IndirectArgs,
|
||||
};
|
||||
|
||||
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
|
||||
struct DrawIndirectArgs {
|
||||
uint64_t address = 0;
|
||||
uint32_t count = 0;
|
||||
uint32_t stride = 0;
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
uint32_t index_count = 0;
|
||||
const void* index_addr = nullptr;
|
||||
@@ -57,6 +59,8 @@ struct DrawIndexArgs {
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
// With indirect records, index_count is INDEX_BUFFER_SIZE.
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct DrawAutoArgs {
|
||||
@@ -66,6 +70,7 @@ struct DrawAutoArgs {
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
@@ -161,18 +166,19 @@ public:
|
||||
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
|
||||
const PipelineCache::Pipeline& pipeline,
|
||||
std::span<PreparedBindings* const> bindings);
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
|
||||
private:
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
// False, with nothing drawn, when the draw's indirect records need CPU counts.
|
||||
bool DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
bool DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
|
||||
struct GraphicsBindings {
|
||||
std::array<PreparedBindings, 3> vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
};
|
||||
|
||||
[[nodiscard]] TextureBinding ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value);
|
||||
void PrepareGraphicsBindings(std::span<PreparedBindings* const> stages,
|
||||
std::span<RenderColorInfo> colors);
|
||||
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
|
||||
@@ -187,9 +193,8 @@ private:
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state);
|
||||
void ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
DrawRenderState& state, vk::PrimitiveTopology topology,
|
||||
const DrawEmitInfo& emit, const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable);
|
||||
DrawRenderState& state, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source);
|
||||
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth,
|
||||
vk::ImageAspectFlags& feedback_aspects,
|
||||
|
||||
@@ -53,7 +53,7 @@ static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::Descriptor
|
||||
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& program = input.stage.module->info;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
EXIT("compute runtime buffer count does not match shader metadata\n");
|
||||
@@ -135,7 +135,7 @@ bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& program = input.stage.module->info;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
@@ -208,7 +208,7 @@ bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
if (logged_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: compute %s shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
image_clear ? "image clear" : "buffer fill", input.stage.program->shader_hash,
|
||||
image_clear ? "image clear" : "buffer fill", input.stage.module->info.shader_hash,
|
||||
descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
return true;
|
||||
@@ -216,7 +216,7 @@ bool RenderExecutor::TryConsumeComputeFill(const ShaderComputeInputInfo& input,
|
||||
|
||||
static void BindSharedMemory(RenderContext& context, ShaderComputeInputInfo& input,
|
||||
PreparedBindings& bindings, uint64_t indirect_args = 0) {
|
||||
if (input.stage.program->bindings.descriptor_counts[
|
||||
if (input.stage.module->info.bindings.descriptor_counts[
|
||||
static_cast<size_t>(ShaderRecompiler::IR::DescriptorBindingKind::SharedMemory)] == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -248,7 +248,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t thread_group_z, uint32_t mode, bool async_compute) {
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
m_context.GetCommandScheduler().PopPendingOperations();
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DispatchDirect), submit_id,
|
||||
@@ -293,7 +292,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
const auto& cs_regs = sh_ctx.GetCs();
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
@@ -311,16 +309,15 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
input_info.workgroup_counts[axis] = threads / size + (threads % size != 0u);
|
||||
}
|
||||
}
|
||||
const auto compute_program =
|
||||
m_context.GetPipelineCache().GetComputeProgram(cs_regs, sh_regs, input_info);
|
||||
if (!compute_program) return;
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
if (!m_context.GetPipelineCache().GetComputeProgram(cs_regs, input_info)) return;
|
||||
if (use_thread_dimensions) {
|
||||
input_info.dispatch_threads_num[0] = thread_group_x;
|
||||
input_info.dispatch_threads_num[1] = thread_group_y;
|
||||
input_info.dispatch_threads_num[2] = thread_group_z;
|
||||
}
|
||||
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& program = input_info.stage.module->info;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
if (resources.specialization_reads.empty() &&
|
||||
(TryConsumeComputeMetaClear(input_info, buffer) ||
|
||||
@@ -350,8 +347,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
|
||||
buffer.EndRendering();
|
||||
auto& pipeline =
|
||||
m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info);
|
||||
auto& bindings = m_compute_bindings;
|
||||
PrepareBindings(input_info.stage, bindings);
|
||||
if (program.bindings.dispatch_thread_dword != ShaderRecompiler::IR::PushData::NoStart) {
|
||||
@@ -363,6 +359,9 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
if (program.info.uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
RebindImages(bindings);
|
||||
BindSharedMemory(m_context, input_info, bindings);
|
||||
RebindBuffers(bindings);
|
||||
@@ -497,20 +496,22 @@ void RenderExecutor::DispatchIndirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
ShaderComputeInputInfo input_info {};
|
||||
input_info.async_compute = async_compute;
|
||||
input_info.indirect_args_address = args_addr;
|
||||
const auto compute_program = m_context.GetPipelineCache().GetComputeProgram(
|
||||
cs_regs, buffer.GetRegisters().GetShaderRegisters(), input_info);
|
||||
if (!compute_program) return;
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
if (!m_context.GetPipelineCache().GetComputeProgram(cs_regs, input_info)) return;
|
||||
buffer.EndRendering();
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info, compute_program);
|
||||
auto& pipeline = m_context.GetPipelineCache().GetComputePipeline(input_info);
|
||||
auto& bindings = m_compute_bindings;
|
||||
PrepareBindings(input_info.stage, bindings);
|
||||
PreparedBindings* descriptor_stage = &bindings;
|
||||
FindBuffers(std::span {&descriptor_stage, 1u});
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& program = input_info.stage.module->info;
|
||||
if (program.info.uses_dma) {
|
||||
m_context.PrepareBda();
|
||||
}
|
||||
BindSharedMemory(m_context, input_info, bindings, args_addr);
|
||||
if (program.info.uses_image_heap) {
|
||||
m_context.GetImageHeap().Rebind(*this);
|
||||
}
|
||||
RebindImages(bindings);
|
||||
// Acquiring arguments can merge cache buffers; finalize shader bindings afterward.
|
||||
const auto [args_buffer, args_offset] = m_context.GetBufferCache().ObtainBuffer(
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
RenderContext::RenderContext(GraphicContext& graphics)
|
||||
RenderContext::RenderContext(GraphicContext& graphics, const std::filesystem::path& shader_store)
|
||||
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
|
||||
m_descriptor_heap(graphics, m_command_scheduler.GetMasterSemaphore()),
|
||||
m_pipeline_cache(graphics), m_sampler_cache(graphics),
|
||||
m_pipeline_cache(graphics, &m_image_heap, shader_store), m_sampler_cache(graphics),
|
||||
m_buffer_cache(graphics, m_command_scheduler, m_page_manager, m_texture_cache),
|
||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache) {
|
||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache),
|
||||
m_image_heap(*this, m_page_manager), m_occlusion_queries(graphics, m_command_scheduler) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
}
|
||||
|
||||
@@ -64,6 +65,7 @@ bool RenderContext::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) no
|
||||
if (access == PageFaultAccess::Write) {
|
||||
m_buffer_cache.InvalidateMemory(fault_vaddr, fault_size);
|
||||
m_texture_cache.InvalidateMemory(fault_vaddr, fault_size);
|
||||
m_image_heap.InvalidateMemory(fault_vaddr, fault_size);
|
||||
} else {
|
||||
m_buffer_cache.ReadMemory(fault_vaddr, fault_size);
|
||||
}
|
||||
@@ -76,6 +78,7 @@ bool RenderContext::InvalidateMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.InvalidateMemory(vaddr, size);
|
||||
m_image_heap.InvalidateMemory(vaddr, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -112,6 +115,7 @@ void RenderContext::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_image_heap.UnmapMemory(vaddr, size);
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Subtract(vaddr, size);
|
||||
};
|
||||
@@ -130,7 +134,11 @@ void RenderContext::PrepareBda() {
|
||||
m_bda_logged = true;
|
||||
}
|
||||
std::shared_lock lock(m_mapped_ranges_mutex);
|
||||
m_buffer_cache.SynchronizeBda(m_mapped_ranges);
|
||||
m_buffer_cache.TakeBdaSync().ForEach([this](uint64_t begin, uint64_t end) {
|
||||
m_mapped_ranges.ForEachInRange(begin, end - begin, [this](uint64_t start, uint64_t finish) {
|
||||
m_buffer_cache.SynchronizeBuffersInRange(start, finish - start);
|
||||
});
|
||||
});
|
||||
m_fault_process_pending = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "kernel/eventQueue.h"
|
||||
|
||||
@@ -29,7 +31,8 @@ class GuestGpu;
|
||||
|
||||
class RenderContext {
|
||||
public:
|
||||
explicit RenderContext(GraphicContext& graphics);
|
||||
explicit RenderContext(GraphicContext& graphics,
|
||||
const std::filesystem::path& shader_store = ShaderStorePath());
|
||||
~RenderContext();
|
||||
KYTY_CLASS_NO_COPY(RenderContext);
|
||||
|
||||
@@ -43,10 +46,12 @@ public:
|
||||
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
|
||||
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
|
||||
DescriptorHeap& GetDescriptorHeap() { return m_descriptor_heap; }
|
||||
ImageHeap& GetImageHeap() { return m_image_heap; }
|
||||
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
|
||||
BufferCache& GetBufferCache() { return m_buffer_cache; }
|
||||
TextureCache& GetTextureCache() { return m_texture_cache; }
|
||||
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
|
||||
OcclusionQueries& GetOcclusionQueries() { return m_occlusion_queries; }
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||
@@ -76,6 +81,8 @@ private:
|
||||
PageManager m_page_manager;
|
||||
BufferCache m_buffer_cache;
|
||||
TextureCache m_texture_cache;
|
||||
ImageHeap m_image_heap;
|
||||
OcclusionQueries m_occlusion_queries;
|
||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||
RangeSet m_mapped_ranges;
|
||||
std::unique_ptr<GuestGpu> m_gpu;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/debug.h"
|
||||
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
|
||||
#include "graphics/host_gpu/renderer/image/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
@@ -58,7 +57,7 @@ std::pair<int32_t, uint32_t> ResolveDrawOffsets(uint32_t index_offset,
|
||||
}
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const auto& program = *vs_input_info.stage.program;
|
||||
const auto& program = vs_input_info.stage.module->info;
|
||||
const auto& resources = *vs_input_info.stage.resources;
|
||||
if (index_offset == 0 &&
|
||||
program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
|
||||
@@ -198,7 +197,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
|
||||
const auto& dc = ctx.GetDepthControl();
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
const auto& vp0 = vp.viewports[0];
|
||||
const auto& ps_resources = ps_input_info.stage.program->info;
|
||||
const auto& ps_resources = ps_input_info.stage.module->info.info;
|
||||
const auto sampled_images = std::count_if(
|
||||
ps_resources.images.begin(), ps_resources.images.end(), [](const auto& image) {
|
||||
return image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
@@ -321,7 +320,7 @@ static void UpdateGraphicsDynamicState(const CommandBuffer& buffer, DynamicState
|
||||
const auto& ctx = buffer.GetRegisters();
|
||||
const auto& vp = ctx.GetScreenViewport();
|
||||
const vk::Extent2D framebuffer_extent {rendering.width, rendering.height};
|
||||
const auto& outputs = vs_input_info.stage.program->info.outputs;
|
||||
const auto& outputs = vs_input_info.stage.module->info.info.outputs;
|
||||
const bool indexed_viewports =
|
||||
std::any_of(outputs.begin(), outputs.end(), [](const auto& output) {
|
||||
return output.kind == ShaderRecompiler::IR::StageOutputKind::ViewportIndex;
|
||||
@@ -433,22 +432,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 +457,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 +484,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 +571,6 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
|
||||
return state;
|
||||
}
|
||||
|
||||
static uint32_t DrawColorOutputMask(const HW::Context& ctx) {
|
||||
const auto& sh_regs = ctx.GetShaderRegisters();
|
||||
const auto write_mask = ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask;
|
||||
uint32_t output_mask = ctx.HasObjectPrimitiveIdOutput() ? 1u << 7u : 0;
|
||||
for (uint32_t index = 0; index < RENDER_COLOR_ATTACHMENTS_MAX; index++) {
|
||||
const auto slot = ShaderPixelExportTarget(sh_regs.m_cbShaderMask, index);
|
||||
if (slot >= RENDER_COLOR_ATTACHMENTS_MAX) {
|
||||
break;
|
||||
}
|
||||
if (sh_regs.target_output_mode[index] != 0 &&
|
||||
render_target_mask_slot(write_mask, slot) != 0) {
|
||||
output_mask |= 1u << slot;
|
||||
}
|
||||
}
|
||||
return output_mask;
|
||||
}
|
||||
|
||||
enum class CbColorMode : uint8_t {
|
||||
Disable = 0,
|
||||
Normal = 1,
|
||||
@@ -631,10 +596,11 @@ bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
|
||||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
|
||||
}
|
||||
|
||||
// vertex_offset is the first vertex of non-indexed draws.
|
||||
struct DrawEmitInfo {
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
int32_t vertex_offset = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawIndirectArgs indirect;
|
||||
};
|
||||
|
||||
struct DrawIndexBufferSource {
|
||||
@@ -869,61 +835,24 @@ static bool ResolvePrimitiveRestart(const CommandBuffer& buffer,
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t color_output_mask, DrawRenderState& state) {
|
||||
auto& ctx = buffer.GetRegisters();
|
||||
auto& sh_ctx = buffer.GetShaders();
|
||||
|
||||
const auto& vertex_shader_info = sh_ctx.GetVs();
|
||||
const auto& pixel_shader_info = sh_ctx.GetPs();
|
||||
const auto& shader_regs = ctx.GetShaderRegisters();
|
||||
|
||||
state.programs = {};
|
||||
state.ps_input_info = {};
|
||||
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
|
||||
target_export_mapping {};
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
const auto& rt = ctx.GetRenderTarget(slot);
|
||||
if ((color_output_mask & (1u << slot)) != 0 && rt.base.addr != 0) {
|
||||
target_export_mapping[slot] =
|
||||
TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
|
||||
rt.info.channel_order)
|
||||
.export_mapping;
|
||||
}
|
||||
}
|
||||
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||
}
|
||||
state.programs = pipeline_cache.GetGraphicsPrograms(
|
||||
vertex_shader_info, pixel_shader_info, shader_regs, ctx, buffer.GetUserConfig(),
|
||||
target_export_mapping, state.ps_active, state.vertex_info, state.ps_input_info);
|
||||
}
|
||||
|
||||
bool RenderExecutor::PrepareDrawRenderState(CommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
DrawRenderState& state) {
|
||||
const auto& shader_regs = buffer.GetRegisters().GetShaderRegisters();
|
||||
const auto color_output_mask = DrawColorOutputMask(buffer.GetRegisters());
|
||||
state.ps_active = buffer.GetShaders().GetPs().ps_regs.data_addr != 0 &&
|
||||
(color_output_mask != 0 ||
|
||||
PixelShaderHasDepthOrCoverageSideEffects(shader_regs));
|
||||
RefreshShaders(buffer, draw, color_output_mask, state);
|
||||
uint32_t mrt_mask = 0;
|
||||
if (state.ps_active) {
|
||||
for (const auto& output: state.ps_input_info.stage.program->info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt ||
|
||||
output.kind == ShaderRecompiler::IR::StageOutputKind::ObjectPrimitiveId) {
|
||||
mrt_mask |= 1u << output.location;
|
||||
}
|
||||
}
|
||||
m_context.GetImageHeap().BeginDraw();
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "GetGraphicsPrograms");
|
||||
}
|
||||
m_context.GetPipelineCache().GetGraphicsPrograms(buffer, state);
|
||||
// Vertex programs beyond the host mesh limits or with unsupported call targets are not
|
||||
// compiled.
|
||||
if (state.vertex_info[0].stage.module == nullptr) {
|
||||
return false;
|
||||
}
|
||||
mrt_mask &= color_output_mask;
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "ResolveRenderColorTarget");
|
||||
}
|
||||
for (uint32_t slot = 0; slot < RENDER_COLOR_ATTACHMENTS_MAX; slot++) {
|
||||
if ((mrt_mask & (1u << slot)) != 0) {
|
||||
if ((state.mrt_mask & (1u << slot)) != 0) {
|
||||
ResolveRenderColorTarget(buffer, state.color_info[state.color_count],
|
||||
render_target_slice_offset, slot);
|
||||
if (state.color_info[state.color_count].image_id) {
|
||||
@@ -1043,8 +972,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
|
||||
emit.first_instance);
|
||||
vk_buffer.draw(draw.index_count, draw.instance_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset), emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
@@ -1054,7 +983,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
vk_buffer.drawIndexed(4, draw.instance_count, i, emit.vertex_offset,
|
||||
emit.first_instance);
|
||||
} else {
|
||||
vk_buffer.draw(4, draw.instance_count, i + emit.first_vertex,
|
||||
vk_buffer.draw(4, draw.instance_count,
|
||||
i + static_cast<uint32_t>(emit.vertex_offset),
|
||||
emit.first_instance);
|
||||
}
|
||||
}
|
||||
@@ -1075,7 +1005,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
|
||||
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled ||
|
||||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
|
||||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
|
||||
emit.first_vertex != 0 || emit.first_instance != 0 ||
|
||||
emit.vertex_offset != 0 || emit.first_instance != 0 ||
|
||||
state.color_count != 1 || color.target_slot != 0 || !depth.image_id ||
|
||||
color.desc.info.samples != 1 ||
|
||||
depth.desc.info.extent.width != color.Extent().width ||
|
||||
@@ -1121,16 +1051,15 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
|
||||
|
||||
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, DrawRenderState& state,
|
||||
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source,
|
||||
bool primitive_restart_enable) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const DrawEmitInfo& emit,
|
||||
const DrawIndexBufferSource& index_source) {
|
||||
auto& ucfg = buffer.GetUserConfig();
|
||||
const bool primitive_restart_enable = state.primitive_restart_enable;
|
||||
if (CopyDepthToColor(buffer, draw, emit, state)) {
|
||||
return;
|
||||
}
|
||||
const auto vertex_stages =
|
||||
std::span {state.vertex_info.data(), state.programs.VertexStageCount()};
|
||||
const bool mesh_active = state.vertex_info[0].stage.program->stage == ShaderType::Mesh;
|
||||
const auto vertex_stages = state.VertexStages();
|
||||
const bool mesh_active = state.vertex_info[0].stage.module->info.stage == ShaderType::Mesh;
|
||||
uint32_t mesh_groups = 0;
|
||||
if (mesh_active) {
|
||||
const auto& mesh = state.vertex_info[0].mesh;
|
||||
@@ -1189,13 +1118,18 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
|
||||
index_binding = PrepareIndexBuffer(buffer, index_source);
|
||||
}
|
||||
std::pair<Buffer*, uint64_t> args_buffer {};
|
||||
if (emit.indirect.address != 0) {
|
||||
const auto size = static_cast<uint64_t>(emit.indirect.count - 1) * emit.indirect.stride +
|
||||
(draw.IsIndexed() ? sizeof(vk::DrawIndexedIndirectCommand)
|
||||
: sizeof(vk::DrawIndirectCommand));
|
||||
args_buffer = m_context.GetBufferCache().ObtainBuffer(emit.indirect.address, size, false);
|
||||
EXIT_IF((args_buffer.second & 3u) != 0);
|
||||
}
|
||||
if (draw.IsIndexed()) {
|
||||
LogDrawPhase(draw.Name(), "CreatePipeline");
|
||||
}
|
||||
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, vertex_stages, buffer,
|
||||
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
|
||||
state.programs);
|
||||
auto& pipeline = m_context.GetPipelineCache().GetGraphicsPipeline(buffer, state);
|
||||
vk::ImageAspectFlags feedback_aspects;
|
||||
const auto rendering =
|
||||
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info,
|
||||
@@ -1225,12 +1159,11 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (draw_dwords != 0) {
|
||||
std::array<uint32_t, PushData::MeshDrawDwordCount> draw_data {};
|
||||
if (mesh_active) {
|
||||
draw_data = {
|
||||
draw.index_count,
|
||||
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
|
||||
emit.first_instance, index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
draw_data = {draw.index_count,
|
||||
static_cast<uint32_t>(emit.vertex_offset),
|
||||
index_source.guest_element_size,
|
||||
static_cast<uint32_t>(index_source.address),
|
||||
static_cast<uint32_t>(index_source.address >> 32u)};
|
||||
} else {
|
||||
draw_data[0] = buffer.GetUserConfig().GetObjectId();
|
||||
}
|
||||
@@ -1273,11 +1206,20 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (!draw.IsIndexed()) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||
}
|
||||
auto& occlusion_queries = m_context.GetOcclusionQueries();
|
||||
const int32_t occlusion_query = occlusion_queries.Begin(vk_buffer);
|
||||
if (mesh_active) {
|
||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||
} else {
|
||||
} else if (args_buffer.first == nullptr) {
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
|
||||
} else if (draw.IsIndexed()) {
|
||||
vk_buffer.drawIndexedIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||
emit.indirect.count, emit.indirect.stride);
|
||||
} else {
|
||||
vk_buffer.drawIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||
emit.indirect.count, emit.indirect.stride);
|
||||
}
|
||||
occlusion_queries.End(vk_buffer, occlusion_query);
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::Draws);
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::DrawInstances, draw.instance_count);
|
||||
|
||||
@@ -1294,7 +1236,30 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
}
|
||||
}
|
||||
|
||||
void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
|
||||
// mesh and quad-list draws do. Indirect records carry their own offsets.
|
||||
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
|
||||
const ShaderVertexInputInfo& vs,
|
||||
DrawOffsetSource source,
|
||||
int32_t first_vertex,
|
||||
uint32_t first_instance,
|
||||
const DrawIndirectArgs& indirect) {
|
||||
if (indirect.address != 0) {
|
||||
if (vs.stage.module->info.stage == ShaderType::Mesh ||
|
||||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DrawEmitInfo {.indirect = indirect};
|
||||
}
|
||||
if (source == DrawOffsetSource::IndirectArgs) {
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
|
||||
}
|
||||
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
|
||||
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
|
||||
}
|
||||
|
||||
bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const DrawIndexArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
@@ -1309,19 +1274,19 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
reinterpret_cast<uint64_t>(args.index_addr));
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (args.index_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
if (args.index_count == 0 || (args.instance_count == 0 && args.indirect.address == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DrawHasValidVertexShader(sh_ctx)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -1345,9 +1310,9 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
return;
|
||||
DrawRenderState state {};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
@@ -1367,7 +1332,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
default: EXIT("unknown index_type_and_size: %u\n", args.index_type_and_size);
|
||||
}
|
||||
index_source.size = static_cast<uint64_t>(args.index_count) * index_source.guest_element_size;
|
||||
const bool primitive_restart = ResolvePrimitiveRestart(buffer, index_source);
|
||||
state.primitive_restart_enable = ResolvePrimitiveRestart(buffer, index_source);
|
||||
|
||||
std::vector<uint16_t> expanded_indices;
|
||||
if (index_source.guest_element_size == 1) {
|
||||
@@ -1375,7 +1340,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const auto* src = static_cast<const uint8_t*>(args.index_addr);
|
||||
expanded_indices.resize(args.index_count);
|
||||
for (uint32_t i = 0; i < args.index_count; i++) {
|
||||
expanded_indices[i] = primitive_restart && src[i] == 0xffu ? 0xffffu : src[i];
|
||||
expanded_indices[i] =
|
||||
state.primitive_restart_enable && src[i] == 0xffu ? 0xffffu : src[i];
|
||||
}
|
||||
index_source.host_data = expanded_indices.data();
|
||||
index_source.size = expanded_indices.size() * sizeof(uint16_t);
|
||||
@@ -1383,33 +1349,24 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndex, args.index_count,
|
||||
args.instance_count, args.first_instance};
|
||||
DrawRenderState state {};
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
|
||||
args.index_addr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
|
||||
DrawEmitInfo emit {};
|
||||
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex;
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
|
||||
primitive_restart);
|
||||
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
|
||||
args.base_vertex, args.first_instance, args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, *emit, index_source);
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(buffer.IsInvalid());
|
||||
@@ -1423,19 +1380,19 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
args.first_instance);
|
||||
|
||||
Common::LockGuard lock(m_context.GetMutex());
|
||||
if (args.vertex_count == 0 || args.instance_count == 0) {
|
||||
return;
|
||||
if (args.indirect.address == 0 && (args.vertex_count == 0 || args.instance_count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
|
||||
DepthStencilCopy(buffer) ||
|
||||
ResolveColorTargets(buffer, args.render_target_slice_offset)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DrawHasValidVertexShader(sh_ctx)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -1458,20 +1415,19 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
const DrawCallInfo draw {CommandBufferDebugOp::DrawIndexAuto,
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
DrawRenderState state {};
|
||||
if (!GetDrawTopology(ucfg, state.topology)) {
|
||||
ResetBindings();
|
||||
return true;
|
||||
}
|
||||
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
|
||||
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
|
||||
state.vertex_info[0].stage.program->param_export_mask == 0 &&
|
||||
state.vertex_info[0].stage.module->info.param_export_mask == 0 &&
|
||||
state.ps_input_info.input_num != 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
|
||||
@@ -1480,24 +1436,18 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
|
||||
}
|
||||
ResetBindings();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LogDrawStateIfNeeded(buffer, draw, state, 0, nullptr);
|
||||
|
||||
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
|
||||
const auto [vertex_offset, instance_offset] =
|
||||
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
|
||||
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
|
||||
DrawEmitInfo emit {};
|
||||
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
|
||||
emit.first_vertex = native_indirect ? 0 :
|
||||
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
|
||||
emit.first_instance = native_indirect ? 0 : instance_offset;
|
||||
|
||||
DrawIndexBufferSource index_source {};
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
|
||||
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
|
||||
static_cast<int32_t>(args.first_vertex), args.first_instance,
|
||||
args.indirect);
|
||||
if (emit) {
|
||||
ExecutePreparedDraw(submit_id, buffer, draw, state, *emit, {});
|
||||
}
|
||||
ResetBindings();
|
||||
return emit.has_value();
|
||||
}
|
||||
|
||||
bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_target_slice_offset) {
|
||||
@@ -1529,8 +1479,9 @@ bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_
|
||||
cache.MarkGpuWritten(dst.image_id);
|
||||
auto& source = cache.GetImage(src.image_id);
|
||||
auto& destination = cache.GetImage(dst.image_id);
|
||||
destination.Resolve(source, {src.guest_mip_level, 1, src.guest_array_layer, 1},
|
||||
{dst.guest_mip_level, 1, dst.guest_array_layer, 1});
|
||||
destination.Resolve(source,
|
||||
{src.desc.view_info.base_level, 1, src.desc.view_info.base_layer, 1},
|
||||
{dst.desc.view_info.base_level, 1, dst.desc.view_info.base_layer, 1});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#version 450
|
||||
|
||||
// One workgroup per metadata slice; see TileManager::CheckColorClear.
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
layout(set = 0, binding = 0, std430) buffer Metadata { uint data[]; } metadata;
|
||||
layout(set = 0, binding = 1, std430) writeonly buffer Predicates { uint data[]; } predicates;
|
||||
|
||||
layout(push_constant, std430) uniform Check {
|
||||
uint metadata_base;
|
||||
uint words;
|
||||
uint keys;
|
||||
uint expand;
|
||||
} params;
|
||||
|
||||
const uint Slots = 8u; // TileManager::ColorClearSlots
|
||||
|
||||
shared uint key;
|
||||
shared uint mismatch;
|
||||
|
||||
void main() {
|
||||
const uint slice = gl_WorkGroupID.x;
|
||||
const uint base = (params.metadata_base >> 2u) + slice * params.words;
|
||||
// Each invocation reads, and may rewrite, only its own words.
|
||||
if (gl_LocalInvocationIndex == 0u) {
|
||||
key = metadata.data[base] & 0xffu;
|
||||
mismatch = 0u;
|
||||
}
|
||||
barrier();
|
||||
const uint pattern = key * 0x01010101u;
|
||||
bool same = true;
|
||||
for (uint i = gl_LocalInvocationIndex; same && i < params.words; i += gl_WorkGroupSize.x) {
|
||||
same = metadata.data[base + i] == pattern;
|
||||
}
|
||||
if (!same) {
|
||||
atomicOr(mismatch, 1u);
|
||||
}
|
||||
barrier();
|
||||
const uint slot = key >> 5u;
|
||||
const bool hit = mismatch == 0u && (key & 0x1fu) == 0u && (params.keys & (1u << slot)) != 0u;
|
||||
if (gl_LocalInvocationIndex < Slots) {
|
||||
predicates.data[slice * Slots + gl_LocalInvocationIndex] =
|
||||
uint(hit && slot == gl_LocalInvocationIndex);
|
||||
}
|
||||
if (hit && params.expand != 0u) {
|
||||
for (uint i = gl_LocalInvocationIndex; i < params.words; i += gl_WorkGroupSize.x) {
|
||||
metadata.data[base + i] = 0xffffffffu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,6 +310,10 @@ void GpuActivityRows(const Snapshot& shown) {
|
||||
Row("CPU waiting on GPU",
|
||||
Fmt("%.0f ms/s (%.0f waits/s)", Rate(shown, Counter::GpuWaitNs) / 1e6,
|
||||
Rate(shown, Counter::GpuWaits)));
|
||||
Row("DCC/CMASK readbacks",
|
||||
Fmt("%.0f ms/s (%.0f/s)", Rate(shown, Counter::ColorMetadataReadbackNs) / 1e6,
|
||||
Rate(shown, Counter::ColorMetadataReadbacks)));
|
||||
Row("DCC/CMASK GPU checks", Fmt("%.0f/s", Rate(shown, Counter::ColorMetadataChecks)));
|
||||
}
|
||||
|
||||
void CompilationRows(const Snapshot& shown) {
|
||||
|
||||
@@ -221,7 +221,7 @@ public:
|
||||
void WaitForSubmitSlot(VideoOutConfig& cfg);
|
||||
bool Flip(uint32_t micros);
|
||||
void GetFlipStatus(VideoOutConfig& cfg, VideoOutFlipStatus& out);
|
||||
void Wait(VideoOutConfig& cfg, int index);
|
||||
bool IsSafeForRendering(VideoOutConfig& cfg, int index);
|
||||
|
||||
private:
|
||||
enum class RequestState { Reserved, Recording, Ready, Presenting };
|
||||
@@ -240,6 +240,7 @@ private:
|
||||
Graphics::Presenter::Frame* frame;
|
||||
bool premultiplied_alpha;
|
||||
};
|
||||
void SignalDoneLocked();
|
||||
|
||||
Graphics::Presenter& m_presenter;
|
||||
Common::Mutex m_mutex;
|
||||
@@ -250,6 +251,7 @@ private:
|
||||
std::list<Request> m_cpu_requests;
|
||||
std::list<Request> m_cancelled_requests;
|
||||
bool m_processing = false;
|
||||
bool m_gpu_waiting = false;
|
||||
uint64_t m_next_request_id = 1;
|
||||
};
|
||||
|
||||
@@ -953,7 +955,7 @@ bool FlipQueue::Reserve(VideoOutConfig& cfg, int index, int64_t flip_arg, FlipRe
|
||||
}
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
}
|
||||
if (cfg.flip_status.flipPendingNum >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) {
|
||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::FlipsRejected);
|
||||
@@ -1033,7 +1035,7 @@ void FlipQueue::Cancel(VideoOutConfig& cfg) {
|
||||
it = queue->erase(it);
|
||||
}
|
||||
}
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_submit_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
@@ -1205,19 +1207,26 @@ void FlipQueue::WaitForSubmitSlot(VideoOutConfig& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
void FlipQueue::Wait(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto has_request = [this, &cfg, index] {
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
return std::any_of(m_requests.begin(), m_requests.end(), matches) ||
|
||||
std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
};
|
||||
while (has_request()) {
|
||||
m_done_cond_var.Wait(&m_mutex);
|
||||
void FlipQueue::SignalDoneLocked() {
|
||||
m_done_cond_var.SignalAll();
|
||||
// A stalled GPU cannot drain and shut down without re-checking under m_mutex, so it is alive here.
|
||||
if (m_gpu_waiting) {
|
||||
m_gpu_waiting = false;
|
||||
m_presenter.Renderer().GetGpu().Wake();
|
||||
}
|
||||
}
|
||||
|
||||
// A display buffer is safe for rendering once it is neither displayed nor waiting to be.
|
||||
bool FlipQueue::IsSafeForRendering(VideoOutConfig& cfg, int index) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto matches = [&cfg, index](const auto& r) { return r.cfg == &cfg && r.index == index; };
|
||||
const bool safe = cfg.flip_status.currentBuffer != index &&
|
||||
std::none_of(m_requests.begin(), m_requests.end(), matches) &&
|
||||
std::none_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches);
|
||||
m_gpu_waiting = !safe;
|
||||
return safe;
|
||||
}
|
||||
|
||||
bool FlipQueue::Flip(uint32_t micros) {
|
||||
KYTY_PROFILER_BLOCK("FlipQueue::Flip");
|
||||
|
||||
@@ -1311,7 +1320,7 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
it = m_requests.erase(it);
|
||||
}
|
||||
m_processing = false;
|
||||
m_done_cond_var.SignalAll();
|
||||
SignalDoneLocked();
|
||||
m_submit_slot_cond_var.SignalAll();
|
||||
m_mutex.Unlock();
|
||||
for (size_t i = count; i != 0; i--) {
|
||||
@@ -1694,12 +1703,12 @@ void VideoOutDriver::WaitForSubmitSlot(int handle) {
|
||||
m_impl->GetFlipQueue().WaitForSubmitSlot(*cfg);
|
||||
}
|
||||
|
||||
void VideoOutDriver::WaitFlipDone(int handle, int index) {
|
||||
bool VideoOutDriver::IsSafeForRendering(int handle, int index) {
|
||||
auto* ctx = m_impl->Get(handle);
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsValidBufferIndex(index));
|
||||
m_impl->GetFlipQueue().Wait(*ctx, index);
|
||||
return m_impl->GetFlipQueue().IsSafeForRendering(*ctx, index);
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
void CompleteFlip(uint64_t request_id);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void WaitForSubmitSlot(int handle);
|
||||
void WaitFlipDone(int handle, int index);
|
||||
[[nodiscard]] bool IsSafeForRendering(int handle, int index);
|
||||
|
||||
[[nodiscard]] Impl& State() noexcept;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
@@ -65,6 +66,9 @@ vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noe
|
||||
features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageImageArrayNonUniformIndexing = VK_TRUE;
|
||||
features.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE;
|
||||
features.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE;
|
||||
features.descriptorBindingUpdateUnusedWhilePending = VK_TRUE;
|
||||
features.descriptorBindingPartiallyBound = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -295,6 +299,14 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
check_feature(features12.shaderStorageBufferArrayNonUniformIndexing,
|
||||
"shaderStorageBufferArrayNonUniformIndexing",
|
||||
required_features12.shaderStorageBufferArrayNonUniformIndexing);
|
||||
check_feature(features12.descriptorBindingSampledImageUpdateAfterBind,
|
||||
"descriptorBindingSampledImageUpdateAfterBind",
|
||||
required_features12.descriptorBindingSampledImageUpdateAfterBind);
|
||||
check_feature(features12.descriptorBindingUpdateUnusedWhilePending,
|
||||
"descriptorBindingUpdateUnusedWhilePending",
|
||||
required_features12.descriptorBindingUpdateUnusedWhilePending);
|
||||
check_feature(features12.descriptorBindingPartiallyBound, "descriptorBindingPartiallyBound",
|
||||
required_features12.descriptorBindingPartiallyBound);
|
||||
check_feature(features13.robustImageAccess, "robustImageAccess");
|
||||
check_feature(robustness2.nullDescriptor, "nullDescriptor");
|
||||
check_feature(features13.dynamicRendering, "dynamicRendering",
|
||||
@@ -325,9 +337,13 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
|
||||
check_feature(device_features2.features.shaderImageGatherExtended,
|
||||
"shaderImageGatherExtended");
|
||||
check_feature(device_features2.features.shaderResourceMinLod, "shaderResourceMinLod");
|
||||
|
||||
check_feature(device_features2.features.independentBlend, "independentBlend");
|
||||
check_feature(device_features2.features.tessellationShader, "tessellationShader");
|
||||
check_feature(device_features2.features.drawIndirectFirstInstance,
|
||||
"drawIndirectFirstInstance");
|
||||
check_feature(device_features2.features.multiDrawIndirect, "multiDrawIndirect");
|
||||
|
||||
if (!skip_device) {
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
@@ -492,6 +508,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
maintenance8.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &maintenance8;
|
||||
}
|
||||
const bool shader_clock_extension =
|
||||
HasExtension(device_extensions, VK_KHR_SHADER_CLOCK_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderClockFeaturesKHR shader_clock {};
|
||||
if (shader_clock_extension) {
|
||||
shader_clock.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &shader_clock;
|
||||
}
|
||||
const bool image_atomic_int64_extension =
|
||||
HasExtension(device_extensions, VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT image_atomic_int64 {};
|
||||
@@ -499,6 +522,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
image_atomic_int64.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &image_atomic_int64;
|
||||
}
|
||||
const bool conditional_rendering_extension =
|
||||
HasExtension(device_extensions, VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceConditionalRenderingFeaturesEXT conditional_rendering {};
|
||||
if (conditional_rendering_extension) {
|
||||
conditional_rendering.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &conditional_rendering;
|
||||
}
|
||||
const bool barycentric_extension =
|
||||
HasExtension(device_extensions, VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR supported_barycentric {};
|
||||
@@ -572,6 +602,13 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||
graphics.maintenance8_enabled = maintenance8_extension && maintenance8.maintenance8;
|
||||
// The device clock ticks at timestampPeriod nanoseconds.
|
||||
if (shader_clock_extension && shader_clock.shaderDeviceClock) {
|
||||
graphics.device_clock_period_ps = static_cast<uint32_t>(
|
||||
std::lround(graphics.physical_device_properties.limits.timestampPeriod * 1000.0f));
|
||||
}
|
||||
graphics.conditional_rendering_enabled =
|
||||
conditional_rendering_extension && conditional_rendering.conditionalRendering;
|
||||
LOGF("Vulkan maintenance8 support: %s\n",
|
||||
graphics.maintenance8_enabled ? "true" : "false");
|
||||
LOGF("Vulkan depth feedback support: %s\n",
|
||||
@@ -597,10 +634,15 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.depthClamp = VK_TRUE;
|
||||
#endif
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderStorageImageMultisample =
|
||||
supported_features2.features.shaderStorageImageMultisample;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.shaderResourceMinLod = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.drawIndirectFirstInstance = VK_TRUE;
|
||||
device_features.multiDrawIndirect = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
device_features.shaderClipDistance = VK_TRUE;
|
||||
@@ -609,7 +651,22 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.multiViewport = VK_TRUE;
|
||||
device_features.fillModeNonSolid = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
// Storage image sample counts beyond 1x require the feature.
|
||||
if (!device_features.shaderStorageImageMultisample) {
|
||||
graphics.physical_device_properties.limits.storageImageSampleCounts =
|
||||
vk::SampleCountFlagBits::e1;
|
||||
}
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
// Occlusion queries reset their queries from the host and count samples exactly where they can.
|
||||
graphics.occlusion_queries_enabled =
|
||||
Config::OcclusionQueriesEnabled() && supported_features12.hostQueryReset;
|
||||
graphics.occlusion_query_precise_enabled =
|
||||
graphics.occlusion_queries_enabled && supported_features2.features.occlusionQueryPrecise;
|
||||
features12.hostQueryReset = graphics.occlusion_queries_enabled;
|
||||
device_features.occlusionQueryPrecise = graphics.occlusion_query_precise_enabled;
|
||||
LOGF("Vulkan occlusion queries: %s, precise: %s\n",
|
||||
graphics.occlusion_queries_enabled ? "true" : "false",
|
||||
graphics.occlusion_query_precise_enabled ? "true" : "false");
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
device_features.shaderFloat64 =
|
||||
supported_features2.features.shaderFloat64 &&
|
||||
@@ -670,6 +727,23 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
maintenance8.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &maintenance8;
|
||||
}
|
||||
if (graphics.device_clock_period_ps != 0u) {
|
||||
shader_clock.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &shader_clock;
|
||||
}
|
||||
if (graphics.conditional_rendering_enabled) {
|
||||
conditional_rendering.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &conditional_rendering;
|
||||
}
|
||||
// The renderer only uses the Vulkan 1.3 core dynamic states, but RADV (Mesa 26.3) creates its
|
||||
// PS epilog cache only when this EXT feature is enabled and crashes in vkCmdDraw when a
|
||||
// depth- or stencil-writing fragment shader needs an epilog.
|
||||
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT extended_dynamic_state {};
|
||||
extended_dynamic_state.extendedDynamicState = VK_TRUE;
|
||||
if (HasExtension(device_extensions, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)) {
|
||||
extended_dynamic_state.pNext = const_cast<void*>(create_info.pNext);
|
||||
create_info.pNext = &extended_dynamic_state;
|
||||
}
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
|
||||
@@ -1041,12 +1115,15 @@ void WindowContext::CreateVulkan() {
|
||||
graphic_ctx.memory_budget_ext_enabled = true;
|
||||
}
|
||||
for (const auto* extension: {VK_KHR_MAINTENANCE_8_EXTENSION_NAME,
|
||||
VK_KHR_SHADER_CLOCK_EXTENSION_NAME,
|
||||
VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME,
|
||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME}) {
|
||||
VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME,
|
||||
VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME}) {
|
||||
if (HasExtension(available_extensions, extension)) {
|
||||
device_extensions.push_back(extension);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_SIMD
|
||||
#include "stb_image.h"
|
||||
|
||||
// IWYU pragma: no_include <intrin.h>
|
||||
|
||||
@@ -30,10 +30,6 @@ namespace Libs::Graphics::ShaderRecompiler {
|
||||
|
||||
namespace {
|
||||
|
||||
const char* GetDumpLabel(const CompileOptions& options) {
|
||||
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
|
||||
}
|
||||
|
||||
std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
|
||||
std::string dump = "CFG:\n";
|
||||
dump += cfg;
|
||||
@@ -44,30 +40,17 @@ std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
|
||||
return dump;
|
||||
}
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Local: return "LS";
|
||||
case ShaderType::TessellationControl: return "HS";
|
||||
case ShaderType::TessellationEvaluation: return "TES";
|
||||
case ShaderType::Mesh: return "MS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void LogDispatcherFallback(const CompileOptions& options, const CFG::Graph& cfg, const char* phase) {
|
||||
const auto* block = cfg.FindBlock(cfg.failure_block);
|
||||
const auto start = block != nullptr ? block->start_pc : UINT32_MAX;
|
||||
const auto end = block != nullptr ? block->end_pc : UINT32_MAX;
|
||||
const auto predecessors = block != nullptr ? block->predecessors.size() : 0u;
|
||||
const auto successors = block != nullptr ? block->successors.size() : 0u;
|
||||
LOGF("%s CFG dispatcher fallback: stage=%s hash=0x%016" PRIx64
|
||||
LOGF("ShaderRecompiler %s CFG dispatcher fallback: hash=0x%016" PRIx64
|
||||
" phase=%s failure=%s block=%" PRIu32 " pc=0x%08" PRIx32 "..0x%08" PRIx32 " preds=%" PRIu64
|
||||
" succs=%" PRIu64 " blocks=%" PRIu64 " loops=%" PRIu64 " back_edges=%" PRIu64
|
||||
" reason=%s\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash, phase,
|
||||
StageName(options.stage), options.shader_hash, phase,
|
||||
CFG::FailureKindToString(cfg.failure_kind).c_str(), cfg.failure_block, start, end,
|
||||
static_cast<uint64_t>(predecessors), static_cast<uint64_t>(successors),
|
||||
static_cast<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
|
||||
@@ -484,6 +467,7 @@ Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<c
|
||||
void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
using namespace IR;
|
||||
EXIT_NOT_IMPLEMENTED(options.stage != ShaderType::Compute);
|
||||
source.wave_size = options.wave_size;
|
||||
for (uint32_t i = 0; i < source.decoded.instructions.size(); ++i) {
|
||||
if (source.decoded.instructions[i].opcode != Decoder::Opcode::S_SWAPPC_B64) continue;
|
||||
source.calls.push_back({.instruction = i});
|
||||
@@ -520,7 +504,8 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
};
|
||||
Register = [&](uint32_t before, uint32_t code) -> Value {
|
||||
std::vector<uint32_t> crossed_calls;
|
||||
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code, crossed_calls);
|
||||
const auto index = CFG::FindScalarDefinition(source.decoded, graph, before, code,
|
||||
source.wave_size, crossed_calls);
|
||||
for (const auto crossed: crossed_calls) {
|
||||
std::ranges::find(source.calls, crossed, &ShaderSource::Call::instruction)->preserved_registers.set(code);
|
||||
}
|
||||
@@ -584,14 +569,30 @@ void PrepareCallTargets(ShaderSource& source, const CompileOptions& options) {
|
||||
for (auto& inst: block) {
|
||||
inst.SetParent(nullptr);
|
||||
if (inst.GetOpcode() == ValueOpcode::LoadAddressU32 || inst.GetOpcode() == ValueOpcode::ReadConstBuffer)
|
||||
inst.SetFlags(SrtReadFlags {.index = inst.Flags<MemoryFlags>().index});
|
||||
inst.SetFlags(MemoryFlags {.index = inst.Flags<MemoryFlags>().index});
|
||||
}
|
||||
query.value_storage.splice(query.value_storage.end(), block.Instructions());
|
||||
source.call_targets = std::move(static_cast<ResourcePlan&>(query));
|
||||
std::vector<Value> targets;
|
||||
for (const auto& site: source.calls) targets.insert(targets.end(), site.target.begin(), site.target.end());
|
||||
CompileSrtWalker(source.call_targets, targets);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Local: return "LS";
|
||||
case ShaderType::TessellationControl: return "HS";
|
||||
case ShaderType::TessellationEvaluation: return "TES";
|
||||
case ShaderType::Mesh: return "MS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ShaderSource PrepareShaderSource(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
EXIT_IF(code.empty());
|
||||
ShaderSource source;
|
||||
@@ -617,12 +618,18 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
EXIT_IF(source.unsupported_call_pc != UINT32_MAX);
|
||||
auto& reads = source.reads;
|
||||
reads.clear();
|
||||
IR::SrtReadCapture capture(runtime, reads);
|
||||
const auto clean = IR::CleanRuntime(capture.ObservedRuntime());
|
||||
IR::SrtWalker walker(source.call_targets, clean);
|
||||
std::vector<uint32_t> flat;
|
||||
const bool walked = IR::WalkSrt(source.call_targets, runtime, flat, &reads);
|
||||
const auto read_code = [&](uint64_t address, std::span<uint32_t> words) {
|
||||
if (runtime.read_specialization_memory == nullptr ||
|
||||
!runtime.read_specialization_memory(runtime.userdata, address, words)) return false;
|
||||
reads.emplace_back(address, words.size_bytes());
|
||||
return true;
|
||||
};
|
||||
for (auto& site: source.calls) {
|
||||
uint32_t low = 0, high = 0;
|
||||
if (!walker.Evaluate(site.target[0], low) || !walker.Evaluate(site.target[1], high))
|
||||
if (!walked || !IR::WalkedValue(source.call_targets, site.target[0], low) ||
|
||||
!IR::WalkedValue(source.call_targets, site.target[1], high))
|
||||
EXIT("shader call at pc 0x%08x has an unavailable scalar target",
|
||||
source.decoded.instructions[site.instruction].pc);
|
||||
// SWAPPC reads the old aliased pair and ignores the target's low two bits.
|
||||
@@ -646,7 +653,7 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
break;
|
||||
}
|
||||
linked.observed_function.resize(function.size);
|
||||
if (!clean.read_memory(clean.userdata, source.calls[i].address, linked.observed_function) ||
|
||||
if (!read_code(source.calls[i].address, linked.observed_function) ||
|
||||
!std::ranges::equal(expected, linked.observed_function)) {
|
||||
unchanged = false;
|
||||
break;
|
||||
@@ -676,8 +683,7 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
uint32_t count = 1;
|
||||
Decoder::Instruction inst;
|
||||
for (;;) {
|
||||
if (!clean.read_memory(clean.userdata, address + uint64_t {index} * sizeof(uint32_t),
|
||||
std::span(words).first(count)))
|
||||
if (!read_code(address + uint64_t {index} * sizeof(uint32_t), std::span(words).first(count)))
|
||||
EXIT("shader call at pc 0x%08x cannot read leaf code", call.pc);
|
||||
inst = {};
|
||||
Decoder::DecodeInstruction(words, 0u, inst);
|
||||
@@ -698,10 +704,11 @@ const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtR
|
||||
EXIT_IF(function_pc > UINT32_MAX - function_size);
|
||||
const auto function_offset = static_cast<uint32_t>(linked.code.size());
|
||||
for (auto inst: function) {
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg) ||
|
||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u));
|
||||
EXIT_IF(CFG::MayWriteScalarRegister(inst, call.dst.reg, source.wave_size) ||
|
||||
CFG::MayWriteScalarRegister(inst, call.dst.reg + 1u, source.wave_size));
|
||||
for (uint32_t code = 0; code < site.preserved_registers.size(); ++code) {
|
||||
if (site.preserved_registers[code] && CFG::MayWriteScalarRegister(inst, code))
|
||||
if (site.preserved_registers[code] &&
|
||||
CFG::MayWriteScalarRegister(inst, code, source.wave_size))
|
||||
EXIT("shader call at pc 0x%08x clobbers scalar target dependency s%u", call.pc, code);
|
||||
}
|
||||
linked.code.insert(linked.code.end(), inst.raw, inst.raw + inst.word_count);
|
||||
@@ -756,6 +763,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
static_cast<unsigned>(options.stage));
|
||||
}
|
||||
|
||||
const auto* stage = StageName(options.stage);
|
||||
const auto compile_begin = std::chrono::steady_clock::now();
|
||||
const auto phase_ms = [&compile_begin]() {
|
||||
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
@@ -765,27 +773,25 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
std::string decoded_dump;
|
||||
if (options.dump_ir) {
|
||||
decoded_dump = Decoder::ProgramToString(decoded);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s decoded RDNA2 (early):\n%s", GetDumpLabel(options), decoded_dump.c_str());
|
||||
}
|
||||
LOGF("ShaderRecompiler %s decoded RDNA2:\n%s", stage, decoded_dump.c_str());
|
||||
}
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph\n", GetDumpLabel(options),
|
||||
StageName(options.stage), options.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " CFG BuildGraph\n", stage,
|
||||
options.shader_hash);
|
||||
auto native_cfg = CFG::BuildGraph(decoded);
|
||||
CFG::Graph structured_cfg;
|
||||
auto* selected_cfg = &native_cfg;
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " CFG BuildGraph blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " back_edges=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
stage, options.shader_hash,
|
||||
static_cast<uint64_t>(native_cfg.blocks.size()),
|
||||
static_cast<uint64_t>(native_cfg.natural_loops.size()),
|
||||
static_cast<uint64_t>(native_cfg.back_edges.size()), phase_ms());
|
||||
if (native_cfg.irreducible) {
|
||||
LogDispatcherFallback(options, native_cfg, "build");
|
||||
} else {
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " CFG Structurize\n", stage,
|
||||
options.shader_hash);
|
||||
structured_cfg = CFG::Structurize(native_cfg);
|
||||
if (structured_cfg.unsupported) {
|
||||
native_cfg.unsupported = true;
|
||||
@@ -795,12 +801,12 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
LogDispatcherFallback(options, native_cfg, "structurize");
|
||||
} else {
|
||||
selected_cfg = &structured_cfg;
|
||||
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
LOGF("ShaderRecompiler %s structured CFG success: blocks=%" PRIu64 "\n", stage,
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()));
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " CFG Structurize blocks=%" PRIu64
|
||||
" loops=%" PRIu64 " elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
stage, options.shader_hash,
|
||||
static_cast<uint64_t>(selected_cfg->blocks.size()),
|
||||
static_cast<uint64_t>(selected_cfg->natural_loops.size()), phase_ms());
|
||||
}
|
||||
@@ -813,7 +819,7 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
decoded, *options.input_info.vertex, options.user_data_base,
|
||||
static_cast<uint32_t>(options.user_data.size()), options.wave_size);
|
||||
if (!embedded_fetch.loads.empty()) {
|
||||
LOGF("%s embedded vertex fetch plan: detected=%" PRIu64 "\n", GetDumpLabel(options),
|
||||
LOGF("ShaderRecompiler %s embedded vertex fetch plan: detected=%" PRIu64 "\n", stage,
|
||||
static_cast<uint64_t>(embedded_fetch.loads.size()));
|
||||
}
|
||||
}
|
||||
@@ -826,12 +832,12 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
.input_info = options.input_info,
|
||||
.embedded_fetch = embedded_fetch.loads.empty() ? nullptr : &embedded_fetch,
|
||||
};
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " IR TranslateProgram\n", stage,
|
||||
options.shader_hash);
|
||||
auto ir = Frontend::TranslateProgram(decoded, cfg, translate_options);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " IR TranslateProgram blocks=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
stage, options.shader_hash,
|
||||
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
|
||||
IR::RewriteToSsa(ir.blocks);
|
||||
const auto simplify = [&ir] {
|
||||
@@ -842,8 +848,9 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
simplify();
|
||||
const auto read_lane_stats = IR::EliminateReadLane(ir, ir.wave_size);
|
||||
if (read_lane_stats.rewritten_reads != 0 || read_lane_stats.rewritten_reductions != 0) {
|
||||
LOGF("%s read-lane elimination: reads=%" PRIu32 " reductions=%" PRIu32 "\n",
|
||||
GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.rewritten_reductions);
|
||||
LOGF("ShaderRecompiler %s read-lane elimination: reads=%" PRIu32
|
||||
" reductions=%" PRIu32 "\n",
|
||||
stage, read_lane_stats.rewritten_reads, read_lane_stats.rewritten_reductions);
|
||||
}
|
||||
// Cleanup also exposes profitable folds by removing dead predicate consumers.
|
||||
simplify();
|
||||
@@ -857,10 +864,8 @@ TranslateResult TranslateProgram(const Decoder::Program& decoded, const CompileO
|
||||
std::string cfg_dump;
|
||||
if (options.dump_ir) {
|
||||
cfg_dump = CFG::GraphToString(cfg);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR before resource tracking:\n%s", GetDumpLabel(options),
|
||||
MakeIrDump(cfg_dump, ir).c_str());
|
||||
}
|
||||
LOGF("ShaderRecompiler %s native IR before resource tracking:\n%s", stage,
|
||||
MakeIrDump(cfg_dump, ir).c_str());
|
||||
}
|
||||
IR::TrackResources(ir, decoded, native_cfg, options.input_info);
|
||||
TranslateResult result;
|
||||
@@ -877,6 +882,7 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
uint32_t push_data_start_dword) {
|
||||
const auto emit_begin = std::chrono::steady_clock::now();
|
||||
auto& ir = translated.program;
|
||||
const auto* stage = StageName(ir.stage);
|
||||
IR::ApplyResourceSpecialization(ir, specialization);
|
||||
// The resource plan owns host descriptor evaluation now. Keep only dependencies consumed
|
||||
// by GPU memory operations; bound descriptor dwords must not retain shader instructions.
|
||||
@@ -923,19 +929,16 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(translated.cfg_dump, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
LOGF("ShaderRecompiler %s native IR:\n%s", stage, ir_dump.c_str());
|
||||
}
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash);
|
||||
LOGF("ShaderRecompiler %s phase begin: hash=0x%016" PRIx64 " SPIR-V EmitProgram\n", stage,
|
||||
ir.shader_hash);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info, push_data_start_dword, *options.profile,
|
||||
options.emit_debug_info);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(ir.stage), ir.shader_hash,
|
||||
static_cast<uint64_t>(spirv.size()),
|
||||
LOGF("ShaderRecompiler %s phase end: hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" wave_size=%u elapsed_ms=%" PRIu64 "\n",
|
||||
stage, ir.shader_hash, static_cast<uint64_t>(spirv.size()), ir.wave_size,
|
||||
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - emit_begin)
|
||||
.count()));
|
||||
|
||||
@@ -22,10 +22,8 @@ struct CompileOptions {
|
||||
uint32_t user_data_base = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
bool emit_debug_info = false;
|
||||
const Spirv::Profile* profile = &Spirv::DefaultProfile;
|
||||
const char* dump_label = nullptr;
|
||||
std::span<const uint32_t> user_data;
|
||||
std::span<const uint32_t> back_code;
|
||||
ShaderStageInputInfo input_info;
|
||||
@@ -64,11 +62,13 @@ struct ShaderSource {
|
||||
IR::ResourcePlan call_targets;
|
||||
std::vector<Call> calls;
|
||||
uint32_t unsupported_call_pc = UINT32_MAX;
|
||||
uint32_t wave_size = 64;
|
||||
std::optional<Linked> linked;
|
||||
uint64_t revision = 0;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> reads;
|
||||
};
|
||||
|
||||
[[nodiscard]] const char* StageName(ShaderType stage);
|
||||
[[nodiscard]] ShaderSource PrepareShaderSource(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
const Decoder::Program& RefreshShaderSource(ShaderSource& source, const IR::SrtRuntime& runtime);
|
||||
|
||||
@@ -169,6 +169,9 @@ uint32_t Builder::AddString(const char* text) {
|
||||
}
|
||||
|
||||
void Builder::AddName(uint32_t target, const char* name) {
|
||||
if (!m_emit_names) {
|
||||
return;
|
||||
}
|
||||
std::vector<uint32_t> operands = {target};
|
||||
AppendString(operands, name);
|
||||
AppendInstruction(m_debug, spv::OpName, operands);
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t AddString(const char* text);
|
||||
void EnableNames() { m_emit_names = true; }
|
||||
void AddName(uint32_t target, const char* name);
|
||||
template <typename... Args>
|
||||
void AddAnnotation(spv::Op opcode, const Args&... operands) {
|
||||
@@ -159,6 +160,7 @@ private:
|
||||
std::map<std::string, uint32_t> m_import_ids;
|
||||
std::map<std::vector<uint32_t>, uint32_t> m_declaration_ids;
|
||||
size_t m_unpatched_phi_incomings = 0;
|
||||
bool m_emit_names = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -161,6 +161,7 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
|
||||
if (!kind.has_value()) {
|
||||
Fail(program, "native shader plan has an invalid image class");
|
||||
}
|
||||
if (image.heap) continue;
|
||||
if (image.mip_count == 0u ||
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
Fail(program, "native shader plan has an invalid image mip descriptor count");
|
||||
@@ -226,6 +227,7 @@ std::vector<uint32_t> EmitProgram(IR::Program& program, ShaderStageInputInfo inp
|
||||
std::snprintf(name, sizeof(name), "rdna2-%016" PRIx64 "-block-byte-pc-plus-one",
|
||||
program.shader_hash);
|
||||
state.debug_source = state.builder.AddString(name);
|
||||
state.builder.EnableNames();
|
||||
}
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
struct Profile {
|
||||
bool maintenance8 = false; // Dynamic texel offsets on image samples.
|
||||
bool native_float_min_max = false; // NMin/NMax give the guest's NaN and signed-zero results.
|
||||
// OpReadClockKHR Device tick in picoseconds; 0 without shaderDeviceClock.
|
||||
uint32_t device_clock_period_ps = 0;
|
||||
};
|
||||
|
||||
inline constexpr Profile DefaultProfile {};
|
||||
|
||||
@@ -54,9 +54,30 @@ uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t value) {
|
||||
uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
// Clamping first keeps the truncating conversion defined; NaN and values past the largest
|
||||
// float below the integer limit are selected afterwards.
|
||||
const auto clamped =
|
||||
EmitExt(state, TypeF32(state), GLSLstd450NClamp,
|
||||
{src, ConstantF32(state, signed_value ? 0xcf000000u : 0u),
|
||||
ConstantF32(state, signed_value ? 0x4effffffu : 0x4f7fffffu)});
|
||||
const auto converted = signed_value
|
||||
? Unary(state, spv::OpBitcast, TypeU32(state),
|
||||
Unary(state, spv::OpConvertFToS, TypeI32(state), clamped))
|
||||
: Unary(state, spv::OpConvertFToU, TypeU32(state), clamped);
|
||||
const auto above = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, signed_value ? 0x4f000000u : 0x4f800000u));
|
||||
const auto high =
|
||||
Select(state, TypeU32(state), above,
|
||||
ConstantU32(state, signed_value ? 0x7fffffffu : 0xffffffffu), converted);
|
||||
return Select(state, TypeU32(state), Unary(state, spv::OpIsNan, TypeBool(state), src),
|
||||
ConstantU32(state, 0), high);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits) {
|
||||
// Native half conversion causes regressions; use compact integer RTZ conversion.
|
||||
const auto bits = EmitBitcastF32ToU32(state, value);
|
||||
const auto abs = EmitAndConstant(state, bits, 0x7fffffffu);
|
||||
const auto sign = EmitAndConstant(
|
||||
state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), bits, ConstantU32(state, 16)),
|
||||
@@ -92,28 +113,6 @@ uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t value) {
|
||||
return EmitOrU32(state, sign, magnitude);
|
||||
}
|
||||
|
||||
uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
// Clamping first keeps the truncating conversion defined; NaN and values past the largest
|
||||
// float below the integer limit are selected afterwards.
|
||||
const auto clamped =
|
||||
EmitExt(state, TypeF32(state), GLSLstd450NClamp,
|
||||
{src, ConstantF32(state, signed_value ? 0xcf000000u : 0u),
|
||||
ConstantF32(state, signed_value ? 0x4effffffu : 0x4f7fffffu)});
|
||||
const auto converted = signed_value
|
||||
? Unary(state, spv::OpBitcast, TypeU32(state),
|
||||
Unary(state, spv::OpConvertFToS, TypeI32(state), clamped))
|
||||
: Unary(state, spv::OpConvertFToU, TypeU32(state), clamped);
|
||||
const auto above = Binary(state, spv::OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, signed_value ? 0x4f000000u : 0x4f800000u));
|
||||
const auto high =
|
||||
Select(state, TypeU32(state), above,
|
||||
ConstantU32(state, signed_value ? 0x7fffffffu : 0xffffffffu), converted);
|
||||
return Select(state, TypeU32(state), Unary(state, spv::OpIsNan, TypeBool(state), src),
|
||||
ConstantU32(state, 0), high);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t EmitFPFma32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
const auto result = EmitGlsl<GLSLstd450Fma, IR::Type::F32>(state, a, b, c);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, result, spv::DecorationNoContraction);
|
||||
@@ -199,7 +198,8 @@ uint32_t EmitCompositeExtractU32x2(EmitterState& state, uint32_t arg0, IR::Value
|
||||
|
||||
uint32_t EmitPackFloat2x16Rtz(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
return EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(
|
||||
state, EmitF32ToF16RtzBits(state, arg0), EmitF32ToF16RtzBits(state, arg1),
|
||||
state, EmitF32BitsToF16RtzBits(state, EmitBitcastF32ToU32(state, arg0)),
|
||||
EmitF32BitsToF16RtzBits(state, EmitBitcastF32ToU32(state, arg1)),
|
||||
ConstantU32(state, 16), ConstantU32(state, 16));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,13 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr) {
|
||||
ShaderPixelParameterIsCustom(*state.input_info.pixel, attr);
|
||||
}
|
||||
|
||||
uint32_t MrtOutputType(EmitterState& state, uint32_t index) {
|
||||
const auto& pixel = *state.input_info.pixel;
|
||||
if ((pixel.target_sint_mask & (1u << index)) != 0u) return TypeI32Vector(state, 4);
|
||||
if ((pixel.target_uint_mask & (1u << index)) != 0u) return TypeU32Vector(state, 4);
|
||||
return TypeF32Vector(state, 4);
|
||||
}
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp) {
|
||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index == 0) {
|
||||
return state.per_vertex_variable;
|
||||
@@ -120,7 +127,14 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
} else {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
if (image.dimension == ImageDimension::Dim1D || image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(sampled == 1 ? spv::CapabilitySampled1D
|
||||
: spv::CapabilityImage1D);
|
||||
}
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
if (sampled == 2 && info.multisampled != 0u) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageMultisample);
|
||||
}
|
||||
const auto scalar_type =
|
||||
image.atomic64 ? TypeU64(state) : ImageScalarType(state, image.numeric_class);
|
||||
return state.builder.Type(spv::OpTypeImage, scalar_type,
|
||||
@@ -144,14 +158,20 @@ void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id, spv::Capabil
|
||||
state.builder.RequireCapability(capability);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
|
||||
}
|
||||
|
||||
ImageDefinition& ImageDescriptorDefinition(EmitterState& state, const IR::ImageResource& image,
|
||||
IR::DescriptorBindingKind kind) {
|
||||
return (image.heap ? state.heap_images : state.images)[IR::ImageBindingIndex(kind)];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip, array_index);
|
||||
const auto image = state.builder.AllocateId();
|
||||
const auto kind = *IR::DescriptorBindingForImage(state.program.info.images[resource]);
|
||||
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
|
||||
const auto& resource_info = state.program.info.images[resource];
|
||||
const auto kind = *IR::DescriptorBindingForImage(resource_info);
|
||||
state.builder.AddFunction(spv::OpLoad, ImageDescriptorDefinition(state, resource_info, kind).type,
|
||||
image, pointer);
|
||||
if (array_index != 0u) {
|
||||
DecorateNonUniformDescriptor(state, image,
|
||||
@@ -184,13 +204,13 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto kind = *IR::DescriptorBindingForImage(image_resource);
|
||||
auto& sampled_type = state.images[IR::ImageBindingIndex(kind)].sampled_type;
|
||||
if (sampled_type == 0) {
|
||||
sampled_type = state.builder.Type(
|
||||
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
|
||||
auto& definition = ImageDescriptorDefinition(state, image_resource,
|
||||
*IR::DescriptorBindingForImage(image_resource));
|
||||
if (definition.sampled_type == 0) {
|
||||
definition.sampled_type = state.builder.Type(spv::OpTypeSampledImage, definition.type);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
state.builder.AddFunction(spv::OpSampledImage, definition.sampled_type, sampled_image, image,
|
||||
sampler_id);
|
||||
if (array_index != 0u || sampler_dynamic) {
|
||||
DecorateNonUniformDescriptor(state, sampled_image, spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
}
|
||||
@@ -202,26 +222,31 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(mip >= image.mip_count);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
EXIT_IF(!kind.has_value() || (image.heap && array_index == 0u));
|
||||
if (array_index == 0u) {
|
||||
array_index = ConstantU32(state, image.descriptor_index + mip);
|
||||
}
|
||||
const auto& definition = state.images[IR::ImageBindingIndex(*kind)];
|
||||
const auto& definition = ImageDescriptorDefinition(state, image, *kind);
|
||||
return DescriptorElementPointer(state, definition.pointer_type, definition.variable,
|
||||
array_index, *kind, resource,
|
||||
"image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index) {
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto EmitWrite = [&](uint32_t mip) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, LoadImageDescriptor(state, resource, mip, array_index),
|
||||
coord, texel);
|
||||
const auto descriptor = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
if (sample == 0u) {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel);
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpImageWrite, descriptor, coord, texel,
|
||||
spv::ImageOperandsSampleMask, sample);
|
||||
}
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::Dynamic) {
|
||||
EmitWrite(0);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <optional>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
@@ -113,6 +114,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 +145,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 +291,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 +331,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 +347,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 +363,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 +564,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 +712,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 +819,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 +836,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 +868,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 +900,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 +927,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");
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ ImageSampleLayout Layout(const IR::MemoryInfo& mem) {
|
||||
layout.coord = cursor;
|
||||
cursor += info.coordinate_components;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLodClamp)) layout.clamp = cursor++;
|
||||
return layout;
|
||||
}
|
||||
|
||||
@@ -389,6 +390,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
||||
const auto format = image.conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
if (IR::ReadsDepthBits(image)) return info;
|
||||
const bool raw_signed = IR::RawSignedStorageFormat(image, format) != Prospero::BufferFormat::kInvalid;
|
||||
EXIT_IF((Prospero::RemapTextureFormat(format) == format && !raw_signed) || info.component_count == 0u ||
|
||||
info.component_count > 4u);
|
||||
@@ -424,6 +426,7 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
|
||||
const auto numeric_class = ctx.state.program.info.images[mem.resource].numeric_class;
|
||||
const auto scalar_type = ImageScalarType(ctx.state, numeric_class);
|
||||
const bool integer = info.packed_bitfield || info.type == Format::ComponentType::Uint;
|
||||
uint32_t packed = 0;
|
||||
if (info.packed_bitfield) {
|
||||
packed = ctx.state.builder.AllocateId();
|
||||
@@ -441,8 +444,16 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, scalar_type,
|
||||
components[component], texel, component);
|
||||
// UNorm backing preserves the guest's filtering; scaling precedes swizzle constants.
|
||||
const auto unorm_max = static_cast<float>((1u << info.component_bits[component]) - 1u);
|
||||
components[component] = Binary(ctx.state, spv::OpFMul, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 255.0f));
|
||||
ConstantF32Value(ctx.state, unorm_max));
|
||||
if (integer) {
|
||||
// Depth bits: round back to the stored UNorm code.
|
||||
components[component] =
|
||||
Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
|
||||
Binary(ctx.state, spv::OpFAdd, scalar_type, components[component],
|
||||
ConstantF32Value(ctx.state, 0.5f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t component = info.component_count; component < 4u; component++) {
|
||||
@@ -454,20 +465,19 @@ uint32_t UnpackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint
|
||||
for (uint32_t component = 0; component < 4u; component++) {
|
||||
const auto selector = (swizzle >> (component * 3u)) & 7u;
|
||||
if (selector == 1u) {
|
||||
selected[component] = info.packed_bitfield
|
||||
selected[component] = integer
|
||||
? ConstantU32(ctx.state, Format::FormattedConstantBits(info, Format::FormattedSourceKind::One))
|
||||
: ConstantF32Value(ctx.state, 1.0f);
|
||||
} else if (selector >= 4u) {
|
||||
selected[component] = components[selector - 4u];
|
||||
} else {
|
||||
selected[component] = info.packed_bitfield ? ConstantU32(ctx.state, 0u)
|
||||
: ZeroF32(ctx.state);
|
||||
selected[component] = integer ? ConstantU32(ctx.state, 0u) : ZeroF32(ctx.state);
|
||||
}
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeConstruct,
|
||||
ImageVectorType(ctx.state, numeric_class, 4), result, selected[0],
|
||||
selected[1], selected[2], selected[3]);
|
||||
integer ? TypeU32Vector(ctx.state, 4) : TypeF32Vector(ctx.state, 4),
|
||||
result, selected[0], selected[1], selected[2], selected[3]);
|
||||
return info.type == Format::ComponentType::Unorm
|
||||
? Unary(ctx.state, spv::OpBitcast, TypeF32Vector(ctx.state, 4), result) : result;
|
||||
}
|
||||
@@ -691,6 +701,21 @@ auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t resul
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
}
|
||||
if (image.heap) {
|
||||
const auto entry = EmitImageHeapEntry(state, key, image.indirect_mapping_offset);
|
||||
const auto kinds = static_cast<uint32_t>(image.indirect_resources.size()) - 1u;
|
||||
// One kind leaves every slot word a bare element.
|
||||
if (kinds == 1u) return emit(image.indirect_resources[1], entry);
|
||||
const auto element = Binary(state, spv::OpBitwiseAnd, TypeU32(state), entry,
|
||||
ConstantU32(state, (1u << IR::ImageHeapKindShift) - 1u));
|
||||
const auto EmitKind = [&](uint32_t kind) {
|
||||
return emit(image.indirect_resources[kind + 1u], element);
|
||||
};
|
||||
return EmitIndexSwitch(state,
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), entry,
|
||||
ConstantU32(state, IR::ImageHeapKindShift)),
|
||||
kinds, result_type, EmitKind);
|
||||
}
|
||||
const auto selected = EmitIndirectResourceIndex(
|
||||
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
|
||||
const auto SameType = [&](uint32_t lhs, uint32_t rhs) {
|
||||
@@ -870,14 +895,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(spv::CapabilityImageQuery);
|
||||
const auto dimension = image.dimension;
|
||||
const auto sampled = MakeSampledImage(state, mem.resource,
|
||||
LoadSamplerDescriptor(state, mem.sampler));
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
|
||||
image.cube));
|
||||
const auto sampler = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto lod = EmitImageAccess(ctx, inst, TypeF32Vector(state, 2),
|
||||
[&](uint32_t resource, uint32_t array_index) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto query = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpImageQueryLod, TypeF32Vector(state, 2), query,
|
||||
MakeSampledImage(state, resource, sampler, 0u, array_index),
|
||||
CoordF32(ctx, mem, *address, 0,
|
||||
ImageDimensionInfoFor(candidate.dimension).spatial_components,
|
||||
candidate.cube));
|
||||
return query;
|
||||
});
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
@@ -932,9 +962,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto mip_lod = candidate.mip_mode == IR::ImageMipMode::Dynamic
|
||||
? LodU32(ctx, mem, *address, mem.image_dimension) : 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, candidate.dimension);
|
||||
const auto& dimension_info = ImageDimensionInfoFor(candidate.dimension);
|
||||
const auto sample = dimension_info.multisampled != 0u
|
||||
? AddressU32(ctx, mem, *address, dimension_info.coordinate_components) : 0u;
|
||||
const auto texel = StoreTexel(ctx, selected, ctx.Arg(inst, 2),
|
||||
candidate.numeric_class == Prospero::TextureNumericClass::Uint);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index);
|
||||
EmitStorageImageWrite(state, resource, mip_lod, coord, texel, array_index, sample);
|
||||
});
|
||||
});
|
||||
return;
|
||||
@@ -1037,7 +1070,12 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
}
|
||||
std::array<uint32_t, 4> operands {};
|
||||
// _cl clamps the computed LOD from below.
|
||||
uint32_t min_lod = 0;
|
||||
if (layout.clamp != NoImageComponent) {
|
||||
min_lod = AddressF32(ctx, mem, *address, layout.clamp);
|
||||
}
|
||||
std::array<uint32_t, 5> operands {};
|
||||
size_t operand_count = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
operands[0] = spv::ImageOperandsGradMask;
|
||||
@@ -1049,6 +1087,11 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
||||
}
|
||||
// SPIR-V takes MinLod only with implicit LOD or Grad.
|
||||
if (min_lod != 0) {
|
||||
lod = EmitGlsl<GLSLstd450FMax, IR::Type::F32>(state, lod, min_lod);
|
||||
min_lod = 0;
|
||||
}
|
||||
operands[0] = spv::ImageOperandsLodMask;
|
||||
operands[1] = lod;
|
||||
operand_count = 2;
|
||||
@@ -1072,6 +1115,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);
|
||||
|
||||
@@ -112,6 +112,7 @@ struct EmitterState {
|
||||
uint32_t lane_half = 0;
|
||||
std::array<BufferDefinition, 4> storage_buffers {};
|
||||
std::vector<uint32_t> memory_byte_offsets;
|
||||
std::vector<uint32_t> memory_byte_sizes;
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
@@ -130,6 +131,7 @@ struct EmitterState {
|
||||
uint32_t lds_u64_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> images {};
|
||||
std::array<ImageDefinition, IR::ImageBindingCount> heap_images {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t sampler_type = 0;
|
||||
uint32_t sampler_pointer_type = 0;
|
||||
@@ -144,6 +146,7 @@ struct EmitterState {
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t pixel_valid_mask_variable = 0;
|
||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||
uint32_t helper_invocation_variable = 0;
|
||||
uint32_t per_vertex_variable = 0;
|
||||
uint32_t point_size_variable = 0;
|
||||
uint32_t clip_distance_variable = 0;
|
||||
@@ -281,6 +284,7 @@ struct ImageSampleLayout {
|
||||
uint32_t bias = NoImageComponent;
|
||||
uint32_t coord = 0;
|
||||
uint32_t lod = NoImageComponent;
|
||||
uint32_t clamp = NoImageComponent;
|
||||
uint32_t grad_x = NoImageComponent;
|
||||
uint32_t grad_y = NoImageComponent;
|
||||
};
|
||||
@@ -304,6 +308,7 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
|
||||
|
||||
|
||||
uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo& exp);
|
||||
uint32_t MrtOutputType(EmitterState& state, uint32_t index);
|
||||
|
||||
uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||
|
||||
@@ -339,8 +344,9 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
uint32_t mip = 0, uint32_t array_index = 0,
|
||||
bool sampler_dynamic = false);
|
||||
|
||||
// sample is 0 (no id) for single-sampled images.
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel, uint32_t array_index);
|
||||
uint32_t texel, uint32_t array_index, uint32_t sample);
|
||||
|
||||
spv::ExecutionModel ExecutionModelForStage(ShaderType stage);
|
||||
|
||||
@@ -412,7 +418,7 @@ Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::
|
||||
|
||||
uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resource);
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state);
|
||||
void EmitMemoryBindings(EmitterState& state);
|
||||
|
||||
uint32_t LdsDwordCount(const EmitterState& state);
|
||||
void EnsureLdsStorage(EmitterState& state);
|
||||
@@ -430,6 +436,8 @@ struct MemoryResourceAccess {
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource);
|
||||
// Slot word of an image heap byte offset; offsets past the heap select the null element.
|
||||
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset);
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t array_index = 0u);
|
||||
|
||||
@@ -527,6 +535,7 @@ inline constexpr auto EmitFNegateValue = EmitNative<spv::OpFNegate, IR::Type::F3
|
||||
inline constexpr auto EmitFAbsValue = EmitGlsl<GLSLstd450FAbs, IR::Type::F32, uint32_t>;
|
||||
|
||||
uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits);
|
||||
uint32_t EmitF32BitsToF16RtzBits(EmitterState& state, uint32_t bits);
|
||||
|
||||
void EmitProgram(EmitterState& state);
|
||||
|
||||
|
||||
@@ -1541,16 +1541,16 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto index =
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto low = state.builder.AllocateId();
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
|
||||
// Elect and count from the guest EXEC words, which V_MBCNT also ranks lanes by.
|
||||
const auto low = ctx.Arg(inst, 1);
|
||||
const auto high = ctx.Arg(inst, 2);
|
||||
const auto count = Binary(state, spv::OpIAdd, TypeU32(state),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), low),
|
||||
Unary(state, spv::OpBitCount, TypeU32(state), high));
|
||||
const auto first = ctx.FirstLane(ballot);
|
||||
const auto mask = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 4), mask, low, high,
|
||||
ConstantU32(state, 0), ConstantU32(state, 0));
|
||||
const auto first = ctx.FirstLane(mask);
|
||||
const auto source_lane =
|
||||
state.lane_count == 2
|
||||
? Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 31))
|
||||
@@ -1564,9 +1564,7 @@ uint32_t EmitAppendConsume(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto condition = AndCondition(
|
||||
state, is_first,
|
||||
AndCondition(state,
|
||||
state.lane_count == 2 ? Binary(state, spv::OpINotEqual, TypeBool(state), count,
|
||||
ConstantU32(state, 0))
|
||||
: exec,
|
||||
Binary(state, spv::OpINotEqual, TypeBool(state), count, ConstantU32(state, 0)),
|
||||
bounds));
|
||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||
const auto value = state.builder.AllocateId();
|
||||
|
||||
@@ -38,19 +38,39 @@ uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t LoadFlattenedSrt(EmitterState& state, uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.flattened_srt_variable, ConstantU32(state, 0), index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset) {
|
||||
const auto Mapping = [&](uint32_t word) {
|
||||
return LoadFlattenedSrt(state, ConstantU32(state, mapping_offset + word));
|
||||
};
|
||||
// Keys past the table select its trailing null slot.
|
||||
const auto index = EmitUMin32(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
|
||||
ConstantU32(state, 2u)),
|
||||
Mapping(0u));
|
||||
const auto offset = Unary(state, spv::OpUConvert, TypeU64(state),
|
||||
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index,
|
||||
ConstantU32(state, 2u)));
|
||||
const auto pointer = Unary(state, spv::OpConvertUToPtr, TypePhysicalU32Pointer(state),
|
||||
Binary(state, spv::OpIAdd, TypeU64(state),
|
||||
PackU64(state, Mapping(1u), Mapping(2u)), offset));
|
||||
const auto entry = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), entry, pointer,
|
||||
spv::MemoryAccessAlignedMask, 4u);
|
||||
return entry;
|
||||
}
|
||||
|
||||
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
|
||||
uint32_t search_iterations, uint32_t default_resource) {
|
||||
const auto LoadMapping = [&](uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
|
||||
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
|
||||
index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
|
||||
return value;
|
||||
};
|
||||
const auto mapping = ConstantU32(state, mapping_offset);
|
||||
const auto count = LoadMapping(mapping);
|
||||
const auto count = LoadFlattenedSrt(state, mapping);
|
||||
if (search_iterations == 0u) {
|
||||
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
|
||||
ConstantU32(state, 2u));
|
||||
@@ -58,7 +78,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
const auto safe = Select(state, TypeU32(state), valid, index, ConstantU32(state, 0u));
|
||||
const auto entry = Binary(state, spv::OpIAdd, TypeU32(state), safe,
|
||||
ConstantU32(state, mapping_offset + 1u));
|
||||
return Select(state, TypeU32(state), valid, LoadMapping(entry),
|
||||
return Select(state, TypeU32(state), valid, LoadFlattenedSrt(state, entry),
|
||||
ConstantU32(state, default_resource));
|
||||
}
|
||||
const auto entry_at = [&](uint32_t index) {
|
||||
@@ -76,7 +96,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
ConstantU32(state, 1u));
|
||||
const auto probe = Select(state, TypeU32(state), active, mid, ConstantU32(state, 0u));
|
||||
const auto less = Binary(state, spv::OpULessThan, TypeBool(state),
|
||||
LoadMapping(entry_at(probe)), key);
|
||||
LoadFlattenedSrt(state, entry_at(probe)), key);
|
||||
low = Select(state, TypeU32(state),
|
||||
Binary(state, spv::OpLogicalAnd, TypeBool(state), active, less),
|
||||
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low);
|
||||
@@ -87,9 +107,10 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
|
||||
const auto in_range = Binary(state, spv::OpULessThan, TypeBool(state), low, count);
|
||||
const auto entry = entry_at(Select(state, TypeU32(state), in_range, low, ConstantU32(state, 0u)));
|
||||
const auto match = Binary(state, spv::OpLogicalAnd, TypeBool(state), in_range,
|
||||
Binary(state, spv::OpIEqual, TypeBool(state), LoadMapping(entry), key));
|
||||
const auto resource = LoadMapping(Binary(state, spv::OpIAdd, TypeU32(state), entry,
|
||||
ConstantU32(state, 1u)));
|
||||
Binary(state, spv::OpIEqual, TypeBool(state),
|
||||
LoadFlattenedSrt(state, entry), key));
|
||||
const auto resource = LoadFlattenedSrt(
|
||||
state, Binary(state, spv::OpIAdd, TypeU32(state), entry, ConstantU32(state, 1u)));
|
||||
return Select(state, TypeU32(state), match, resource, ConstantU32(state, default_resource));
|
||||
}
|
||||
|
||||
@@ -133,9 +154,10 @@ uint32_t SharedIndirectBufferResource(const EmitterState& state, uint32_t resour
|
||||
return representative;
|
||||
}
|
||||
|
||||
void EmitMemoryOffsets(EmitterState& state) {
|
||||
void EmitMemoryBindings(EmitterState& state) {
|
||||
const auto count = state.program.bindings.descriptor_counts[static_cast<size_t>(IR::DescriptorBindingKind::Buffers)];
|
||||
state.memory_byte_offsets.resize(count);
|
||||
state.memory_byte_sizes.resize(count);
|
||||
std::vector<bool> dynamic_offsets(count);
|
||||
for (uint32_t resource = 0; resource < state.program.info.buffers.size(); ++resource) {
|
||||
if (SharedIndirectBufferResource(state, resource) == UINT32_MAX) continue;
|
||||
@@ -153,6 +175,8 @@ void EmitMemoryOffsets(EmitterState& state) {
|
||||
if (dynamic_offsets[i + lane]) continue;
|
||||
state.memory_byte_offsets[i + lane] = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, ConstantU32(state, lane * 8u), ConstantU32(state, 8u));
|
||||
state.memory_byte_sizes[i + lane] =
|
||||
EmitShaderDataDwordLoad(state, state.program.bindings.MemorySizeDword() + i + lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,22 +260,28 @@ MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBufferArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, access.object_pointer, spv::DecorationNonUniform);
|
||||
const auto offsets_start = state.program.bindings.memory_offset_dword +
|
||||
(state.program.bindings.UsesPushData() ? state.program.bindings.push_data_start_dword : 0u);
|
||||
const auto& bindings = state.program.bindings;
|
||||
const auto data_start = bindings.UsesPushData() ? bindings.push_data_start_dword : 0u;
|
||||
const auto word = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||
state, ConstantU32(state, offsets_start),
|
||||
state, ConstantU32(state, bindings.memory_offset_dword + data_start),
|
||||
Binary(state, spv::OpShiftRightLogical, TypeU32(state), array_index, ConstantU32(state, 2))));
|
||||
const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, spv::OpBitwiseAnd, TypeU32(state), array_index, ConstantU32(state, 3)),
|
||||
ConstantU32(state, 3));
|
||||
access.byte_offset = EmitNative<spv::OpBitFieldUExtract, IR::Type::U32>(
|
||||
state, word, shift, ConstantU32(state, 8));
|
||||
access.length = EmitShaderDataDwordLoadAt(state, EmitAddU32(
|
||||
state, ConstantU32(state, bindings.MemorySizeDword() + data_start), array_index));
|
||||
} else {
|
||||
access.byte_offset = state.memory_byte_offsets[static_index];
|
||||
access.length = state.memory_byte_sizes[static_index];
|
||||
}
|
||||
// storage_buffers[i] views the binding as 1 << i byte elements.
|
||||
const auto shift = static_cast<uint32_t>(&buffer - state.storage_buffers.data());
|
||||
if (shift != 0u) {
|
||||
access.length = Binary(state, spv::OpShiftRightLogical, TypeU32(state), access.length,
|
||||
ConstantU32(state, shift));
|
||||
}
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpArrayLength, TypeU32(state), access.length,
|
||||
access.object_pointer, 0);
|
||||
return access;
|
||||
}
|
||||
|
||||
@@ -451,6 +481,17 @@ uint32_t EncodeFormattedStoreComponent(EmitterState& state, const Format::Buffer
|
||||
UnormRounding rounding) {
|
||||
EXIT_IF(rounding != UnormRounding::RoundEven && info.type != Format::ComponentType::Unorm);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.type == Format::ComponentType::Float && bits < 16u) {
|
||||
// Truncate to f16, whose bias the unsigned 11/10-bit floats share, and drop mantissa
|
||||
// bits; negatives and NaN, whose f16 bits lie above +inf, store zero.
|
||||
const auto half = data_bits == 16u ? EmitAndConstant(state, data, 0xffffu)
|
||||
: EmitF32BitsToF16RtzBits(state, data);
|
||||
return Select(state, TypeU32(state),
|
||||
EmitCompareU32Constant(state, spv::OpUGreaterThan, half, 0x7c00u),
|
||||
ConstantU32(state, 0),
|
||||
EmitBinaryU32(state, spv::OpShiftRightLogical, half,
|
||||
ConstantU32(state, 15u - bits)));
|
||||
}
|
||||
if (data_bits == 16u) {
|
||||
if (info.type == Format::ComponentType::Uint) {
|
||||
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
|
||||
|
||||
@@ -187,7 +187,7 @@ void EmitMeshEntryPoint(EmitterState& state) {
|
||||
vertex[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
spv::OpBitFieldUExtract, TypeU32(state), vertex[component], packed,
|
||||
ConstantU32(state, component * 10u), ConstantU32(state, 10));
|
||||
ConstantU32(state, component * 10u), ConstantU32(state, 9));
|
||||
}
|
||||
const auto triangle = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
|
||||
|
||||
@@ -206,6 +206,17 @@ uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
|
||||
return state.builder.Type(spv::OpTypeArray, TypeF32(state), ConstantU32(state, count));
|
||||
}
|
||||
|
||||
void DefineImageArray(EmitterState& state, ImageDefinition& definition, const IR::ImageResource& image,
|
||||
uint32_t count, uint32_t set, uint32_t binding) {
|
||||
definition.type = ImageType(state, image);
|
||||
const auto array = state.builder.Type(spv::OpTypeArray, definition.type, ConstantU32(state, count));
|
||||
definition.variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, spv::StorageClassUniformConstant, array), spv::StorageClassUniformConstant);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationDescriptorSet, set);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationBinding, binding);
|
||||
definition.pointer_type = TypePointer(state, spv::StorageClassUniformConstant, definition.type);
|
||||
}
|
||||
|
||||
void DefineDescriptors(EmitterState& state) {
|
||||
using Kind = IR::DescriptorBindingKind;
|
||||
auto& info = state.program.info;
|
||||
@@ -222,6 +233,14 @@ void DefineDescriptors(EmitterState& state) {
|
||||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
|
||||
EXIT("shader image has an invalid descriptor type or mip count");
|
||||
}
|
||||
if (image.heap) {
|
||||
// Heap kinds alias one array; the root itself is never accessed.
|
||||
auto& definition = state.heap_images[IR::ImageBindingIndex(*kind)];
|
||||
if (image.indirect_resources.empty() && definition.variable == 0) {
|
||||
DefineImageArray(state, definition, image, IR::ImageHeapCapacity, IR::ImageHeapSet, 0u);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto& count = counts[static_cast<size_t>(*kind)];
|
||||
image.descriptor_index = count;
|
||||
if (count == 0) image_types[IR::ImageBindingIndex(*kind)] = ℑ
|
||||
@@ -331,21 +350,11 @@ void DefineDescriptors(EmitterState& state) {
|
||||
default: {
|
||||
EXIT_IF(IR::ImageBindingResourceClass(kind) ==
|
||||
IR::ImageResourceClass::None);
|
||||
const auto& image = *image_types[IR::ImageBindingIndex(kind)];
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(kind));
|
||||
auto& definition = state.images[IR::ImageBindingIndex(kind)];
|
||||
definition.type = ImageType(state, image);
|
||||
definition.variable = Define(ArrayType(definition.type), name.c_str(),
|
||||
spv::StorageClassUniformConstant);
|
||||
definition.pointer_type = TypePointer(
|
||||
state, spv::StorageClassUniformConstant, definition.type);
|
||||
if (image.dimension == ImageDimension::Dim1D ||
|
||||
image.dimension == ImageDimension::Dim1DArray) {
|
||||
state.builder.RequireCapability(image.resource_class ==
|
||||
IR::ImageResourceClass::Sampled
|
||||
? spv::CapabilitySampled1D
|
||||
: spv::CapabilityImage1D);
|
||||
}
|
||||
DefineImageArray(state, definition, *image_types[IR::ImageBindingIndex(kind)],
|
||||
counts[index], 0u, IR::NativeBinding(state.program.stage, kind));
|
||||
state.builder.AddName(definition.variable,
|
||||
("image_" + std::to_string(static_cast<uint32_t>(kind))).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -586,6 +595,13 @@ void DefineInputs(EmitterState& state) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationFlat);
|
||||
}
|
||||
}
|
||||
if (state.program.info.helper_invocation) {
|
||||
const auto variable = DefineInterfaceVariable(state, TypeBool(state), spv::StorageClassInput,
|
||||
"gl_HelperInvocation");
|
||||
state.helper_invocation_variable = variable;
|
||||
state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn,
|
||||
spv::BuiltInHelperInvocation);
|
||||
}
|
||||
}
|
||||
|
||||
void DefineOutputs(EmitterState& state) {
|
||||
@@ -682,13 +698,15 @@ void DefineOutputs(EmitterState& state) {
|
||||
break;
|
||||
case IR::StageOutputKind::Parameter:
|
||||
case IR::StageOutputKind::Mrt: {
|
||||
const bool uint_output =
|
||||
binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel &&
|
||||
binding.index < std::size(state.input_info.pixel->target_output_mode) &&
|
||||
(state.input_info.pixel->target_output_mode[binding.index] == 7u ||
|
||||
(state.input_info.pixel->target_packed_unorm_mask & (1u << binding.index)) != 0u);
|
||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
auto type = TypeF32Vector(state, 4);
|
||||
if (binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel && binding.index < 8u) {
|
||||
// Packed UNorm stores its encoded bits.
|
||||
const auto bit = 1u << binding.index;
|
||||
type = (state.input_info.pixel->target_packed_unorm_mask & bit) != 0u
|
||||
? TypeU32Vector(state, 4)
|
||||
: MrtOutputType(state, binding.index);
|
||||
}
|
||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||
binding.debug_name.c_str());
|
||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
||||
@@ -752,7 +770,7 @@ void DefineModule(EmitterState& state) {
|
||||
if (state.program.info.buffer_u16) {
|
||||
state.builder.RequireCapability(spv::CapabilityStorageBuffer16BitAccess);
|
||||
}
|
||||
if (state.program.info.uses_dma) {
|
||||
if (state.program.info.uses_dma || state.program.info.uses_image_heap) {
|
||||
state.builder.RequireCapability(spv::CapabilityInt64);
|
||||
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
@@ -820,7 +838,7 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
|
||||
}
|
||||
state.builder.RequireExtension("SPV_KHR_float_controls");
|
||||
state.builder.AddMemoryModel(state.program.info.uses_dma
|
||||
state.builder.AddMemoryModel(state.program.info.uses_dma || state.program.info.uses_image_heap
|
||||
? spv::AddressingModelPhysicalStorageBuffer64
|
||||
: spv::AddressingModelLogical,
|
||||
spv::MemoryModelGLSL450);
|
||||
|
||||
@@ -235,9 +235,9 @@ void EmitDirectInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (ctx.half != 0 &&
|
||||
(op == Op::Ballot || op == Op::ReadFirstLane || op == Op::SubgroupUMinU32 ||
|
||||
op == Op::ReadConstBuffer ||
|
||||
op == Op::SubgroupUMaxU32 || op == Op::ReadConstBuffer || op == Op::ReadRealtimeClock ||
|
||||
(op == Op::LoadAddressU32 && ctx.Memory(inst).kind == IR::ResourceKind::ScalarAddress))) {
|
||||
// Whole-wave results and SMEM loads (SGPR operands, no EXEC) are identical in both halves.
|
||||
// Whole-wave results, clocks and SMEM loads (SGPR operands, no EXEC) match in both halves.
|
||||
ctx.Define(inst, ctx.other_half->Result(inst));
|
||||
return;
|
||||
}
|
||||
@@ -746,7 +746,7 @@ void EmitProgram(EmitterState& state) {
|
||||
state.lds_base_dwords = EmitBinaryU32(state, spv::OpIMul, index,
|
||||
ConstantU32(state, LdsDwordCount(state)));
|
||||
}
|
||||
EmitMemoryOffsets(state);
|
||||
EmitMemoryBindings(state);
|
||||
if (program.blocks.empty()) {
|
||||
EmitReturn(ctx);
|
||||
} else if (state.program.dispatcher_fallback) {
|
||||
|
||||
@@ -1710,21 +1710,25 @@ Graph Structurize(const Graph& graph) {
|
||||
return GotoStructurizer(graph).Run();
|
||||
}
|
||||
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code) {
|
||||
// Vector comparisons and carry results can write a scalar pair in wave64.
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size) {
|
||||
// A vector lane mask (comparison or carry) spans wave_size / 32 SGPRs; a lane read writes one.
|
||||
if (inst.family == Decoder::Family::VOP1 || inst.family == Decoder::Family::VOP2 ||
|
||||
inst.family == Decoder::Family::VOPC || inst.family == Decoder::Family::VOP3 ||
|
||||
inst.family == Decoder::Family::VOP3P) {
|
||||
const bool lane_read =
|
||||
inst.opcode == Opcode::V_READLANE_B32 || inst.opcode == Opcode::V_READFIRSTLANE_B32;
|
||||
const uint32_t size = lane_read ? 1u : wave_size / 32u;
|
||||
uint32_t destination = 0;
|
||||
for (const auto* operand: {&inst.dst, &inst.dst2})
|
||||
if (ScalarOperandCode(*operand, destination) && code >= destination && code <= destination + 1u)
|
||||
if (ScalarOperandCode(*operand, destination) && code >= destination && code < destination + size)
|
||||
return true;
|
||||
}
|
||||
return InstructionWritesScalarCode(inst, code);
|
||||
}
|
||||
|
||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||
uint32_t before, uint32_t code, std::vector<uint32_t>& crossed_calls) {
|
||||
uint32_t before, uint32_t code, uint32_t wave_size,
|
||||
std::vector<uint32_t>& crossed_calls) {
|
||||
EXIT_IF(before >= program.instructions.size());
|
||||
const auto found = std::ranges::find_if(graph.blocks, [before](const auto& block) {
|
||||
return block.inst_begin <= before && before < block.inst_end;
|
||||
@@ -1743,11 +1747,11 @@ uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& grap
|
||||
for (uint32_t i = point.second; i > block->inst_begin;) {
|
||||
const auto& inst = program.instructions[--i];
|
||||
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
||||
EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code));
|
||||
EXIT_NOT_IMPLEMENTED(MayWriteScalarRegister(inst, code, wave_size));
|
||||
crossed_calls.push_back(i);
|
||||
continue;
|
||||
}
|
||||
if (MayWriteScalarRegister(inst, code)) { index = i; break; }
|
||||
if (MayWriteScalarRegister(inst, code, wave_size)) { index = i; break; }
|
||||
}
|
||||
if (index != UINT32_MAX || block->id == graph.entry_block) {
|
||||
EXIT_IF(definition && *definition != index);
|
||||
|
||||
@@ -113,9 +113,10 @@ struct Graph {
|
||||
};
|
||||
|
||||
Graph BuildGraph(const Decoder::Program& program);
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code);
|
||||
bool MayWriteScalarRegister(const Decoder::Instruction& inst, uint32_t code, uint32_t wave_size);
|
||||
uint32_t FindScalarDefinition(const Decoder::Program& program, const Graph& graph,
|
||||
uint32_t before, uint32_t code, std::vector<uint32_t>& crossed_calls);
|
||||
uint32_t before, uint32_t code, uint32_t wave_size,
|
||||
std::vector<uint32_t>& crossed_calls);
|
||||
// Returns structured control flow or failure diagnostics without changing the native graph.
|
||||
// On failure, failure_block is an original block ID or UINT32_MAX.
|
||||
Graph Structurize(const Graph& graph);
|
||||
|
||||
@@ -73,7 +73,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) {
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -258,7 +258,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 +461,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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -764,7 +766,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}));
|
||||
WriteMask(inst.dst2, carry, VectorMask(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 {
|
||||
|
||||
@@ -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))),
|
||||
@@ -969,17 +965,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:
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
#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, a, b, c, d, e, f, g, h, i, j)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor::PointerTable, a, b, c, d)
|
||||
KYTY_IO_FIELDS(DescriptorSource::IndirectDescriptor, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)
|
||||
KYTY_IO_FIELDS(DescriptorSource, a, b, c)
|
||||
KYTY_IO_FIELDS(SrtRead, a, b)
|
||||
KYTY_IO_FIELDS(ResourceBlock, a, b, c, d)
|
||||
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;
|
||||
|
||||
@@ -169,6 +166,7 @@ struct ImageResource {
|
||||
bool depth_compare = false;
|
||||
bool cube = false;
|
||||
bool r128 = false;
|
||||
bool heap = false; // Resident image heap root or kind.
|
||||
uint32_t indirect_root = NoIndirectImage;
|
||||
uint32_t indirect_mapping_offset = 0;
|
||||
uint32_t indirect_search_iterations = 0;
|
||||
@@ -188,6 +186,12 @@ RawSignedStorageFormat(const ImageResource& image, Prospero::BufferFormat format
|
||||
return Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
// A raw load of a kDepth k16UInt T# reads a D16 surface's codes. Vulkan cannot view D16 as
|
||||
// R16Uint, so the host binds it as UNorm and the shader converts back.
|
||||
[[nodiscard]] constexpr bool ReadsDepthBits(const ImageResource& image) {
|
||||
return image.conversion_format == Prospero::BufferFormat::k16UInt;
|
||||
}
|
||||
|
||||
struct SamplerResource {
|
||||
static constexpr uint32_t NoIndirectSampler = UINT32_MAX;
|
||||
|
||||
@@ -337,7 +341,12 @@ struct StageOutput {
|
||||
inline constexpr uint32_t FirstImageBinding = 1u;
|
||||
inline constexpr uint32_t FirstComparisonImageBinding = 22u;
|
||||
inline constexpr uint32_t FirstStorageImageBinding = 29u;
|
||||
inline constexpr uint32_t ImageBindingCount = 48u;
|
||||
inline constexpr uint32_t ImageBindingCount = 50u;
|
||||
// Resident image heaps share one sampled-image array at binding 0 of this set. A heap slot
|
||||
// word is kind << ImageHeapKindShift | element; element 0 is the null descriptor.
|
||||
inline constexpr uint32_t ImageHeapSet = 1u;
|
||||
inline constexpr uint32_t ImageHeapCapacity = 65536u;
|
||||
inline constexpr uint32_t ImageHeapKindShift = 20u;
|
||||
|
||||
enum class DescriptorBindingKind : uint32_t {
|
||||
Buffers = 0u,
|
||||
@@ -351,12 +360,12 @@ enum class DescriptorBindingKind : uint32_t {
|
||||
Count,
|
||||
};
|
||||
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 49u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 56u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 51u);
|
||||
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 {};
|
||||
|
||||
@@ -413,8 +422,8 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
constexpr uint32_t SampledUintBinding = 8u;
|
||||
constexpr uint32_t SampledSintBinding = 15u;
|
||||
constexpr uint32_t StorageFloatBinding = FirstStorageImageBinding;
|
||||
constexpr uint32_t StorageUintBinding = StorageFloatBinding + 5u;
|
||||
constexpr uint32_t AtomicUintBinding = StorageUintBinding + 5u;
|
||||
constexpr uint32_t StorageUintBinding = StorageFloatBinding + 6u;
|
||||
constexpr uint32_t AtomicUintBinding = StorageUintBinding + 6u;
|
||||
|
||||
uint32_t base = 0;
|
||||
bool sampled = false;
|
||||
@@ -461,10 +470,10 @@ DescriptorBindingForImage(const ImageResource& image) {
|
||||
case Decoder::ImageDimension::Dim2D: dimension = 2u; break;
|
||||
case Decoder::ImageDimension::Dim2DArray: dimension = 3u; break;
|
||||
case Decoder::ImageDimension::Dim2DMsaa:
|
||||
if (!sampled) {
|
||||
if (image.atomic) {
|
||||
return std::nullopt;
|
||||
}
|
||||
dimension = 4u;
|
||||
dimension = sampled ? 4u : 5u;
|
||||
break;
|
||||
case Decoder::ImageDimension::Dim2DMsaaArray:
|
||||
if (!sampled) {
|
||||
@@ -486,10 +495,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;
|
||||
}
|
||||
@@ -523,6 +538,7 @@ struct ShaderInfo {
|
||||
bool uses_lds = false;
|
||||
bool uses_gds = false;
|
||||
bool uses_flattened_srt = false;
|
||||
bool uses_image_heap = false;
|
||||
bool uses_dispatch_threads = false;
|
||||
bool bvh = false;
|
||||
bool subgroup_ballot = false;
|
||||
@@ -531,6 +547,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;
|
||||
@@ -645,24 +662,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();
|
||||
|
||||
@@ -682,21 +685,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;
|
||||
};
|
||||
|
||||
@@ -123,13 +123,9 @@ public:
|
||||
[[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;
|
||||
}
|
||||
// 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 {
|
||||
@@ -171,7 +167,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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -307,8 +311,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,
|
||||
@@ -363,7 +335,7 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
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 +364,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))
|
||||
@@ -408,8 +380,8 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
std::array<uint32_t, 4> masks {};
|
||||
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 ||
|
||||
!WalkedValue(program, indirect.selector_first, first) ||
|
||||
!EvaluateIndirectCount(program, indirect, runtime, count) || count != masks.size() * 32u ||
|
||||
selector->stride != sizeof(masks) || selector->offset != 0u ||
|
||||
material.Type() != 0u || material.Stride() != sizeof(masks) ||
|
||||
material.SwizzleEnabled() || material.AddTid() || material.OutOfBounds() != 0u ||
|
||||
@@ -462,8 +434,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();
|
||||
@@ -483,8 +455,8 @@ bool ResolveSelectorKeys(const DescriptorSource::IndirectDescriptor& indirect,
|
||||
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;
|
||||
@@ -541,8 +513,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 +576,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 +630,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 &&
|
||||
@@ -681,6 +652,32 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
const auto count = table_size >= first + 4u
|
||||
? std::min((table_size - first - 4u) / step + 1u, domain)
|
||||
: 0u;
|
||||
descriptors[resource_index] = {.dword_count = dword_count};
|
||||
if constexpr (std::is_same_v<Specialization, ResourceSpecialization::Image>) {
|
||||
const auto& image = program.info.images[resource_index];
|
||||
// A resident heap maps slot words instead of materializing its entries.
|
||||
ImageHeapView view;
|
||||
if (image_heap && runtime.image_heap != nullptr &&
|
||||
image.resource_class == ImageResourceClass::Sampled &&
|
||||
image.mip_mode != ImageMipMode::Dynamic &&
|
||||
runtime.image_heap->Acquire({table_base, table_size, step, first, count, &image},
|
||||
view)) {
|
||||
snapshot.specialization_reads.emplace_back(table_base, table_size);
|
||||
if (view.kinds.empty()) return true;
|
||||
auto root = root_resource;
|
||||
root.indirect_root = resource_index;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(snapshot.flattened_srt.size());
|
||||
root.heap = true;
|
||||
specializations[resource_index] = root;
|
||||
snapshot.flattened_srt.insert(snapshot.flattened_srt.end(),
|
||||
{view.slot_count, static_cast<uint32_t>(view.slots), static_cast<uint32_t>(view.slots >> 32u)});
|
||||
for (const auto& kind: view.kinds) {
|
||||
descriptors.push_back(kind);
|
||||
specializations.push_back(root);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (child_count == 0u || (!image_heap && count * child_count > MaxIndirectDescriptorProbes))
|
||||
return failed("candidate count", fmt::format("rows={} limit={}", count, MaxIndirectDescriptorProbes));
|
||||
// Affine SMEM offsets wrap in U32 before adding the aligned immediate.
|
||||
@@ -692,7 +689,6 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
return read_failed("image heap read", 0u, 0u, table_base, 0u, heap_words.size() * sizeof(uint32_t));
|
||||
}
|
||||
const auto mapping_offset = snapshot.flattened_srt.size();
|
||||
descriptors[resource_index] = {.dword_count = dword_count};
|
||||
snapshot.flattened_srt.resize(mapping_offset + 2u, 0u);
|
||||
for (uint32_t word = 0; word < count; ++word) {
|
||||
const auto table_offset = affine ? first + word * step : uint64_t {word} * 4u;
|
||||
@@ -746,8 +742,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 &&
|
||||
@@ -763,7 +759,7 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@@ -802,7 +798,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))
|
||||
@@ -932,7 +928,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
return SpecializationFail("FMASK requires a direct image load");
|
||||
}
|
||||
}
|
||||
image.conversion_format = ImageConversionFormat(format);
|
||||
// See ReadsDepthBits; raw loads only, since filtering would blend the codes.
|
||||
const bool depth_bits =
|
||||
!storage && base.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt &&
|
||||
static_cast<Prospero::TileMode>((descriptor.dwords[3] >> 20u) & 0x1fu) ==
|
||||
Prospero::TileMode::kDepth;
|
||||
image.conversion_format = depth_bits ? format : ImageConversionFormat(format);
|
||||
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
@@ -940,7 +941,7 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
const bool raw_sint_storage = raw_sint_format != Prospero::BufferFormat::kInvalid;
|
||||
if (raw_sint_format == Prospero::BufferFormat::k16UInt) image.conversion_format = format;
|
||||
image.numeric_class = Prospero::SampledTextureNumericClass(
|
||||
Prospero::RemapTextureFormat(format));
|
||||
depth_bits ? Prospero::BufferFormat::k16UNorm : Prospero::RemapTextureFormat(format));
|
||||
if (!storage && format == Prospero::BufferFormat::k11_11_10UNorm) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage) {
|
||||
return SpecializationFail("packed UNorm sampling requires a direct image");
|
||||
@@ -1005,10 +1006,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
|
||||
const auto key_count = root.indirect_mapping_offset < snapshot.flattened_srt.size()
|
||||
? snapshot.flattened_srt[root.indirect_mapping_offset]
|
||||
: 0u;
|
||||
// A heap maps its slot count and slot address; tables map each key.
|
||||
size_t mapping_words = 3u;
|
||||
if (!root.heap)
|
||||
mapping_words = 1u + size_t {key_count} * (root.indirect_search_iterations == 0u ? 1u : 2u);
|
||||
if (key_count == 0u ||
|
||||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(key_count) * (root.indirect_search_iterations == 0u ? 1u : 2u) >
|
||||
snapshot.flattened_srt.size()) {
|
||||
root.indirect_mapping_offset + mapping_words > snapshot.flattened_srt.size()) {
|
||||
return SpecializationFail("indirect image specialization has an invalid key mapping");
|
||||
}
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
@@ -1309,6 +1312,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;
|
||||
@@ -1348,16 +1362,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) {
|
||||
@@ -1379,7 +1390,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 =
|
||||
@@ -1393,17 +1403,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());
|
||||
@@ -1491,57 +1502,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;
|
||||
};
|
||||
@@ -1550,20 +1520,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,
|
||||
@@ -1578,24 +1544,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 = {};
|
||||
@@ -1619,9 +1585,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(
|
||||
@@ -1629,7 +1595,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;
|
||||
}
|
||||
}
|
||||
@@ -1679,13 +1645,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)) {
|
||||
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) ||
|
||||
@@ -1696,7 +1662,7 @@ 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)) {
|
||||
@@ -1717,8 +1683,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());
|
||||
@@ -1744,6 +1710,43 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
return BuildResourceSpecialization(program, snapshot, specialization);
|
||||
}
|
||||
|
||||
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.
|
||||
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 =
|
||||
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
|
||||
if (NullImageDescriptor(value) || !ValidImageDescriptor(value, root.r128) ||
|
||||
DescriptorDimension(value, root.dimension) != root.dimension ||
|
||||
Prospero::IsFmaskTextureFormat(format) || format == Prospero::BufferFormat::k11_11_10UNorm ||
|
||||
ImageConversionFormat(format) != Prospero::BufferFormat::kInvalid ||
|
||||
(root.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt &&
|
||||
tile == Prospero::TileMode::kDepth) ||
|
||||
numeric_class == Prospero::TextureNumericClass::Unsupported ||
|
||||
(root.depth_compare && numeric_class != Prospero::TextureNumericClass::Float)) {
|
||||
value.dwords.fill(0u);
|
||||
return 0u;
|
||||
}
|
||||
return 1u + static_cast<uint32_t>(numeric_class) * 2u + (DescriptorIsCube(value) ? 1u : 0u);
|
||||
}
|
||||
|
||||
ImageResource ImageHeapResource(const ImageResource& root, uint32_t kind) {
|
||||
ImageResource resource;
|
||||
resource.resource_class = ImageResourceClass::Sampled;
|
||||
resource.read = true;
|
||||
resource.heap = true;
|
||||
resource.dimension = root.dimension;
|
||||
resource.unfiltered_access_only = root.unfiltered_access_only;
|
||||
resource.depth_compare = root.depth_compare;
|
||||
resource.r128 = root.r128;
|
||||
if (kind != 0u) {
|
||||
resource.numeric_class = static_cast<Prospero::TextureNumericClass>((kind - 1u) / 2u);
|
||||
resource.cube = ((kind - 1u) & 1u) != 0u;
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
|
||||
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete);
|
||||
EXIT_IF(program.info.buffers.size() > specialization.buffers.size() ||
|
||||
@@ -1794,6 +1797,7 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
image.indirect_mapping_offset = source.indirect_mapping_offset;
|
||||
image.indirect_search_iterations = source.indirect_search_iterations;
|
||||
image.cube = source.cube;
|
||||
image.heap = source.heap;
|
||||
image.indirect_resources.clear();
|
||||
}
|
||||
for (uint32_t index = 0; index < images.size(); index++) {
|
||||
@@ -1995,4 +1999,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
|
||||
@@ -31,6 +31,7 @@ struct ResourceSpecialization {
|
||||
uint32_t indirect_search_iterations = 0;
|
||||
bool cube = false;
|
||||
bool fmask = false;
|
||||
bool heap = false;
|
||||
bool operator==(const Image&) const = default;
|
||||
};
|
||||
|
||||
@@ -52,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);
|
||||
@@ -59,6 +65,13 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
// Applies an already-derived specialization to native IR before layout and emission.
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization);
|
||||
|
||||
// Kind of an image heap entry as root sees it, or 0 (and a zeroed value) when root reads it as
|
||||
// null. Entries of one kind specialize identically.
|
||||
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value);
|
||||
// The sampled resource that root resolves entries of kind as; kind 0 gives the identity of
|
||||
// root's view of a heap.
|
||||
ImageResource ImageHeapResource(const ImageResource& root, uint32_t kind);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_ */
|
||||
@@ -366,7 +366,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;
|
||||
}
|
||||
@@ -517,7 +516,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 +577,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;
|
||||
@@ -594,6 +595,7 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
|
||||
};
|
||||
info.uses_flattened_srt |= std::ranges::any_of(info.buffers, uses_mapping) ||
|
||||
std::ranges::any_of(info.images, uses_mapping);
|
||||
info.uses_image_heap = std::ranges::any_of(info.images, &ImageResource::heap);
|
||||
switch (program.stage) {
|
||||
case ShaderType::Vertex:
|
||||
case ShaderType::Local: CollectVertexInputs(input_info.vertex, info, inputs.components); break;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 168 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user