mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 16:23:05 +00:00
Compare commits
173
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af19eabf0 | ||
|
|
32ea0869e5 | ||
|
|
e04007a7c6 | ||
|
|
1a3a15e4cf | ||
|
|
679adbfa5e | ||
|
|
d285efe3b5 | ||
|
|
a64c8d3205 | ||
|
|
82c52c3fab | ||
|
|
dd1e90fcd7 | ||
|
|
ef0155636a | ||
|
|
9d550fbffa | ||
|
|
fcef691e0a | ||
|
|
839c172612 | ||
|
|
9f49ac8249 | ||
|
|
fde550ef66 | ||
|
|
2b3993a595 | ||
|
|
de3e86791c | ||
|
|
6f66f77b09 | ||
|
|
d853aba955 | ||
|
|
cd62693792 | ||
|
|
cb4c8f3878 | ||
|
|
4d371d5f5f | ||
|
|
72963b01e1 | ||
|
|
80a18e82b7 | ||
|
|
574e687734 | ||
|
|
c725ec50ff | ||
|
|
c6f6e306e0 | ||
|
|
b847135321 | ||
|
|
ca3dc8792a | ||
|
|
ac3156df3a | ||
|
|
6aa6e390ee | ||
|
|
e4c10de8b6 | ||
|
|
b3b3aeef73 | ||
|
|
056dcee70c | ||
|
|
13c5bbe916 | ||
|
|
bbe3bd0304 | ||
|
|
f9b279c49d | ||
|
|
b61360a0c0 | ||
|
|
27ee6f02d6 | ||
|
|
2097ebb414 | ||
|
|
587df9145f | ||
|
|
2c5060e14f | ||
|
|
75aaa963d2 | ||
|
|
34ebfe40c3 | ||
|
|
2a54310876 | ||
|
|
8bccd325ec | ||
|
|
ad935311f0 | ||
|
|
19fc0274de | ||
|
|
6cc9e5de11 | ||
|
|
bf7ca29487 | ||
|
|
3cf426662e | ||
|
|
1fd07c2c83 | ||
|
|
f880c58ccc | ||
|
|
49e59e572e | ||
|
|
9d1fc8328f | ||
|
|
c9ff09c7c2 | ||
|
|
ceb2b664e0 | ||
|
|
b5b4c06179 | ||
|
|
d70f9c0b2b | ||
|
|
e847f82fab | ||
|
|
807a594c2d | ||
|
|
ced73c56cc | ||
|
|
9c6f6fa624 | ||
|
|
533176f9ff | ||
|
|
66c1663495 | ||
|
|
30c7715d3f | ||
|
|
093911a1dc | ||
|
|
d83f1f8235 | ||
|
|
50e78d717d | ||
|
|
6ca8201982 | ||
|
|
99a87bd65b | ||
|
|
f9f8e12c35 | ||
|
|
dcaf4205cb | ||
|
|
4735359466 | ||
|
|
211242452b | ||
|
|
6ae5cb7b64 | ||
|
|
997ad60c50 | ||
|
|
44f982bca7 | ||
|
|
0456f5a06b | ||
|
|
b346f58b73 | ||
|
|
ace877ce28 | ||
|
|
cf0cbee02d | ||
|
|
98e7eeb72a | ||
|
|
803c124136 | ||
|
|
d9d17737c3 | ||
|
|
46850f9574 | ||
|
|
314d37155a | ||
|
|
ddcdb654ad | ||
|
|
cdec5c515b | ||
|
|
1eb13dfa5e | ||
|
|
dda628ca81 | ||
|
|
13ad9f510d | ||
|
|
4c769dfbf9 | ||
|
|
ccd19b3e56 | ||
|
|
81d0ea5697 | ||
|
|
43de8bfaac | ||
|
|
86b539a9ea | ||
|
|
81a5dd612f | ||
|
|
9c00c7077b | ||
|
|
49ff962f69 | ||
|
|
c83aac966e | ||
|
|
9918dc2591 | ||
|
|
3ab372ccec | ||
|
|
843c0c59be | ||
|
|
d93812b0bd | ||
|
|
1c68c94c05 | ||
|
|
c66060be10 | ||
|
|
e7b269a9f6 | ||
|
|
d5b19850fb | ||
|
|
e7fe8192bb | ||
|
|
ebe0979563 | ||
|
|
a379304362 | ||
|
|
bec7c510c2 | ||
|
|
6a187ac67e | ||
|
|
9641b51925 | ||
|
|
b96a30ed88 | ||
|
|
2ec0e9e8f3 | ||
|
|
51afce3a97 | ||
|
|
dea80f34ca | ||
|
|
994271e2f6 | ||
|
|
59902d1fd8 | ||
|
|
b92f0a5458 | ||
|
|
251f6d6121 | ||
|
|
0d8c83a5cb | ||
|
|
3624d96c78 | ||
|
|
3213934d42 | ||
|
|
246212789f | ||
|
|
c7f7fd974b | ||
|
|
5cf2f5767a | ||
|
|
e8e580cf6c | ||
|
|
74a78f37d0 | ||
|
|
8d3ebb5a55 | ||
|
|
3628d2b4c1 | ||
|
|
d96d0eeb18 | ||
|
|
bd401a1407 | ||
|
|
070730be0a | ||
|
|
94e7d0a189 | ||
|
|
c28b71f8e0 | ||
|
|
27f015ecd1 | ||
|
|
8596627ced | ||
|
|
d4cca82a22 | ||
|
|
dd9ecd5f65 | ||
|
|
81b0c1302d | ||
|
|
b9b0b87545 | ||
|
|
040b2b9653 | ||
|
|
eccd6f6cac | ||
|
|
0ce19357f6 | ||
|
|
d762d24f73 | ||
|
|
eb917463ad | ||
|
|
c2189de88f | ||
|
|
2fe5c150bc | ||
|
|
5b8e3b51b0 | ||
|
|
f27a136430 | ||
|
|
fb4d97e8a9 | ||
|
|
5ffcd28ca4 | ||
|
|
b3c8f91a84 | ||
|
|
c78a5a9fdf | ||
|
|
858c18468b | ||
|
|
cd74809086 | ||
|
|
e21c5f37d0 | ||
|
|
2d5023fda2 | ||
|
|
98200fed19 | ||
|
|
c48a8bd6ae | ||
|
|
16ea36eb4b | ||
|
|
8f6f65f4c9 | ||
|
|
b81f1fc774 | ||
|
|
c85bd45604 | ||
|
|
5c86f894be | ||
|
|
86e3c90e32 | ||
|
|
93be8c201b | ||
|
|
894bc7de3c | ||
|
|
fc91fd2b1a | ||
|
|
a06a005124 |
No files matched your search
+9
-8
@@ -126,7 +126,7 @@ if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
|
||||
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
|
||||
endif()
|
||||
|
||||
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
|
||||
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.3.0)
|
||||
|
||||
include_directories(
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest/include
|
||||
@@ -431,16 +431,15 @@ add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
||||
)
|
||||
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ShaderStageRuntimeTests.cpp"
|
||||
add_executable(resource_materialization_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ResourceMaterializationTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/shaderStageRuntime.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
|
||||
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
|
||||
)
|
||||
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
|
||||
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
|
||||
target_link_libraries(resource_materialization_tests fmt::fmt)
|
||||
target_include_directories(resource_materialization_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
|
||||
@@ -542,7 +541,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
|
||||
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
|
||||
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
|
||||
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
|
||||
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
|
||||
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
|
||||
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
|
||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||
@@ -568,6 +567,8 @@ if(BUILD_TESTING)
|
||||
|
||||
add_test(NAME texture_cache_layered_image
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
|
||||
add_test(NAME host_image_allocation
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --host-image-allocation-only)
|
||||
add_test(NAME texture_cache_image_views
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
|
||||
add_test(NAME texture_cache_storage_sampled
|
||||
@@ -598,7 +599,7 @@ if(BUILD_TESTING)
|
||||
page_manager_tests
|
||||
bit_array_tests
|
||||
shader_vertex_metadata_tests
|
||||
shader_stage_runtime_tests
|
||||
resource_materialization_tests
|
||||
resource_tracking_tests
|
||||
event_queue_lifetime_tests
|
||||
kernel_file_system_tests
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 2.2 MiB |
@@ -59,6 +59,7 @@ int DbgExitHandler(const char* file, int line, fmt::text_style style, std::strin
|
||||
}
|
||||
|
||||
void DbgExit(int status) {
|
||||
Subsystems::EmergencyShutdownActive();
|
||||
std::fflush(nullptr);
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ void DbgExit(int status);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||
#else
|
||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
||||
#endif
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define EXIT_IF(x) \
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
Close();
|
||||
}
|
||||
|
||||
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#elif defined(__APPLE__)
|
||||
#else
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <unistd.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/ucontext.h>
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <initializer_list>
|
||||
#include <ucontext.h> // IWYU pragma: keep
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
@@ -32,6 +35,51 @@ static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
|
||||
// macOS uses the same POSIX platform setting and needs a signal stack too.
|
||||
class ThreadSignalStack {
|
||||
public:
|
||||
ThreadSignalStack() {
|
||||
const auto page_size = static_cast<size_t>(::getpagesize());
|
||||
const auto stack_size =
|
||||
(std::max<size_t>(64 * 1024, MINSIGSTKSZ) + page_size - 1) & ~(page_size - 1);
|
||||
if (::posix_memalign(&m_memory, page_size, stack_size) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack_t stack {};
|
||||
stack.ss_sp = m_memory;
|
||||
stack.ss_size = stack_size;
|
||||
if (::sigaltstack(&stack, &m_previous) != 0) {
|
||||
std::free(m_memory);
|
||||
m_memory = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadSignalStack() {
|
||||
if (m_memory != nullptr && ::sigaltstack(&m_previous, nullptr) == 0) {
|
||||
std::free(m_memory);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsInitialized() const { return m_memory != nullptr; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(ThreadSignalStack)
|
||||
|
||||
private:
|
||||
void* m_memory = nullptr;
|
||||
stack_t m_previous {};
|
||||
};
|
||||
|
||||
bool InitializeThreadSignalStack() {
|
||||
// Keep fault handling off guest stacks, which GPU tracking can make read-only.
|
||||
thread_local ThreadSignalStack signal_stack;
|
||||
return signal_stack.IsInitialized();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
|
||||
@@ -253,7 +301,7 @@ bool InstallHandler(Handler handler) {
|
||||
#elif defined(__APPLE__)
|
||||
struct sigaction sa {};
|
||||
sa.sa_sigaction = SignalHandler;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
// The guest signal-dispatch path (KernelRaiseException) interrupts threads with
|
||||
// SIGUSR1; block it while a fault is being resolved so a stop-the-world request
|
||||
@@ -274,8 +322,7 @@ bool InstallHandler(Handler handler) {
|
||||
struct sigaction action {};
|
||||
action.sa_sigaction = SignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
// Fault resolution needs the normal thread stack.
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
|
||||
|
||||
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
||||
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
||||
|
||||
@@ -39,6 +39,10 @@ using Handler = bool (*)(const ExceptionInfo&);
|
||||
|
||||
bool InstallHandler(Handler handler);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
bool InitializeThreadSignalStack();
|
||||
#endif
|
||||
|
||||
} // namespace Common::HostException
|
||||
|
||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||
@@ -316,10 +316,12 @@ inline std::string SafeCsv(std::string_view text) {
|
||||
return std::string(text);
|
||||
}
|
||||
|
||||
inline std::string Utf16ToUtf8(const char16_t* utf16) {
|
||||
std::u16string input(utf16);
|
||||
inline std::string Utf16ToUtf8(std::u16string_view utf16) {
|
||||
if (utf16.empty()) {
|
||||
return {};
|
||||
}
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
return convert.to_bytes(input);
|
||||
return convert.to_bytes(utf16.data(), utf16.data() + utf16.size());
|
||||
}
|
||||
|
||||
inline ByteBuffer HexToBin(std::string_view text) {
|
||||
|
||||
@@ -73,13 +73,9 @@ public:
|
||||
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
|
||||
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
|
||||
void SetNumInstances(uint32_t num_instances);
|
||||
void DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 0, const void* object_ids = nullptr,
|
||||
uint32_t render_target_slice_offset = 0, int32_t vertex_offset_add = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
|
||||
void DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0);
|
||||
void DrawIndex(DrawIndexArgs args);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count);
|
||||
void DrawIndexAuto(DrawAutoArgs args);
|
||||
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
@@ -149,10 +145,6 @@ private:
|
||||
uint32_t interrupt_context_id);
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
void SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset, uint32_t first_vertex,
|
||||
uint32_t first_instance);
|
||||
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
|
||||
@@ -24,7 +24,6 @@ constexpr auto MakeContextDispatchTable() {
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_CLEAR] = HwCtxSetDepthClear;
|
||||
g_hw_ctx_func[Pm4::PA_SC_SCREEN_SCISSOR_TL] = HwCtxSetScreenScissor;
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_INFO] = HwCtxSetStencilInfo;
|
||||
g_hw_ctx_func[Pm4::DB_SHADING_RATE_ENCODING] = HwCtxSetDepthShadingRateEncoding;
|
||||
g_hw_ctx_func[Pm4::PA_SU_HARDWARE_SCREEN_OFFSET] = HwCtxSetHardwareScreenOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_OFFSET] = HwCtxSetWindowOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_SCISSOR_TL] = HwCtxSetWindowScissor;
|
||||
@@ -140,13 +139,13 @@ constexpr auto MakeShaderDispatchTable() {
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMPRING_SIZE] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC3] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PACE_ID] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_PS] = HwShSetRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = HwShSetRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShSetRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShSetRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_HS] = HwShSetRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShSetRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShSetRegisters;
|
||||
|
||||
return g_hw_sh_func;
|
||||
}
|
||||
|
||||
@@ -45,8 +45,6 @@ uint32_t HwCtxSetStencilClear(CommandProcessor&, uint32_t, uint32_t, const uint3
|
||||
uint32_t HwCtxSetDepthClear(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetScreenScissor(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetStencilInfo(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthShadingRateEncoding(CommandProcessor&, uint32_t, uint32_t,
|
||||
const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetWindowOffset(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
@@ -100,7 +98,7 @@ uint32_t HwShSetCsUserSgpr(CommandProcessor&, uint32_t, uint32_t, const uint32_t
|
||||
uint32_t HwShIgnoreUserAccumulator(CommandProcessor&, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwShSetCsRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShIgnoreRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetRegisters(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetPrimitiveType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetIndexType(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetObjectId(CommandProcessor&, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
|
||||
@@ -599,17 +599,6 @@ static void HwCtxSetDepthShadingRateEncodingRegister(CommandProcessor& cp, uint3
|
||||
ctx.SetDepthRenderTarget(target);
|
||||
}
|
||||
|
||||
KYTY_HW_CTX_PARSER(HwCtxSetDepthShadingRateEncoding) {
|
||||
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(num_values != 1);
|
||||
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_SHADING_RATE_ENCODING);
|
||||
|
||||
HwCtxSetDepthShadingRateEncodingRegister(cp, buffer[0]);
|
||||
|
||||
return num_values;
|
||||
}
|
||||
|
||||
static HW::EqaaControl ParseEqaaControl(uint32_t value) {
|
||||
HW::EqaaControl r;
|
||||
|
||||
@@ -991,7 +980,7 @@ static void HwShIgnoreComputeRegister([[maybe_unused]] uint32_t cmd_offset,
|
||||
static void HwShIgnoreShaderRegister([[maybe_unused]] uint32_t cmd_offset,
|
||||
[[maybe_unused]] uint32_t value) {}
|
||||
|
||||
KYTY_HW_SH_PARSER(HwShIgnoreRegisters) {
|
||||
KYTY_HW_SH_PARSER(HwShSetRegisters) {
|
||||
const auto reg_num = (cmd_id >> 16u) & 0x3fffu;
|
||||
EXIT_NOT_IMPLEMENTED(reg_num == 0);
|
||||
|
||||
@@ -1618,15 +1607,15 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
|
||||
auto* index_addr =
|
||||
reinterpret_cast<void*>(buffer[1] | (static_cast<uint64_t>(buffer[2]) << 32u));
|
||||
uint32_t max_instance_count = buffer[3];
|
||||
auto* object_ids =
|
||||
reinterpret_cast<void*>(buffer[4] | (static_cast<uint64_t>(buffer[5]) << 32u));
|
||||
uint32_t instance_count = buffer[6];
|
||||
uint32_t flags = buffer[7];
|
||||
uint32_t instance_count = buffer[6];
|
||||
uint32_t flags = buffer[7];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(instance_count > max_instance_count);
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0xa0u) != 0);
|
||||
|
||||
cp.DrawIndex(index_count, index_addr, 0, 1, instance_count, object_ids);
|
||||
cp.DrawIndex({.index_count = index_count,
|
||||
.index_addr = index_addr,
|
||||
.instance_count = instance_count});
|
||||
|
||||
return 8;
|
||||
}
|
||||
@@ -1641,7 +1630,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndex) {
|
||||
EXIT_NOT_IMPLEMENTED(index_count > max_index_count);
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
|
||||
|
||||
cp.DrawIndex(index_count, index_addr, 0, 1);
|
||||
cp.DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
|
||||
return 5;
|
||||
}
|
||||
@@ -1700,7 +1689,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexOffset) {
|
||||
EXIT_NOT_IMPLEMENTED(index_count > max_index_size);
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0x20u) != 0);
|
||||
|
||||
cp.DrawIndexOffset(index_offset, index_count, 0);
|
||||
cp.DrawIndexOffset(index_offset, index_count);
|
||||
|
||||
return 4;
|
||||
}
|
||||
@@ -1715,7 +1704,7 @@ KYTY_CP_OP_PARSER(CpOpDrawIndexAuto) {
|
||||
|
||||
EXIT_NOT_IMPLEMENTED((flags & ~0x22u) != 0);
|
||||
|
||||
cp.DrawIndexAuto(index_count, 0);
|
||||
cp.DrawIndexAuto({.vertex_count = index_count});
|
||||
|
||||
return 2;
|
||||
}
|
||||
@@ -3640,12 +3629,13 @@ void GraphicsInitJmpTablesShIndirect() {
|
||||
g_hw_sh_indirect_func[Pm4::SPI_GRAPHICS_SHADER_CONTROL_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
for (uint32_t offset = Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS;
|
||||
offset <= Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS; offset++) {
|
||||
g_hw_sh_indirect_func[offset] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
cp.GetShCtx().SetGsUserDataAddress(cmd_offset - Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS,
|
||||
value);
|
||||
};
|
||||
}
|
||||
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_CHKSUM_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
|
||||
HwShIgnoreShaderRegister(cmd_offset, value);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ struct FormatInfo {
|
||||
uint32_t render_target_bytes_per_element;
|
||||
bool sampled_texture;
|
||||
bool uint_texture;
|
||||
bool sint_texture = false;
|
||||
};
|
||||
|
||||
constexpr FormatInfo kFormatInfo[] = {
|
||||
@@ -24,19 +25,19 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k16UNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k16SNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k16UInt, 2, 0, 2, true, true},
|
||||
{BufferFormat::k16SInt, 2, 0, 2, false, false},
|
||||
{BufferFormat::k16SInt, 2, 0, 2, true, false, true},
|
||||
{BufferFormat::k16Float, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8UNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8SNorm, 2, 0, 2, true, false},
|
||||
{BufferFormat::k8_8UInt, 2, 0, 2, true, true},
|
||||
{BufferFormat::k8_8SInt, 2, 0, 2, false, false},
|
||||
{BufferFormat::k8_8SInt, 2, 0, 2, true, false, true},
|
||||
{BufferFormat::k32UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k32SInt, 4, 0, 4, false, false},
|
||||
{BufferFormat::k32SInt, 4, 0, 4, true, false, true},
|
||||
{BufferFormat::k32Float, 4, 0, 4, true, false},
|
||||
{BufferFormat::k16_16UNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k16_16SNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k16_16UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k16_16SInt, 4, 0, 4, false, false},
|
||||
{BufferFormat::k16_16SInt, 4, 0, 4, true, false, true},
|
||||
{BufferFormat::k16_16Float, 4, 0, 4, true, false},
|
||||
{BufferFormat::k11_11_10UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
|
||||
@@ -45,20 +46,20 @@ constexpr FormatInfo kFormatInfo[] = {
|
||||
{BufferFormat::k8_8_8_8UNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k8_8_8_8SNorm, 4, 0, 4, true, false},
|
||||
{BufferFormat::k8_8_8_8UInt, 4, 0, 4, true, true},
|
||||
{BufferFormat::k8_8_8_8SInt, 4, 0, 4, false, false},
|
||||
{BufferFormat::k8_8_8_8SInt, 4, 0, 4, true, false, true},
|
||||
{BufferFormat::k32_32UInt, 8, 0, 8, true, true},
|
||||
{BufferFormat::k32_32SInt, 8, 0, 8, false, false},
|
||||
{BufferFormat::k32_32SInt, 8, 0, 8, true, false, true},
|
||||
{BufferFormat::k32_32Float, 8, 0, 8, true, false},
|
||||
{BufferFormat::k16_16_16_16UNorm, 8, 0, 8, true, false},
|
||||
{BufferFormat::k16_16_16_16SNorm, 8, 0, 8, true, false},
|
||||
{BufferFormat::k16_16_16_16UInt, 8, 0, 8, true, true},
|
||||
{BufferFormat::k16_16_16_16SInt, 8, 0, 8, false, false},
|
||||
{BufferFormat::k16_16_16_16SInt, 8, 0, 8, true, false, true},
|
||||
{BufferFormat::k16_16_16_16Float, 8, 0, 8, true, false},
|
||||
{BufferFormat::k32_32_32UInt, 12, 0, 12, true, true},
|
||||
{BufferFormat::k32_32_32SInt, 12, 0, 12, false, false},
|
||||
{BufferFormat::k32_32_32SInt, 12, 0, 12, true, false, true},
|
||||
{BufferFormat::k32_32_32Float, 12, 0, 12, true, false},
|
||||
{BufferFormat::k32_32_32_32UInt, 16, 0, 16, true, true},
|
||||
{BufferFormat::k32_32_32_32SInt, 16, 0, 16, false, false},
|
||||
{BufferFormat::k32_32_32_32SInt, 16, 0, 16, true, false, true},
|
||||
{BufferFormat::k32_32_32_32Float, 16, 0, 16, true, false},
|
||||
{BufferFormat::k8Srgb, 1, 0, 0, true, false},
|
||||
{BufferFormat::k8_8Srgb, 2, 0, 0, true, false},
|
||||
@@ -218,14 +219,15 @@ bool IsFmaskTextureFormat(BufferFormat format) {
|
||||
return format >= BufferFormat::kFmask8_S2_F1 && format <= BufferFormat::kFmask64_S16_F8;
|
||||
}
|
||||
|
||||
bool IsSampledTextureFormat(BufferFormat format) {
|
||||
TextureNumericClass SampledTextureNumericClass(BufferFormat format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr && info->sampled_texture;
|
||||
}
|
||||
|
||||
bool IsUintTextureFormat(BufferFormat format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr && info->uint_texture;
|
||||
if (info == nullptr || !info->sampled_texture) {
|
||||
return TextureNumericClass::Unsupported;
|
||||
}
|
||||
if (info->sint_texture) {
|
||||
return TextureNumericClass::Sint;
|
||||
}
|
||||
return info->uint_texture ? TextureNumericClass::Uint : TextureNumericClass::Float;
|
||||
}
|
||||
|
||||
BufferFormat RemapTextureFormat(BufferFormat format) {
|
||||
|
||||
@@ -37,13 +37,14 @@ struct RenderTargetFormatEncoding {
|
||||
}
|
||||
};
|
||||
|
||||
enum class TextureNumericClass { Unsupported, Float, Uint, Sint };
|
||||
|
||||
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type);
|
||||
uint32_t NumBytesPerElement(BufferFormat format);
|
||||
uint32_t BlockCompressedBytesPerBlock(BufferFormat format);
|
||||
uint32_t RenderTargetBytesPerElement(BufferFormat format);
|
||||
bool IsFmaskTextureFormat(BufferFormat format);
|
||||
bool IsSampledTextureFormat(BufferFormat format);
|
||||
bool IsUintTextureFormat(BufferFormat format);
|
||||
TextureNumericClass SampledTextureNumericClass(BufferFormat format);
|
||||
BufferFormat RemapTextureFormat(BufferFormat format);
|
||||
|
||||
} // namespace Libs::Graphics::Prospero
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/sync.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
@@ -40,6 +39,21 @@ static thread_local bool g_gpu_mutex_owned = false;
|
||||
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;
|
||||
};
|
||||
|
||||
class GpuMutexLock final {
|
||||
public:
|
||||
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
|
||||
@@ -546,10 +560,6 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
|
||||
bool GuestGpu::Process(Submission& submission) {
|
||||
const bool first_slice = !submission.started;
|
||||
if (first_slice && RenderDocCaptureRequested()) {
|
||||
Common::LockGuard render_lock(m_renderer.GetMutex());
|
||||
RenderDocStartCapture();
|
||||
}
|
||||
auto& cp = GetProcessor(submission.queue_id);
|
||||
|
||||
if (first_slice && submission.reset_processor) {
|
||||
@@ -847,36 +857,21 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags,
|
||||
uint32_t type, uint32_t instance_count, const void* object_ids,
|
||||
uint32_t render_target_slice_offset, int32_t vertex_offset_add,
|
||||
uint32_t first_instance) {
|
||||
void CommandProcessor::DrawIndex(DrawIndexArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
if (instance_count == 0) {
|
||||
instance_count = m_num_instances;
|
||||
args.index_type_and_size = m_index_type_and_size;
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
if (object_ids != nullptr) {
|
||||
LOGF("\t draw indexed multi-instanced objectIds = 0x%016" PRIx64 "\n",
|
||||
reinterpret_cast<uint64_t>(object_ids));
|
||||
if (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);
|
||||
}
|
||||
if (render_target_slice_offset != 0) {
|
||||
LOGF("\t draw render target slice offset = %" PRIu32 "\n", render_target_slice_offset);
|
||||
}
|
||||
if (vertex_offset_add != 0 || first_instance != 0) {
|
||||
LOGF("\t draw indexed offsets: vertex_offset_add = %" PRId32 ", first_instance = %" PRIu32
|
||||
"\n",
|
||||
vertex_offset_add, first_instance);
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawIndex(
|
||||
m_submit_id, CurrentBuffer(), m_index_type_and_size, index_count, index_addr, flags, type,
|
||||
instance_count, render_target_slice_offset, vertex_offset_add, first_instance);
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count,
|
||||
uint32_t flags) {
|
||||
CheckBuffer();
|
||||
|
||||
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
|
||||
uint64_t index_size = 0;
|
||||
switch (m_index_type_and_size) {
|
||||
case 0: index_size = 2; break;
|
||||
@@ -888,25 +883,10 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou
|
||||
auto* index_addr = reinterpret_cast<const void*>(
|
||||
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size);
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size,
|
||||
index_count, index_addr, flags, 1, m_num_instances);
|
||||
DrawIndex({.index_count = index_count, .index_addr = index_addr});
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
|
||||
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;
|
||||
};
|
||||
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
|
||||
@@ -928,8 +908,11 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
}
|
||||
}
|
||||
m_num_instances = args.instance_count;
|
||||
SubmitNonIndexedDraw(args.vertex_count_per_instance, 0, 0, args.start_vertex_location,
|
||||
args.start_instance_location);
|
||||
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});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -970,28 +953,18 @@ void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiato
|
||||
}
|
||||
|
||||
m_num_instances = args.instance_count;
|
||||
DrawIndex(index_count, index_addr, 0, 1, args.instance_count, nullptr, 0,
|
||||
static_cast<int32_t>(args.base_vertex_location), args.start_instance_location);
|
||||
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});
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr,
|
||||
uint32_t stride_in_bytes, uint32_t draw_initiator,
|
||||
bool indexed) {
|
||||
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;
|
||||
};
|
||||
|
||||
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != 2u);
|
||||
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0);
|
||||
|
||||
@@ -1028,8 +1001,11 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
}
|
||||
m_num_instances = args->instance_count;
|
||||
SubmitNonIndexedDraw(args->vertex_count_per_instance, 0, 0, args->start_vertex_location,
|
||||
args->start_instance_location);
|
||||
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});
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1071,8 +1047,12 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
|
||||
}
|
||||
|
||||
m_num_instances = args->instance_count;
|
||||
DrawIndex(index_count, index_addr, 0, 1, args->instance_count, nullptr, 0,
|
||||
static_cast<int32_t>(args->base_vertex_location), args->start_instance_location);
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,19 +1143,13 @@ void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
|
||||
DispatchDirect(args->thread_group_x, args->thread_group_y, args->thread_group_z, mode);
|
||||
}
|
||||
|
||||
void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset) {
|
||||
SubmitNonIndexedDraw(index_count, flags, render_target_slice_offset, 0, 0);
|
||||
}
|
||||
|
||||
void CommandProcessor::SubmitNonIndexedDraw(uint32_t vertex_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t first_vertex, uint32_t first_instance) {
|
||||
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
|
||||
CheckBuffer();
|
||||
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), vertex_count, flags,
|
||||
render_target_slice_offset, m_num_instances,
|
||||
first_vertex, first_instance);
|
||||
if (args.instance_count == 0) {
|
||||
args.instance_count = m_num_instances;
|
||||
}
|
||||
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
|
||||
}
|
||||
|
||||
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
|
||||
|
||||
@@ -362,16 +362,16 @@ struct AaConfig {
|
||||
struct Viewport {
|
||||
float zmin = 0.0f;
|
||||
float zmax = 0.0f;
|
||||
float xscale = 0.0f;
|
||||
float xscale = 1.0f;
|
||||
float xoffset = 0.0f;
|
||||
float yscale = 0.0f;
|
||||
float yscale = 1.0f;
|
||||
float yoffset = 0.0f;
|
||||
float zscale = 0.0f;
|
||||
float zscale = 1.0f;
|
||||
float zoffset = 0.0f;
|
||||
int viewport_scissor_left = 0;
|
||||
int viewport_scissor_top = 0;
|
||||
int viewport_scissor_right = 0;
|
||||
int viewport_scissor_bottom = 0;
|
||||
int viewport_scissor_right = 16384;
|
||||
int viewport_scissor_bottom = 16384;
|
||||
bool viewport_scissor_window_offset_enable = false;
|
||||
};
|
||||
|
||||
@@ -380,26 +380,26 @@ struct ScreenViewport {
|
||||
uint32_t transform_control = 1087;
|
||||
int screen_scissor_left = 0;
|
||||
int screen_scissor_top = 0;
|
||||
int screen_scissor_right = 0;
|
||||
int screen_scissor_bottom = 0;
|
||||
int screen_scissor_right = 16384;
|
||||
int screen_scissor_bottom = 16384;
|
||||
int window_scissor_left = 0;
|
||||
int window_scissor_top = 0;
|
||||
int window_scissor_right = 0;
|
||||
int window_scissor_bottom = 0;
|
||||
int window_scissor_right = 16384;
|
||||
int window_scissor_bottom = 16384;
|
||||
bool window_scissor_window_offset_enable = false;
|
||||
int generic_scissor_left = 0;
|
||||
int generic_scissor_top = 0;
|
||||
int generic_scissor_right = 0;
|
||||
int generic_scissor_bottom = 0;
|
||||
int generic_scissor_right = 16384;
|
||||
int generic_scissor_bottom = 16384;
|
||||
bool generic_scissor_window_offset_enable = false;
|
||||
int window_offset_x = 0;
|
||||
int window_offset_y = 0;
|
||||
uint32_t hw_offset_x = 0;
|
||||
uint32_t hw_offset_y = 0;
|
||||
float guard_band_horz_clip = 0.0f;
|
||||
float guard_band_vert_clip = 0.0f;
|
||||
float guard_band_horz_discard = 0.0f;
|
||||
float guard_band_vert_discard = 0.0f;
|
||||
float guard_band_horz_clip = 1.0f;
|
||||
float guard_band_vert_clip = 1.0f;
|
||||
float guard_band_horz_discard = 1.0f;
|
||||
float guard_band_vert_discard = 1.0f;
|
||||
uint16_t clip_rect_rule = 0xffffu;
|
||||
int clip_rect_left[4] = {};
|
||||
int clip_rect_top[4] = {};
|
||||
@@ -520,7 +520,8 @@ struct GsShaderResource2 {
|
||||
};
|
||||
|
||||
struct GsStageRegisters {
|
||||
uint64_t data_addr = 0;
|
||||
uint64_t data_addr = 0;
|
||||
uint64_t user_data_addr = 0;
|
||||
GsShaderResource1 rsrc1;
|
||||
GsShaderResource2 rsrc2;
|
||||
};
|
||||
@@ -976,6 +977,12 @@ public:
|
||||
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
|
||||
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
|
||||
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
|
||||
void SetGsUserDataAddress(uint32_t word, uint32_t value) {
|
||||
const auto shift = word * 32u;
|
||||
auto& address = m_vs.gs_regs.user_data_addr;
|
||||
address = (address & ~(uint64_t {0xffffffffu} << shift)) |
|
||||
(static_cast<uint64_t>(value) << shift);
|
||||
}
|
||||
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
|
||||
void SetGsShaderResource2(const GsShaderResource2& rsrc2) { m_vs.gs_regs.rsrc2 = rsrc2; }
|
||||
|
||||
|
||||
@@ -1221,6 +1221,31 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2) {
|
||||
total_size = {};
|
||||
if (width == 0 || height == 0 || slices == 0 || levels != 1 || num_fragments_log2 != 0 ||
|
||||
!std::has_single_bit(bytes_per_element) || bytes_per_element > 16 ||
|
||||
(tile != Prospero::TileMode::kRenderTarget && tile != Prospero::TileMode::kDepth)) {
|
||||
return false;
|
||||
}
|
||||
// Gen5 color metadata uses 4 KiB blocks, with one byte per 256 bytes of color data.
|
||||
// Metadata stays thin for volume surfaces, so every depth slice has its own block raster.
|
||||
const uint32_t coverage_bits = 20u - std::countr_zero(bytes_per_element);
|
||||
const uint32_t block_width = 1u << ((coverage_bits + 1u) / 2u);
|
||||
const uint32_t block_height = 1u << (coverage_bits / 2u);
|
||||
const uint64_t blocks_x = (static_cast<uint64_t>(width) + block_width - 1u) / block_width;
|
||||
const uint64_t blocks_y = (static_cast<uint64_t>(height) + block_height - 1u) / block_height;
|
||||
const uint64_t blocks = blocks_x * blocks_y;
|
||||
if (blocks > UINT32_MAX / 4096u / slices) {
|
||||
return false;
|
||||
}
|
||||
total_size.size = static_cast<uint32_t>(blocks * slices * 4096u);
|
||||
total_size.align = 4096;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
|
||||
@@ -122,6 +122,9 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t num_fragments_log2 = 0);
|
||||
bool TileGetDccSize(uint32_t width, uint32_t height, uint32_t slices,
|
||||
uint32_t bytes_per_element, uint32_t levels, Prospero::TileMode tile,
|
||||
TileSizeAlign& total_size, uint32_t num_fragments_log2 = 0);
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct VulkanBuffer;
|
||||
struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
|
||||
inline constexpr uint32_t VULKAN_TARGET_API_VERSION = VK_API_VERSION_1_3;
|
||||
|
||||
@@ -31,8 +29,12 @@ struct GraphicContext {
|
||||
bool memory_budget_ext_enabled = false;
|
||||
bool rt_extensions_enabled = false;
|
||||
bool compute_subgroup_size_control_enabled = false;
|
||||
bool compute_wave64_supported = false;
|
||||
bool sample_rate_shading_enabled = false;
|
||||
bool attachment_feedback_loop_enabled = false;
|
||||
bool provoking_vertex_last_enabled = false;
|
||||
bool supports_block_texel_view = false;
|
||||
bool mesh_shader_enabled = false;
|
||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||
uint32_t subgroup_size = 0;
|
||||
uint32_t min_subgroup_size = 0;
|
||||
uint32_t max_subgroup_size = 0;
|
||||
@@ -80,6 +82,10 @@ struct GraphicContext {
|
||||
return it->second.first;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool SupportsComputeWave64() const noexcept {
|
||||
return subgroup_size == 64u || compute_subgroup_size_control_enabled;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DeviceSize StorageMinAlignment() const {
|
||||
const auto alignment = physical_device_properties.limits.minStorageBufferOffsetAlignment;
|
||||
return alignment != 0 ? alignment : 1;
|
||||
@@ -91,11 +97,8 @@ struct GraphicContext {
|
||||
[[nodiscard]] bool CanReportMemoryUsage() const noexcept { return memory_budget_ext_enabled; }
|
||||
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
|
||||
[[nodiscard]] uint64_t GetTotalMemoryBudget() const;
|
||||
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
|
||||
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
|
||||
void DeleteImage(VulkanImage& image);
|
||||
void MapMemory(VulkanMemory& memory, void*& data);
|
||||
void UnmapMemory(VulkanMemory& memory);
|
||||
void AppendHardwareRayTracingDeviceExtensions(
|
||||
const std::vector<vk::ExtensionProperties>& available_extensions,
|
||||
std::vector<const char*>& device_extensions);
|
||||
@@ -114,18 +117,6 @@ private:
|
||||
m_image_format_properties;
|
||||
};
|
||||
|
||||
struct VulkanMemory {
|
||||
vk::MemoryRequirements requirements = {};
|
||||
vk::MemoryPropertyFlags property = {};
|
||||
vk::MemoryPropertyFlags preferred_property = {};
|
||||
vk::DeviceMemory memory = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VmaAllocationInfo allocation_info = {};
|
||||
vk::DeviceSize offset = 0;
|
||||
uint32_t type = 0;
|
||||
uint64_t unique_id = 0;
|
||||
};
|
||||
|
||||
struct VulkanImageState {
|
||||
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
|
||||
@@ -139,7 +130,6 @@ struct VulkanImage {
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::ImageType image_type = vk::ImageType::e2D;
|
||||
vk::Extent3D extent = {1, 1, 1};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
uint32_t samples = 1;
|
||||
@@ -148,15 +138,10 @@ struct VulkanImage {
|
||||
vk::Image image = nullptr;
|
||||
VulkanImageState state;
|
||||
std::vector<VulkanImageState> subresource_states;
|
||||
Graphics::VulkanMemory memory;
|
||||
VmaAllocation allocation = nullptr;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
vk::Buffer buffer = nullptr;
|
||||
VulkanMemory memory;
|
||||
vk::BufferUsageFlags usage = {};
|
||||
uint64_t buffer_size = 0;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -23,10 +23,7 @@ void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr,
|
||||
EXIT("MemoryTracker: invalid dirty-page validation range\n");
|
||||
}
|
||||
for (auto page = vaddr; page < vaddr + size; page += TRACKER_PAGE_SIZE) {
|
||||
bool found = false;
|
||||
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
|
||||
[&found](RangeSet::Range) { found = true; });
|
||||
if (!found) {
|
||||
if (!dirty.Intersects(page, TRACKER_PAGE_SIZE)) {
|
||||
EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s "
|
||||
"addr=0x%016" PRIx64 "\n",
|
||||
operation, page);
|
||||
@@ -40,9 +37,7 @@ void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t va
|
||||
const auto begin = vaddr & ~(TRACKER_PAGE_SIZE - 1);
|
||||
const auto end = (vaddr + size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
|
||||
for (auto page = begin; page < end; page += TRACKER_PAGE_SIZE) {
|
||||
bool has_dirty_bytes = false;
|
||||
dirty.ForEachIntersection(page, TRACKER_PAGE_SIZE,
|
||||
[&has_dirty_bytes](RangeSet::Range) { has_dirty_bytes = true; });
|
||||
const bool has_dirty_bytes = dirty.Intersects(page, TRACKER_PAGE_SIZE);
|
||||
if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) {
|
||||
EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s "
|
||||
"addr=0x%016" PRIx64 "\n",
|
||||
@@ -113,7 +108,8 @@ void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
|
||||
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::vector<RegionManager*> managers;
|
||||
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
|
||||
TRACKER_REGION_SIZE);
|
||||
@@ -134,12 +130,6 @@ void MemoryTracker::UntrackMemoryImpl(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();
|
||||
}
|
||||
|
||||
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
UntrackMemoryImpl(vaddr, size);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -34,7 +34,6 @@ public:
|
||||
void InvalidateRegion(uint64_t vaddr, uint64_t size, Flush&& on_flush) noexcept {
|
||||
static_assert(std::is_invocable_v<Flush&>);
|
||||
CheckNotInUploadCallback();
|
||||
ValidateRange(vaddr, size);
|
||||
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const bool should_flush = [&] {
|
||||
@@ -171,7 +170,6 @@ private:
|
||||
}
|
||||
|
||||
static void ValidateRange(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemoryImpl(uint64_t vaddr, uint64_t size);
|
||||
RegionManager* GetOrCreateRegion(uint64_t index);
|
||||
|
||||
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
|
||||
|
||||
@@ -371,8 +371,4 @@ template void PageManager::UpdatePageWatchersForRegion<true, false>(uint64_t, Re
|
||||
template void PageManager::UpdatePageWatchersForRegion<false, true>(uint64_t, RegionBits&);
|
||||
template void PageManager::UpdatePageWatchersForRegion<false, false>(uint64_t, RegionBits&);
|
||||
|
||||
void PageManager::OnGpuMap(uint64_t, uint64_t) {}
|
||||
|
||||
void PageManager::OnGpuUnmap(uint64_t, uint64_t) {}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -24,8 +24,6 @@ public:
|
||||
void UpdatePageWatchers(uint64_t vaddr, uint64_t size);
|
||||
template <bool track, bool is_read = false>
|
||||
void UpdatePageWatchersForRegion(uint64_t base_addr, RegionBits& mask);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
|
||||
+118
-187
@@ -13,7 +13,6 @@
|
||||
#include "kernel/memory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@@ -193,17 +192,16 @@ void BufferCache::DownloadBufferMemory(std::span<const DownloadCopy> copies) {
|
||||
|
||||
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
PageManager& page_manager, TextureCache& texture_cache)
|
||||
: m_graphics(graphics), m_scheduler(scheduler),
|
||||
m_fault_manager(graphics, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
BDA_PAGETABLE_SIZE),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_fault_manager(graphics, scheduler, *this),
|
||||
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
|
||||
m_bda_pagetable_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
BDA_PAGETABLE_SIZE),
|
||||
m_memory_tracker(page_manager),
|
||||
m_staging_buffer(graphics, scheduler, MemoryUsage::Upload, 512 * MiB),
|
||||
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
|
||||
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
|
||||
m_texture_cache(texture_cache) {
|
||||
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
|
||||
m_gds_buffer.Flush(0, m_gds_buffer.Size());
|
||||
SetVulkanObjectNameF(m_graphics.device, m_bda_pagetable_buffer.Handle(),
|
||||
@@ -259,51 +257,36 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
|
||||
}
|
||||
|
||||
void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write) {
|
||||
// CPU invalidation reaches this point only for a GPU-owned tracker page. Resolve the exact
|
||||
// Buffer owner on the GPU thread so the cache index remains single-thread-owned.
|
||||
if (is_write && !IsRegionRegistered(vaddr, size)) {
|
||||
return;
|
||||
}
|
||||
auto& buffer = m_slot_buffers[FindBuffer(vaddr, size)];
|
||||
|
||||
// Widen nearby CPU reads so they share one GPU drain.
|
||||
constexpr uint64_t WindowSize = 512 * 1024;
|
||||
const auto buffer_begin = buffer.CpuAddress();
|
||||
const auto buffer_end = buffer_begin + buffer.Size();
|
||||
const auto window_begin = std::max(vaddr & ~(WindowSize - 1), buffer_begin);
|
||||
const auto window_end = std::min(std::max(window_begin + WindowSize, vaddr + size), buffer_end);
|
||||
|
||||
std::vector<DownloadCopy> copies;
|
||||
m_memory_tracker.ForEachDownloadRange<false>(
|
||||
vaddr, size,
|
||||
window_begin, window_end - window_begin,
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
|
||||
"memory invalidation");
|
||||
},
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
|
||||
for (uint64_t copied = 0; copied < range.size;) {
|
||||
const auto copy_address = range.address + copied;
|
||||
const auto* owner = m_page_table.Find(copy_address >> PageTable::kPageBits);
|
||||
if (owner == nullptr || !*owner) {
|
||||
EXIT("BufferCache: invalidation readback has no buffer owner\n");
|
||||
}
|
||||
auto& buffer = m_slot_buffers[*owner];
|
||||
if (!buffer.IsInBounds(copy_address, 1)) {
|
||||
EXIT("BufferCache: invalidation readback is outside its buffer owner\n");
|
||||
}
|
||||
const auto copy_size = std::min(
|
||||
range.size - copied, buffer.CpuAddress() + buffer.Size() - copy_address);
|
||||
copies.push_back(
|
||||
{&buffer, buffer.Offset(copy_address), copy_address, copy_size});
|
||||
copied += copy_size;
|
||||
}
|
||||
}
|
||||
[&](uint64_t address, uint64_t bytes) noexcept {
|
||||
m_gpu_modified_ranges.ForEachIntersection(address, bytes, [&](RangeSet::Range range) {
|
||||
copies.push_back(
|
||||
{&buffer, buffer.Offset(range.address), range.address, range.size});
|
||||
});
|
||||
});
|
||||
if (copies.empty()) {
|
||||
if (!is_write) {
|
||||
return;
|
||||
}
|
||||
// A preceding read fault can consume the last GPU-owned copy after this write invalidation
|
||||
// has already chosen to flush. Complete the CPU ownership handoff even though this callback
|
||||
// no longer has bytes to download.
|
||||
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
|
||||
return;
|
||||
if (!copies.empty()) {
|
||||
DownloadBufferMemory(copies);
|
||||
// The enumeration covered whole dirty pages and every exact interval on them.
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(window_begin, window_end - window_begin);
|
||||
}
|
||||
DownloadBufferMemory(copies);
|
||||
// The enumeration above covered whole dirty pages and every exact interval on them.
|
||||
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
|
||||
if (is_write) {
|
||||
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
|
||||
}
|
||||
@@ -326,38 +309,81 @@ BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
|
||||
return CreateBuffer(vaddr, size);
|
||||
}
|
||||
|
||||
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
|
||||
auto& command = m_scheduler.Current();
|
||||
EXIT_IF(command.IsInvalid());
|
||||
auto begin = vaddr & ~(CACHING_PAGESIZE - 1);
|
||||
auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
|
||||
auto first = m_buffers.lower_bound(begin);
|
||||
if (first != m_buffers.begin()) {
|
||||
const auto previous = std::prev(first);
|
||||
if (const auto& buffer = m_slot_buffers[previous->second];
|
||||
buffer.CpuAddress() + buffer.Size() > begin) {
|
||||
first = previous;
|
||||
BufferCache::OverlapResult BufferCache::ResolveOverlaps(uint64_t vaddr, uint64_t size) {
|
||||
static constexpr int StreamLeapThreshold = 16;
|
||||
static constexpr uint64_t StreamLeapSize = CACHING_PAGESIZE * 128;
|
||||
|
||||
auto begin = vaddr;
|
||||
auto end = vaddr + size;
|
||||
const auto find_first = [&](uint64_t address) {
|
||||
auto first = m_buffers.lower_bound(address);
|
||||
if (first != m_buffers.begin()) {
|
||||
const auto previous = std::prev(first);
|
||||
const auto& buffer = m_slot_buffers[previous->second];
|
||||
if (buffer.CpuAddress() + buffer.Size() > address) {
|
||||
first = previous;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
};
|
||||
auto first = find_first(begin);
|
||||
auto last = first;
|
||||
int stream_score = 0;
|
||||
bool has_stream_leap = false;
|
||||
for (; last != m_buffers.end() && last->first < end; ++last) {
|
||||
const auto& buffer = m_slot_buffers[last->second];
|
||||
const auto buffer_begin = buffer.CpuAddress();
|
||||
const auto buffer_end = buffer_begin + buffer.Size();
|
||||
const bool expands_left = buffer_begin < begin;
|
||||
const bool expands_right = buffer_end > end;
|
||||
begin = std::min(begin, buffer_begin);
|
||||
end = std::max(end, buffer_end);
|
||||
if (!has_stream_leap && (stream_score += buffer.StreamScore()) > StreamLeapThreshold) {
|
||||
has_stream_leap = true;
|
||||
if (expands_right) {
|
||||
end += std::min(StreamLeapSize, PageTable::kAddressSpaceSize - end);
|
||||
}
|
||||
if (expands_left) {
|
||||
const auto minimum = CACHING_PAGESIZE * 2;
|
||||
if (begin > minimum) {
|
||||
begin -= std::min(StreamLeapSize, begin - minimum);
|
||||
}
|
||||
first = find_first(begin);
|
||||
begin = std::min(begin, first->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto last = first;
|
||||
for (; last != m_buffers.end() && last->first < end; ++last) {
|
||||
const auto& buffer = m_slot_buffers[last->second];
|
||||
begin = std::min(begin, buffer.CpuAddress());
|
||||
end = std::max(end, buffer.CpuAddress() + buffer.Size());
|
||||
return {first, last, begin, end, has_stream_leap};
|
||||
}
|
||||
|
||||
void BufferCache::JoinOverlap(BufferId new_id, BufferId overlap_id, bool accumulate_stream_score) {
|
||||
auto& new_buffer = m_slot_buffers[new_id];
|
||||
auto& overlap = m_slot_buffers[overlap_id];
|
||||
if (accumulate_stream_score) {
|
||||
new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1);
|
||||
}
|
||||
new_buffer.CopyFrom(m_scheduler.Current(), overlap, 0,
|
||||
overlap.CpuAddress() - new_buffer.CpuAddress(), overlap.Size());
|
||||
DeleteBuffer(overlap_id);
|
||||
}
|
||||
|
||||
BufferId BufferCache::CreateBuffer(uint64_t vaddr, uint64_t size) {
|
||||
EXIT_IF(m_scheduler.Current().IsInvalid());
|
||||
const auto end = (vaddr + size + CACHING_PAGESIZE - 1) & ~(CACHING_PAGESIZE - 1);
|
||||
vaddr &= ~(CACHING_PAGESIZE - 1);
|
||||
size = end - vaddr;
|
||||
const auto overlap = ResolveOverlaps(vaddr, size);
|
||||
|
||||
const auto id = m_slot_buffers.insert(
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
|
||||
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, end - begin);
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
m_graphics, m_scheduler, MemoryUsage::DeviceLocal, overlap.begin,
|
||||
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, overlap.end - overlap.begin);
|
||||
const auto& buffer = m_slot_buffers[id];
|
||||
SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
|
||||
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
|
||||
for (auto overlap = first; overlap != last;) {
|
||||
const auto current = overlap++;
|
||||
const auto old_id = current->second;
|
||||
const auto& old = m_slot_buffers[old_id];
|
||||
buffer.CopyFrom(command, old, 0, old.CpuAddress() - begin, old.Size());
|
||||
DeleteBuffer(old_id);
|
||||
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", overlap.begin,
|
||||
overlap.end - overlap.begin);
|
||||
for (auto it = overlap.first; it != overlap.last;) {
|
||||
const auto old_id = (it++)->second;
|
||||
JoinOverlap(id, old_id, !overlap.has_stream_leap);
|
||||
}
|
||||
Register(id);
|
||||
return id;
|
||||
@@ -380,7 +406,6 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t siz
|
||||
command.EndRendering();
|
||||
const auto native = command.Handle();
|
||||
vk::BufferMemoryBarrier before {};
|
||||
before.sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
before.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite |
|
||||
vk::AccessFlagBits::eTransferRead |
|
||||
vk::AccessFlagBits::eTransferWrite;
|
||||
@@ -475,37 +500,18 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
|
||||
if (!GuestRange {vaddr, size}.Valid()) {
|
||||
EXIT("BufferCache: invalid image source\n");
|
||||
}
|
||||
auto find_owner = [&]() -> Buffer* {
|
||||
const auto* owner = m_page_table.Find(vaddr >> PageTable::kPageBits);
|
||||
if (owner == nullptr || !*owner) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto* owner = m_page_table.Find(vaddr >> PageTable::kPageBits);
|
||||
if (owner != nullptr && *owner) {
|
||||
auto& buffer = m_slot_buffers[*owner];
|
||||
return buffer.IsInBounds(vaddr, size) ? &buffer : nullptr;
|
||||
};
|
||||
|
||||
{
|
||||
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
|
||||
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
|
||||
const bool has_dirty_buffer_source = m_gpu_modified_ranges.Intersects(vaddr, size);
|
||||
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
|
||||
"image source");
|
||||
|
||||
auto* owner = find_owner();
|
||||
if (has_dirty_buffer_source && owner == nullptr) {
|
||||
if (!IsRegionRegistered(vaddr, size)) {
|
||||
EXIT("BufferCache: GPU-dirty image source has no native buffer\n");
|
||||
}
|
||||
owner = &m_slot_buffers[FindBuffer(vaddr, size)];
|
||||
}
|
||||
if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) {
|
||||
TouchBuffer(*owner);
|
||||
return {owner, owner->Offset(vaddr)};
|
||||
}
|
||||
if (has_dirty_buffer_source && owner == nullptr) {
|
||||
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
|
||||
if (buffer.IsInBounds(vaddr, size)) {
|
||||
TouchBuffer(buffer);
|
||||
(void)SynchronizeBuffer(buffer, vaddr, size, false, false);
|
||||
return {&buffer, buffer.Offset(vaddr)};
|
||||
}
|
||||
}
|
||||
if (IsRegionGpuModified(vaddr, size)) {
|
||||
return ObtainBuffer(vaddr, size, false, false);
|
||||
}
|
||||
|
||||
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
|
||||
if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) &&
|
||||
@@ -513,52 +519,7 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
|
||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||
}
|
||||
m_staging_buffer.Commit();
|
||||
|
||||
const bool has_dirty_buffer_source = m_gpu_modified_ranges.Intersects(vaddr, size);
|
||||
auto* owner = find_owner();
|
||||
if (has_dirty_buffer_source && owner == nullptr) {
|
||||
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
|
||||
}
|
||||
if (owner == nullptr ||
|
||||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !has_dirty_buffer_source)) {
|
||||
return {&m_staging_buffer, stage_offset};
|
||||
}
|
||||
|
||||
TouchBuffer(*owner);
|
||||
std::vector<std::pair<uint64_t, uint64_t>> uploads;
|
||||
m_memory_tracker.ForEachUploadRange(
|
||||
vaddr, size, false,
|
||||
[&](uint64_t address, uint64_t upload_size) noexcept {
|
||||
uploads.emplace_back(address, upload_size);
|
||||
},
|
||||
[&]() noexcept {
|
||||
for (const auto& [address, upload_size]: uploads) {
|
||||
owner->CopyFrom(m_scheduler.Current(), m_staging_buffer,
|
||||
stage_offset + address - vaddr, owner->Offset(address), upload_size,
|
||||
vk::AccessFlagBits::eHostWrite);
|
||||
}
|
||||
});
|
||||
return {owner, owner->Offset(vaddr)};
|
||||
}
|
||||
|
||||
void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) {
|
||||
if (vaddr == 0 || data.empty() || data.size() > UINT64_MAX - vaddr) {
|
||||
EXIT("BufferCache: invalid host DMA write\n");
|
||||
}
|
||||
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
|
||||
|
||||
const auto end = vaddr + data.size();
|
||||
for (const auto& [address, id]: m_buffers) {
|
||||
auto& buffer = m_slot_buffers[id];
|
||||
const auto buffer_end = address + buffer.Size();
|
||||
const auto begin = std::max(vaddr, address);
|
||||
const auto range_end = std::min(end, buffer_end);
|
||||
if (begin >= range_end) {
|
||||
continue;
|
||||
}
|
||||
WriteDataBuffer(buffer, begin, data.data() + begin - vaddr, range_end - begin);
|
||||
TouchBuffer(buffer);
|
||||
}
|
||||
return {&m_staging_buffer, stage_offset};
|
||||
}
|
||||
|
||||
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
|
||||
@@ -576,23 +537,11 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
|
||||
EXIT("BufferCache: invalid fill memory address\n");
|
||||
}
|
||||
(void)m_texture_cache.ClearMeta(vaddr);
|
||||
{
|
||||
const auto region = m_texture_cache.QueryRegion(vaddr, size);
|
||||
if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) {
|
||||
if (region.image_bytes) {
|
||||
m_texture_cache.InvalidateMemory(vaddr, size);
|
||||
}
|
||||
std::array<uint32_t, 4096> values;
|
||||
values.fill(value);
|
||||
const std::span<const uint8_t> bytes {reinterpret_cast<const uint8_t*>(values.data()),
|
||||
sizeof(values)};
|
||||
for (uint64_t offset = 0; offset < size;) {
|
||||
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
|
||||
WriteHostMemory(vaddr + offset, bytes.first(chunk));
|
||||
offset += chunk;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!IsRegionGpuModified(vaddr, size)) {
|
||||
// Access the guest mapping so write faults invalidate cached buffers and images.
|
||||
auto* destination = reinterpret_cast<uint32_t*>(vaddr);
|
||||
std::fill(destination, destination + size / sizeof(uint32_t), value);
|
||||
return;
|
||||
}
|
||||
|
||||
m_texture_cache.InvalidateMemoryFromGPU(vaddr, size);
|
||||
@@ -615,29 +564,11 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
|
||||
" size=0x%016" PRIx64 " src_gds=%d dst_gds=%d\n",
|
||||
src_vaddr, dst_vaddr, size, static_cast<int>(src_gds), static_cast<int>(dst_gds));
|
||||
}
|
||||
if (src_memory || dst_memory) {
|
||||
const auto src_region =
|
||||
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
|
||||
const auto dst_region =
|
||||
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
|
||||
if (src_memory && dst_memory && !HasGpuDirtyBytes(src_vaddr, size) &&
|
||||
!HasGpuDirtyBytes(dst_vaddr, size) && !src_region.gpu_image_bytes &&
|
||||
!dst_region.gpu_image_bytes) {
|
||||
if (dst_region.image_bytes) {
|
||||
m_texture_cache.InvalidateMemory(dst_vaddr, size);
|
||||
}
|
||||
std::array<uint8_t, 64 * 1024> bytes;
|
||||
for (uint64_t offset = 0; offset < size;) {
|
||||
const auto chunk = std::min<uint64_t>(size - offset, bytes.size());
|
||||
if (!Libs::LibKernel::Memory::TryReadBacking(src_vaddr + offset, bytes.data(),
|
||||
chunk)) {
|
||||
EXIT("BufferCache: host DMA source has no direct backing\n");
|
||||
}
|
||||
WriteHostMemory(dst_vaddr + offset, std::span {bytes}.first(chunk));
|
||||
offset += chunk;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (src_memory && dst_memory && !IsRegionGpuModified(dst_vaddr, size) &&
|
||||
!IsRegionGpuModified(src_vaddr, size) && !m_texture_cache.FindImageFromRange(src_vaddr, size)) {
|
||||
std::memcpy(reinterpret_cast<void*>(dst_vaddr), reinterpret_cast<const void*>(src_vaddr),
|
||||
size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& command = m_scheduler.Current();
|
||||
|
||||
+12
-2
@@ -74,6 +74,15 @@ public:
|
||||
private:
|
||||
friend struct BufferCacheTestAccess;
|
||||
|
||||
using BufferMap = std::map<uint64_t, BufferId>;
|
||||
struct OverlapResult {
|
||||
BufferMap::iterator first;
|
||||
BufferMap::iterator last;
|
||||
uint64_t begin;
|
||||
uint64_t end;
|
||||
bool has_stream_leap;
|
||||
};
|
||||
|
||||
struct DownloadCopy;
|
||||
using PageTable = MultiLevelPageTable<BufferId, CACHING_PAGEBITS, 40, 16>;
|
||||
static_assert(CACHING_PAGESIZE == (uint64_t {1} << PageTable::kPageBits));
|
||||
@@ -84,6 +93,8 @@ private:
|
||||
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
|
||||
void WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source, uint64_t size);
|
||||
void TouchBuffer(const Buffer& buffer);
|
||||
[[nodiscard]] OverlapResult ResolveOverlaps(uint64_t vaddr, uint64_t size);
|
||||
void JoinOverlap(BufferId new_id, BufferId overlap_id, bool accumulate_stream_score);
|
||||
[[nodiscard]] BufferId CreateBuffer(uint64_t vaddr, uint64_t size);
|
||||
void Register(BufferId id);
|
||||
void Unregister(BufferId id);
|
||||
@@ -96,7 +107,6 @@ private:
|
||||
uint64_t total_size);
|
||||
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
|
||||
void DownloadBufferMemory(std::span<const DownloadCopy> copies);
|
||||
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
|
||||
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
@@ -106,7 +116,7 @@ private:
|
||||
Buffer m_bda_pagetable_buffer;
|
||||
Common::SlotVector<Buffer> m_slot_buffers;
|
||||
Common::LeastRecentlyUsedCache<BufferId, uint64_t> m_lru_cache;
|
||||
std::map<uint64_t, BufferId> m_buffers;
|
||||
BufferMap m_buffers;
|
||||
PageTable m_page_table;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
MemoryTracker m_memory_tracker;
|
||||
|
||||
+8
-19
@@ -23,13 +23,10 @@ constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(uint64_t);
|
||||
} // namespace
|
||||
|
||||
FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
BufferCache& buffer_cache, uint32_t caching_pagebits,
|
||||
uint64_t caching_num_pages)
|
||||
BufferCache& buffer_cache)
|
||||
: m_graphics(graphics), m_scheduler(scheduler), m_buffer_cache(buffer_cache),
|
||||
m_caching_pagesize(uint64_t {1} << caching_pagebits),
|
||||
m_caching_num_pages(caching_num_pages), m_fault_buffer_size(caching_num_pages / 8),
|
||||
m_fault_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags,
|
||||
m_fault_buffer_size),
|
||||
BufferCache::CACHING_NUMPAGES / 8),
|
||||
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 0, AllFlags,
|
||||
MaxPendingFaults * PageFaultAreaSize) {
|
||||
SetVulkanObjectNameF(m_graphics.device, m_fault_buffer.Handle(), "Fault Buffer");
|
||||
@@ -39,7 +36,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
|
||||
{1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute, nullptr},
|
||||
};
|
||||
vk::DescriptorSetLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
layout_info.bindingCount = std::size(bindings);
|
||||
layout_info.pBindings = bindings;
|
||||
@@ -49,7 +45,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
|
||||
"create fault-buffer descriptor layout");
|
||||
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = std::size(FAULT_BUFFER_PROCESS_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = FAULT_BUFFER_PROCESS_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
@@ -57,7 +52,6 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
|
||||
"create fault-buffer shader module");
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &m_fault_process_desc_layout;
|
||||
RequireVulkanSuccess(
|
||||
@@ -66,12 +60,10 @@ FaultManager::FaultManager(GraphicContext& graphics, CommandScheduler& scheduler
|
||||
"create fault-buffer pipeline layout");
|
||||
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo pipeline_info {};
|
||||
pipeline_info.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
pipeline_info.stage = stage;
|
||||
pipeline_info.layout = m_fault_process_pipeline_layout;
|
||||
const auto result = m_graphics.device.createComputePipelines(
|
||||
@@ -99,14 +91,13 @@ void FaultManager::ProcessFaultBuffer() {
|
||||
m_download_buffer.Flush(offset, PageFaultAreaSize);
|
||||
|
||||
vk::BufferMemoryBarrier2 pre_barrier {};
|
||||
pre_barrier.sType = vk::StructureType::eBufferMemoryBarrier2;
|
||||
pre_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
pre_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
pre_barrier.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
pre_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderRead;
|
||||
pre_barrier.buffer = m_fault_buffer.Handle();
|
||||
pre_barrier.offset = 0;
|
||||
pre_barrier.size = m_fault_buffer_size;
|
||||
pre_barrier.size = m_fault_buffer.Size();
|
||||
auto post_barrier = pre_barrier;
|
||||
post_barrier.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
post_barrier.srcAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
@@ -114,12 +105,11 @@ void FaultManager::ProcessFaultBuffer() {
|
||||
post_barrier.dstAccessMask = vk::AccessFlagBits2::eShaderWrite;
|
||||
|
||||
const vk::DescriptorBufferInfo infos[] {
|
||||
{m_fault_buffer.Handle(), 0, m_fault_buffer_size},
|
||||
{m_fault_buffer.Handle(), 0, m_fault_buffer.Size()},
|
||||
{m_download_buffer.Handle(), offset, PageFaultAreaSize},
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); ++index) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
@@ -129,7 +119,6 @@ void FaultManager::ProcessFaultBuffer() {
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.sType = vk::StructureType::eDependencyInfo;
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &pre_barrier;
|
||||
@@ -137,7 +126,7 @@ void FaultManager::ProcessFaultBuffer() {
|
||||
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_fault_process_pipeline);
|
||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute,
|
||||
m_fault_process_pipeline_layout, 0, writes);
|
||||
const auto num_threads = m_caching_num_pages / 32;
|
||||
const auto num_threads = BufferCache::CACHING_NUMPAGES / 32;
|
||||
const auto num_workgroups = (num_threads + 63) / 64;
|
||||
command.dispatch(static_cast<uint32_t>(num_workgroups), 1, 1);
|
||||
dependency.pBufferMemoryBarriers = &post_barrier;
|
||||
@@ -146,14 +135,14 @@ void FaultManager::ProcessFaultBuffer() {
|
||||
const auto area = m_current_area;
|
||||
m_scheduler.DeferOperation([this, mapped, offset, area] {
|
||||
m_download_buffer.Invalidate(offset, PageFaultAreaSize);
|
||||
m_fault_ranges.Clear();
|
||||
RangeSet fault_ranges;
|
||||
const auto* faults = std::bit_cast<const uint64_t*>(mapped);
|
||||
const auto count = static_cast<uint32_t>(faults[0]);
|
||||
for (uint32_t index = 1; index <= count; ++index) {
|
||||
m_fault_ranges.Add(faults[index], m_caching_pagesize);
|
||||
fault_ranges.Add(faults[index], BufferCache::CACHING_PAGESIZE);
|
||||
LOGF("Accessed non-GPU cached memory at 0x%016" PRIx64 "\n", faults[index]);
|
||||
}
|
||||
m_fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
|
||||
fault_ranges.ForEach([this](uint64_t start, uint64_t end) {
|
||||
EXIT_IF(end - start > std::numeric_limits<uint32_t>::max());
|
||||
(void)m_buffer_cache.FindBuffer(start, end - start);
|
||||
});
|
||||
|
||||
+1
-7
@@ -2,7 +2,6 @@
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_CACHE_FAULTMANAGER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
||||
|
||||
#include <array>
|
||||
@@ -16,8 +15,7 @@ class FaultManager {
|
||||
static constexpr size_t MaxPendingFaults = 8;
|
||||
|
||||
public:
|
||||
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache,
|
||||
uint32_t caching_pagebits, uint64_t caching_num_pages);
|
||||
FaultManager(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache& buffer_cache);
|
||||
~FaultManager();
|
||||
KYTY_CLASS_NO_COPY(FaultManager);
|
||||
|
||||
@@ -28,10 +26,6 @@ private:
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
BufferCache& m_buffer_cache;
|
||||
RangeSet m_fault_ranges;
|
||||
uint64_t m_caching_pagesize;
|
||||
uint64_t m_caching_num_pages;
|
||||
uint64_t m_fault_buffer_size;
|
||||
Buffer m_fault_buffer;
|
||||
Buffer m_download_buffer;
|
||||
std::array<uint64_t, MaxPendingFaults> m_fault_areas {};
|
||||
|
||||
@@ -12,7 +12,9 @@ GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandSchedule
|
||||
GpuResourceManager::~GpuResourceManager() = default;
|
||||
|
||||
bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
|
||||
constexpr uint64_t fault_size = 8;
|
||||
// The host reports the faulting byte, not the instruction's access width. Both caches
|
||||
// resolve its page; guessing a width can cross the end of a valid guest mapping.
|
||||
constexpr uint64_t fault_size = 1;
|
||||
if (!IsMapped(fault_vaddr, fault_size)) {
|
||||
return false;
|
||||
}
|
||||
@@ -47,7 +49,6 @@ void GpuResourceManager::MapMemory(uint64_t vaddr, uint64_t size) {
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Add(vaddr, size);
|
||||
}
|
||||
m_page_manager.OnGpuMap(vaddr, size);
|
||||
}
|
||||
|
||||
void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
@@ -64,7 +65,6 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
}
|
||||
m_buffer_cache.InvalidateMemory(vaddr, size);
|
||||
m_texture_cache.UnmapMemory(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size);
|
||||
std::lock_guard lock(m_mapped_ranges_mutex);
|
||||
m_mapped_ranges.Subtract(vaddr, size);
|
||||
};
|
||||
|
||||
@@ -256,17 +256,6 @@ private:
|
||||
size_type m_inline_size = 0;
|
||||
};
|
||||
|
||||
// Removes only the requested owner. Other owners in the same page entry remain intact.
|
||||
template <typename Container, typename Value>
|
||||
[[nodiscard]] bool EraseExact(Container& owners, const Value& owner) {
|
||||
const auto it = std::find(owners.begin(), owners.end(), owner);
|
||||
if (it == owners.end()) {
|
||||
return false;
|
||||
}
|
||||
owners.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
@@ -116,9 +116,6 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
|
||||
default: EXIT("unknown border color: %d", static_cast<int>(r.BorderColorType()));
|
||||
}
|
||||
|
||||
sampler_info.sType = vk::StructureType::eSamplerCreateInfo;
|
||||
sampler_info.pNext = nullptr;
|
||||
sampler_info.flags = {};
|
||||
sampler_info.magFilter = to_vk_filter(mag_filter);
|
||||
sampler_info.minFilter = to_vk_filter(min_filter);
|
||||
sampler_info.mipmapMode =
|
||||
|
||||
+26
-53
@@ -4,7 +4,6 @@
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
@@ -64,14 +63,13 @@ constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
|
||||
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
||||
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
|
||||
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
|
||||
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
|
||||
m_size(size) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(graphics.allocator == nullptr || size == 0);
|
||||
|
||||
vk::BufferCreateInfo buffer_info {};
|
||||
buffer_info.size = size;
|
||||
buffer_info.usage = flags;
|
||||
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
const bool with_bda = bool(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress);
|
||||
const VmaAllocationCreateFlags bda_flag =
|
||||
@@ -88,93 +86,76 @@ Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsag
|
||||
VkBuffer native_buffer = VK_NULL_HANDLE;
|
||||
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
|
||||
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
|
||||
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
|
||||
&native_buffer, &m_allocation, &allocation_result));
|
||||
if (result != vk::Result::eSuccess) {
|
||||
graphics.LogMemoryBudget();
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
m_buffer->buffer = native_buffer;
|
||||
m_buffer->usage = flags;
|
||||
m_buffer->buffer_size = size;
|
||||
m_buffer->memory.allocation_info = allocation_result;
|
||||
m_buffer->memory.memory = allocation_result.deviceMemory;
|
||||
m_buffer->memory.offset = allocation_result.offset;
|
||||
m_buffer->memory.type = allocation_result.memoryType;
|
||||
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
|
||||
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
|
||||
if (static_cast<bool>(flags & vk::BufferUsageFlagBits::eShaderDeviceAddress)) {
|
||||
m_buffer = native_buffer;
|
||||
if (with_bda) {
|
||||
vk::BufferDeviceAddressInfo address_info {};
|
||||
address_info.sType = vk::StructureType::eBufferDeviceAddressInfo;
|
||||
address_info.buffer = m_buffer->buffer;
|
||||
address_info.buffer = m_buffer;
|
||||
m_device_address = graphics.device.getBufferAddress(address_info);
|
||||
EXIT_IF(m_device_address == 0);
|
||||
}
|
||||
|
||||
VkMemoryPropertyFlags properties = 0;
|
||||
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
|
||||
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
|
||||
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
vmaGetAllocationMemoryProperties(graphics.allocator, m_allocation, &properties);
|
||||
m_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
if (allocation_result.pMappedData != nullptr) {
|
||||
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
|
||||
static_cast<size_t>(size)};
|
||||
}
|
||||
VulkanTrackAllocation(m_buffer->memory);
|
||||
}
|
||||
|
||||
Buffer::~Buffer() {
|
||||
if (m_buffer->buffer != nullptr) {
|
||||
VulkanUntrackAllocation(m_buffer->memory);
|
||||
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
|
||||
if (m_buffer != nullptr) {
|
||||
vmaDestroyBuffer(m_graphics->allocator, m_buffer, m_allocation);
|
||||
}
|
||||
}
|
||||
|
||||
vk::Buffer Buffer::Handle() const noexcept {
|
||||
return m_buffer->buffer;
|
||||
}
|
||||
|
||||
vk::DeviceAddress Buffer::BufferDeviceAddress() const noexcept {
|
||||
EXIT_IF(m_device_address == 0);
|
||||
return m_device_address;
|
||||
}
|
||||
|
||||
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
|
||||
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
|
||||
return address >= m_cpu_address && size <= Size() && address - m_cpu_address <= Size() - size;
|
||||
}
|
||||
|
||||
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
|
||||
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
|
||||
EXIT_IF(source == nullptr || m_mapped.empty() || offset > Size() || size > Size() - offset);
|
||||
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
|
||||
Flush(offset, size);
|
||||
}
|
||||
|
||||
void Buffer::Flush(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
EXIT_IF(m_mapped.empty() || offset > Size() || size > Size() - offset);
|
||||
if (!IsCoherent() && size != 0) {
|
||||
const auto result =
|
||||
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
||||
vmaFlushAllocation(m_graphics->allocator, m_allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Invalidate(uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_usage != MemoryUsage::Download || offset > m_size || size > m_size - offset);
|
||||
if (!m_is_coherent && size != 0) {
|
||||
EXIT_IF(m_usage != MemoryUsage::Download || offset > Size() || size > Size() - offset);
|
||||
if (!IsCoherent() && size != 0) {
|
||||
const auto result =
|
||||
vmaInvalidateAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
||||
vmaInvalidateAllocation(m_graphics->allocator, m_allocation, offset, size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
|
||||
vk::AccessFlags destination) const {
|
||||
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
|
||||
if (Handle() == nullptr || size == 0 || offset > Size() || size > Size() - offset) {
|
||||
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
|
||||
" capacity=0x%016" PRIx64 "\n",
|
||||
static_cast<const void*>(Handle()), offset, size, m_size);
|
||||
static_cast<const void*>(Handle()), offset, size, Size());
|
||||
}
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barrier.srcAccessMask = source;
|
||||
barrier.dstAccessMask = destination;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
@@ -189,8 +170,8 @@ void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t sou
|
||||
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
|
||||
vk::AccessFlags destination_before, vk::AccessFlags source_after,
|
||||
vk::AccessFlags destination_after) {
|
||||
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
|
||||
destination_offset > m_size || size > m_size - destination_offset) {
|
||||
if (size == 0 || source_offset > source.Size() || size > source.Size() - source_offset ||
|
||||
destination_offset > Size() || size > Size() - destination_offset) {
|
||||
EXIT("Buffer: invalid copy range\n");
|
||||
}
|
||||
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
|
||||
@@ -247,10 +228,8 @@ void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
|
||||
|
||||
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
||||
uint64_t size)
|
||||
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
|
||||
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
|
||||
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
|
||||
}
|
||||
: Buffer(graphics, scheduler, usage, 0, AllFlags, size),
|
||||
m_current_watches(WATCHES_INITIAL_RESERVE), m_previous_watches(WATCHES_INITIAL_RESERVE) {}
|
||||
|
||||
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
|
||||
uint64_t& alignment) {
|
||||
@@ -315,10 +294,8 @@ std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignmen
|
||||
}
|
||||
|
||||
void StreamBuffer::Commit() {
|
||||
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
|
||||
const auto result = vmaFlushAllocation(
|
||||
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
||||
if (Usage() != MemoryUsage::Download && m_mapped_size != 0) {
|
||||
Flush(m_offset, m_mapped_size);
|
||||
}
|
||||
|
||||
m_offset += m_mapped_size;
|
||||
@@ -328,7 +305,7 @@ void StreamBuffer::Commit() {
|
||||
return;
|
||||
}
|
||||
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
|
||||
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
|
||||
m_current_watches.resize(m_current_watches.size() + WATCHES_RESERVE_CHUNK);
|
||||
}
|
||||
auto& watch = m_current_watches[m_current_watch_cursor++];
|
||||
watch.upper_bound = m_offset;
|
||||
@@ -344,10 +321,6 @@ uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignmen
|
||||
return offset;
|
||||
}
|
||||
|
||||
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
|
||||
watches.resize(watches.size() + grow_size);
|
||||
}
|
||||
|
||||
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
|
||||
std::optional<size_t> invalidation_mark,
|
||||
uint64_t requested_upper_bound, bool allow_wait,
|
||||
|
||||
+13
-11
@@ -6,19 +6,19 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
VK_DEFINE_HANDLE(VmaAllocation)
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
class CommandScheduler;
|
||||
struct StreamBufferTestAccess;
|
||||
struct GraphicContext;
|
||||
struct VulkanBuffer;
|
||||
|
||||
enum class MemoryUsage : uint8_t {
|
||||
DeviceLocal,
|
||||
@@ -42,10 +42,10 @@ public:
|
||||
~Buffer();
|
||||
KYTY_CLASS_NO_COPY(Buffer);
|
||||
|
||||
[[nodiscard]] vk::Buffer Handle() const noexcept;
|
||||
[[nodiscard]] vk::Buffer Handle() const noexcept { return m_buffer; }
|
||||
[[nodiscard]] uint64_t Size() const noexcept { return m_size; }
|
||||
[[nodiscard]] std::span<uint8_t> Mapped() const noexcept { return m_mapped; }
|
||||
[[nodiscard]] bool IsCoherent() const noexcept { return m_is_coherent; }
|
||||
[[nodiscard]] bool IsCoherent() const noexcept { return m_coherent; }
|
||||
[[nodiscard]] MemoryUsage Usage() const noexcept { return m_usage; }
|
||||
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
|
||||
[[nodiscard]] vk::DeviceAddress BufferDeviceAddress() const noexcept;
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
return address - m_cpu_address;
|
||||
}
|
||||
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
|
||||
void IncreaseStreamScore(int score) noexcept { stream_score += score; }
|
||||
[[nodiscard]] int StreamScore() const noexcept { return stream_score; }
|
||||
void Write(uint64_t offset, const void* source, uint64_t size);
|
||||
void Flush(uint64_t offset, uint64_t size);
|
||||
void Invalidate(uint64_t offset, uint64_t size);
|
||||
@@ -68,13 +70,13 @@ public:
|
||||
void Fill(uint64_t offset, uint64_t size, uint32_t value);
|
||||
|
||||
// BufferCache state lives directly on the resource.
|
||||
bool is_deleted = false;
|
||||
size_t lru_id = 0;
|
||||
bool is_deleted = false;
|
||||
int stream_score = 0;
|
||||
size_t lru_id = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
|
||||
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
|
||||
[[nodiscard]] VulkanBuffer& NativeBuffer() noexcept { return *m_buffer; }
|
||||
|
||||
private:
|
||||
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
|
||||
@@ -85,11 +87,12 @@ private:
|
||||
CommandScheduler* m_scheduler = nullptr;
|
||||
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
|
||||
uint64_t m_cpu_address = 0;
|
||||
uint64_t m_size = 0;
|
||||
vk::DeviceAddress m_device_address = 0;
|
||||
std::unique_ptr<VulkanBuffer> m_buffer;
|
||||
vk::Buffer m_buffer = nullptr;
|
||||
VmaAllocation m_allocation = nullptr;
|
||||
uint64_t m_size;
|
||||
bool m_coherent = false;
|
||||
std::span<uint8_t> m_mapped;
|
||||
bool m_is_coherent = false;
|
||||
};
|
||||
|
||||
class StreamBuffer final: public Buffer {
|
||||
@@ -110,7 +113,6 @@ private:
|
||||
uint64_t upper_bound = 0;
|
||||
};
|
||||
|
||||
void ReserveWatches(std::vector<Watch>& watches, size_t grow_size);
|
||||
[[nodiscard]] static bool NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
|
||||
uint64_t& alignment);
|
||||
[[nodiscard]] bool WaitPendingOperations(const std::vector<Watch>& watches,
|
||||
|
||||
+382
-288
File diff suppressed because it is too large.
Load diff
+15
-18
@@ -37,12 +37,6 @@ public:
|
||||
BindingType type = BindingType::Texture;
|
||||
};
|
||||
|
||||
struct RegionInfo {
|
||||
bool image_pages = false;
|
||||
bool image_bytes = false;
|
||||
bool gpu_image_bytes = false;
|
||||
};
|
||||
|
||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||
BufferCache& buffer_cache);
|
||||
~TextureCache();
|
||||
@@ -66,15 +60,14 @@ public:
|
||||
uint32_t packed_clear);
|
||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t address, uint64_t size);
|
||||
|
||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
|
||||
uint32_t* fill_value = nullptr);
|
||||
[[nodiscard]] bool ClearMeta(uint64_t address);
|
||||
// Returns true when registered DCC absorbed the fill and the caller may skip the dispatch.
|
||||
// False may still record PendingDcc state, but the guest dispatch must execute.
|
||||
[[nodiscard]] bool TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
|
||||
// Record deferred DCC state while the original guest dispatch writes the metadata.
|
||||
void TrackDccFill(uint64_t address, uint64_t size, uint32_t fill_value);
|
||||
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
|
||||
|
||||
void UnmapMemory(uint64_t address, uint64_t size);
|
||||
@@ -83,12 +76,12 @@ public:
|
||||
|
||||
private:
|
||||
enum class TransferDirection { Upload, Download };
|
||||
struct ColorTransferPlan;
|
||||
struct TextureTransferPlan;
|
||||
struct DownloadPlan;
|
||||
|
||||
struct MetaDataInfo {
|
||||
// A guest metadata-fill dispatch may initialize DCC before its render target is bound.
|
||||
// PendingDcc retains that exact fill until FindRenderTarget classifies the address,
|
||||
// PendingDcc retains that exact fill until an image binding classifies the address,
|
||||
// without exposing an unconfirmed buffer address to the normal metadata heuristics.
|
||||
// Keep all surface metadata in one entry so CMask/FMask can be
|
||||
// registered beside HTile and DCC without introducing parallel tracking paths.
|
||||
@@ -137,16 +130,20 @@ private:
|
||||
[[nodiscard]] ImageId ResolveDepthOverlap(const ImageInfo& requested, BindingType binding,
|
||||
ImageId cached);
|
||||
[[nodiscard]] ImageId ExpandImage(const ImageInfo& info, ImageId source);
|
||||
void RefreshImage(ImageId id, const ImageDesc& desc);
|
||||
void InitializeImage(ImageId id, const ImageDesc& desc);
|
||||
[[nodiscard]] ColorTransferPlan BuildColorTransfer(const Image& image, BindingType binding,
|
||||
TransferDirection direction) const;
|
||||
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
|
||||
void UploadImage(Image& image, const ImageDesc& desc, Buffer& source, uint64_t source_offset);
|
||||
void RefreshImage(ImageId id);
|
||||
void PrepareDccClear(ImageId id, const ImageDesc& desc);
|
||||
void InitializeImage(ImageId id);
|
||||
[[nodiscard]] TextureTransferPlan
|
||||
BuildTextureTransfer(const Image& image, BindingType binding, TransferDirection direction) const;
|
||||
[[nodiscard]] DownloadPlan BuildDownload(const Image& image) const;
|
||||
void UploadImage(Image& image, Buffer& source, uint64_t source_offset);
|
||||
void DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
|
||||
uint64_t destination_size, DownloadPlan plan);
|
||||
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
|
||||
void CommitGpuWrite(Image& image);
|
||||
// Caller holds m_lock. Volume layer ranges select depth slices.
|
||||
void ClearImage(CommandBuffer& command, ImageId id, const vk::ImageSubresourceRange& range,
|
||||
const vk::ClearValue& clear);
|
||||
void PrepareImageCopy(Image& image);
|
||||
void RefreshCopySource(ImageId id);
|
||||
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
@@ -21,47 +22,40 @@ namespace Libs::Graphics {
|
||||
|
||||
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
||||
|
||||
static void ResolveDccClearInfo(RenderColorInfo& info, vk::Format format, bool has_dcc,
|
||||
uint32_t packed_clear) {
|
||||
// Register-backed DCC clears use the target's packed clear value. Decode one-word guest
|
||||
// formats here; unsupported encodings remain tracked without unsafe materialization.
|
||||
info.metadata_clear_supported =
|
||||
has_dcc && DecodePackedColorClear(format, packed_clear, info.color_clear_value);
|
||||
switch (format) {
|
||||
case vk::Format::eR8G8B8A8Unorm:
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Unorm:
|
||||
case vk::Format::eB8G8R8A8Srgb:
|
||||
case vk::Format::eA2B10G10R10UnormPack32:
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
info.metadata_fixed_clear_supported = has_dcc;
|
||||
break;
|
||||
default: info.metadata_fixed_clear_supported = false; break;
|
||||
static bool DccAlphaOnMsb(const HW::ColorInfo& info) {
|
||||
switch (info.format) {
|
||||
case Prospero::ChannelLayout::k10_10_10_2:
|
||||
case Prospero::ChannelLayout::k10_10_10_2Float:
|
||||
case Prospero::ChannelLayout::k5_5_5_1: return true;
|
||||
case Prospero::ChannelLayout::k2_10_10_10:
|
||||
case Prospero::ChannelLayout::k1_5_5_5: return false;
|
||||
default: break;
|
||||
}
|
||||
if (!info.metadata_clear_supported) {
|
||||
info.color_clear_value = {};
|
||||
const auto components =
|
||||
Prospero::ResolveRenderTargetFormat(info.format, info.channel_type).components;
|
||||
if (components == 1) {
|
||||
return info.channel_order != Prospero::ChannelOrder::kStandard;
|
||||
}
|
||||
return components == 3 || info.channel_order == Prospero::ChannelOrder::kStandard ||
|
||||
info.channel_order == Prospero::ChannelOrder::kAlt;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderColorInfo& r,
|
||||
void RenderExecutor::ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& r,
|
||||
uint32_t render_target_slice_offset,
|
||||
uint32_t render_target_slot, bool ignore_target_mask,
|
||||
uint32_t rt_slot, bool ignore_target_mask,
|
||||
bool exact_format) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
|
||||
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(buffer)
|
||||
: render_target_slot);
|
||||
const auto& rt = hw.GetRenderTarget(rt_slot);
|
||||
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
|
||||
if (ignore_target_mask && rt.base.addr != 0 && mask == 0) {
|
||||
mask = 0x0f;
|
||||
}
|
||||
|
||||
r.target_slot = rt_slot;
|
||||
r.export_mapping = {};
|
||||
r = {};
|
||||
r.target_slot = rt_slot;
|
||||
|
||||
if (rt.base.addr == 0 || mask == 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
@@ -76,23 +70,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
}
|
||||
}
|
||||
|
||||
// No color output
|
||||
r.type = RenderColorType::NoColorOutput;
|
||||
r.desc = {};
|
||||
r.base_addr = 0;
|
||||
r.image_id = {};
|
||||
r.image_view = nullptr;
|
||||
r.format = vk::Format::eUndefined;
|
||||
r.extent = {};
|
||||
r.base_mip_level = 0;
|
||||
r.base_array_layer = 0;
|
||||
r.buffer_size = 0;
|
||||
r.samples = 1;
|
||||
r.export_mapping = {};
|
||||
r.color_clear_enable = false;
|
||||
r.metadata_clear_supported = false;
|
||||
r.metadata_fixed_clear_supported = false;
|
||||
r.color_clear_value = {};
|
||||
return;
|
||||
}
|
||||
const auto samples = render_sample_count(rt.attrib.num_fragments);
|
||||
@@ -100,50 +77,11 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
EXIT("unsupported render-target sample configuration: samples=%u fragments=%u\n",
|
||||
rt.attrib.num_samples, rt.attrib.num_fragments);
|
||||
}
|
||||
const auto view = ResolveTargetViewInfo(
|
||||
rt.view.base_array_slice_index, rt.view.last_array_slice_index, render_target_slice_offset);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D:
|
||||
case TargetViewType::Image2DArray: break;
|
||||
case TargetViewType::Unsupported:
|
||||
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
|
||||
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
|
||||
render_target_slice_offset);
|
||||
}
|
||||
r.base_array_layer = view.base_layer;
|
||||
const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
|
||||
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
|
||||
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
|
||||
rt.view.current_mip_level, levels);
|
||||
}
|
||||
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("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
|
||||
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
|
||||
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
|
||||
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
|
||||
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
|
||||
static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
|
||||
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
|
||||
static_cast<uint32_t>(rt.info.channel_order));
|
||||
}
|
||||
}
|
||||
|
||||
// Color-control state selects the color-buffer operation and logical blend operation.
|
||||
// The normal copy operation is a regular color write, not an attachment clear.
|
||||
// Nonlinear clear values are still stored as normalized components.
|
||||
// Fast color clears are metadata driven and must be handled explicitly when
|
||||
// that metadata path is implemented; render-pass load must preserve contents.
|
||||
r.color_clear_enable = false;
|
||||
r.color_clear_value = {};
|
||||
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
bool tile = false;
|
||||
static constexpr std::array image_types {Prospero::ImageType::kColor1D,
|
||||
Prospero::ImageType::kColor2D,
|
||||
Prospero::ImageType::kColor3D};
|
||||
@@ -165,7 +103,48 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
if (volume && samples != 1) {
|
||||
EXIT("multisampled 3D render targets are unsupported\n");
|
||||
}
|
||||
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
|
||||
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
|
||||
// For volumes, CB_COLOR_VIEW bounds exported slices; ATTRIB3 defines storage depth.
|
||||
// The host attachment contains only the selected slices that exist in this mip.
|
||||
const uint32_t last_layer = volume
|
||||
? std::min(rt.view.last_array_slice_index,
|
||||
std::max(depth >> rt.view.current_mip_level, 1u) - 1u)
|
||||
: rt.view.last_array_slice_index;
|
||||
const auto view = ResolveTargetViewInfo(
|
||||
rt.view.base_array_slice_index, last_layer, render_target_slice_offset);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D:
|
||||
case TargetViewType::Image2DArray: break;
|
||||
case TargetViewType::Unsupported:
|
||||
EXIT("invalid render-target view: base=%u last=%u draw_offset=%u\n",
|
||||
rt.view.base_array_slice_index, rt.view.last_array_slice_index,
|
||||
render_target_slice_offset);
|
||||
}
|
||||
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("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
|
||||
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
|
||||
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
|
||||
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
|
||||
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
|
||||
static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
|
||||
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
|
||||
static_cast<uint32_t>(rt.info.channel_order));
|
||||
}
|
||||
}
|
||||
|
||||
// Color-control state selects the color-buffer operation and logical blend operation.
|
||||
// The normal copy operation is a regular color write, not an attachment clear.
|
||||
// Nonlinear clear values are still stored as normalized components.
|
||||
// Fast color clears are metadata driven and must be handled explicitly when
|
||||
// that metadata path is implemented; render-pass load must preserve contents.
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
bool tile = false;
|
||||
const bool standard4 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard4KB;
|
||||
const bool standard64 = rt.attrib3.tile_mode == Prospero::TileMode::kStandard64KB;
|
||||
const bool depth_tile = rt.attrib3.tile_mode == Prospero::TileMode::kDepth;
|
||||
@@ -224,19 +203,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
" layer=%u/%u\n",
|
||||
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
|
||||
}
|
||||
if (tile) {
|
||||
if (volume) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
|
||||
} else if (texture_tile) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
|
||||
} else {
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
}
|
||||
if (pitch == 0) {
|
||||
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
|
||||
}
|
||||
// PPSA28068: the linear EULA target and its sampled view must share the padded pitch.
|
||||
if (!tile || volume || texture_tile) {
|
||||
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
|
||||
} else {
|
||||
pitch = width;
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
|
||||
}
|
||||
if (pitch == 0) {
|
||||
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
|
||||
}
|
||||
|
||||
TileSizeOffset mip_sizes[16] {};
|
||||
@@ -305,12 +279,6 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
|
||||
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
|
||||
std::max(height >> rt.view.current_mip_level, 1u)};
|
||||
const uint32_t view_depth = std::max(depth >> rt.view.current_mip_level, 1u);
|
||||
if (volume &&
|
||||
(view.base_layer >= view_depth || view.layer_count > view_depth - view.base_layer)) {
|
||||
EXIT("3D render-target view exceeds mip depth: base=%u count=%u depth=%u mip=%u\n",
|
||||
view.base_layer, view.layer_count, view_depth, rt.view.current_mip_level);
|
||||
}
|
||||
|
||||
auto decision_log_id = g_render_color_log_count.fetch_add(1);
|
||||
if (decision_log_id < 128) {
|
||||
@@ -337,10 +305,14 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
desc.info.tile_mode = rt.attrib3.tile_mode;
|
||||
const bool has_dcc = rt.info.dcc_compression_enable && rt.dcc_addr.addr != 0;
|
||||
if (has_dcc) {
|
||||
// DCC uses a separate metadata allocation. Carry its address through ImageInfo so
|
||||
// TextureCache can associate metadata fills observed before target registration.
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range.address = rt.dcc_addr.addr;
|
||||
TileSizeAlign metadata_size {};
|
||||
(void)TileGetDccSize(width, height, volume ? depth : view.image_layers, bytes_per_element,
|
||||
levels, rt.attrib3.tile_mode, metadata_size, rt.attrib.num_fragments);
|
||||
desc.info.metadata.kind = ImageMetadataKind::Dcc;
|
||||
desc.info.metadata.range = {rt.dcc_addr.addr, metadata_size.size};
|
||||
desc.info.metadata.dcc_clear_word = rt.clear_word0.word0;
|
||||
desc.info.metadata.dcc_clear_register_valid = true;
|
||||
desc.info.metadata.dcc_alpha_msb = DccAlphaOnMsb(rt.info);
|
||||
}
|
||||
for (uint32_t level = 0; level < levels; level++) {
|
||||
if (volume) {
|
||||
@@ -382,18 +354,10 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer&
|
||||
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
|
||||
auto& texture_cache = m_context.GetTextureCache();
|
||||
r.desc = std::move(desc);
|
||||
r.guest_mip_level = rt.view.current_mip_level;
|
||||
r.guest_array_layer = view.base_layer;
|
||||
r.image_id = texture_cache.FindImage(r.desc, exact_format);
|
||||
r.type = RenderColorType::RenderTexture;
|
||||
r.base_addr = rt.base.addr;
|
||||
r.image_view = nullptr;
|
||||
r.format = r.desc.view_info.format;
|
||||
r.extent = view_extent;
|
||||
r.base_mip_level = rt.view.current_mip_level;
|
||||
r.buffer_size = backing_size;
|
||||
r.samples = samples;
|
||||
r.export_mapping = target_format.export_mapping;
|
||||
r.color_clear_enable = false;
|
||||
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
|
||||
BindRenderTarget(r.image_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,33 +6,24 @@
|
||||
#include "graphics/host_gpu/renderer/renderTarget.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class RenderColorType {
|
||||
NoColorOutput,
|
||||
RenderTexture,
|
||||
};
|
||||
|
||||
struct RenderColorInfo {
|
||||
RenderColorType type = RenderColorType::NoColorOutput;
|
||||
// Discovery keeps guest image information but can remap the view into a larger cache image.
|
||||
TextureCache::ImageDesc desc;
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
vk::Extent2D extent = {};
|
||||
uint32_t base_mip_level = 0;
|
||||
uint32_t base_array_layer = 0;
|
||||
uint64_t base_addr = 0;
|
||||
uint64_t buffer_size = 0;
|
||||
uint32_t target_slot = 0;
|
||||
uint32_t samples = 1;
|
||||
uint32_t guest_mip_level = 0;
|
||||
uint32_t guest_array_layer = 0;
|
||||
Prospero::ColorComponentMapping export_mapping;
|
||||
bool color_clear_enable = false;
|
||||
bool metadata_clear_supported = false;
|
||||
bool metadata_fixed_clear_supported = false;
|
||||
vk::ClearColorValue color_clear_value {};
|
||||
|
||||
[[nodiscard]] vk::Extent2D Extent() const {
|
||||
return {std::max(desc.info.extent.width >> guest_mip_level, 1u),
|
||||
std::max(desc.info.extent.height >> guest_mip_level, 1u)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -19,11 +19,11 @@ void ReportVulkanFatal(const char* what, vk::Result result, uint64_t tick, uint3
|
||||
uint32_t arg3, uint64_t arg4) {
|
||||
LOGF("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
|
||||
what, vk::to_string(result).c_str(), static_cast<int>(result), tick, debug_op,
|
||||
debug_submit, arg0, arg1, arg2, arg3, arg4);
|
||||
std::printf("%s failed: %s (%d), tick=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
what, VulkanToString(result).c_str(), static_cast<int>(result), tick, debug_op,
|
||||
what, vk::to_string(result).c_str(), static_cast<int>(result), tick, debug_op,
|
||||
debug_submit, arg0, arg1, arg2, arg3, arg4);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
@@ -34,7 +34,6 @@ CommandScheduler::CommandPool::CommandPool(GraphicContext& graphics, MasterSemap
|
||||
: m_graphics(graphics), m_master(master) {
|
||||
EXIT_IF(graphics.queue_family == static_cast<uint32_t>(-1));
|
||||
vk::CommandPoolCreateInfo create {};
|
||||
create.sType = vk::StructureType::eCommandPoolCreateInfo;
|
||||
create.queueFamilyIndex = graphics.queue_family;
|
||||
create.flags = vk::CommandPoolCreateFlagBits::eTransient |
|
||||
vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
||||
@@ -52,7 +51,6 @@ size_t CommandScheduler::CommandPool::Grow() {
|
||||
m_buffers.resize(first + GrowStep);
|
||||
|
||||
vk::CommandBufferAllocateInfo allocate {};
|
||||
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
|
||||
allocate.commandPool = m_pool;
|
||||
allocate.level = vk::CommandBufferLevel::ePrimary;
|
||||
allocate.commandBufferCount = static_cast<uint32_t>(GrowStep);
|
||||
@@ -87,10 +85,7 @@ vk::CommandBuffer CommandScheduler::CommandPool::Commit() {
|
||||
m_ticks[*found] = m_master.CurrentTick();
|
||||
}
|
||||
|
||||
m_hint = *found + 1;
|
||||
if (m_hint == m_ticks.size()) {
|
||||
m_hint = 0;
|
||||
}
|
||||
m_hint = (*found + 1) % m_ticks.size();
|
||||
return m_buffers[*found];
|
||||
}
|
||||
|
||||
@@ -153,14 +148,10 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
|
||||
std::lock_guard lock(m_operation_mutex);
|
||||
EXIT_IF(m_operation_state != OperationState::Open);
|
||||
}
|
||||
m_registers = ®isters;
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
m_command.Bind(registers, user_config, shaders);
|
||||
|
||||
if (m_command.IsInvalid()) {
|
||||
BeginNext();
|
||||
} else {
|
||||
BindCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,13 +208,7 @@ void CommandScheduler::Wait(uint64_t tick) {
|
||||
}
|
||||
|
||||
void CommandScheduler::PopPendingOperations() {
|
||||
PopPendingOperations(true);
|
||||
}
|
||||
|
||||
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
|
||||
if (refresh_gpu_tick) {
|
||||
m_master.Refresh();
|
||||
}
|
||||
m_master.Refresh();
|
||||
for (;;) {
|
||||
PendingOperation operation;
|
||||
{
|
||||
@@ -351,11 +336,6 @@ CommandBuffer& CommandScheduler::Current() {
|
||||
return m_command;
|
||||
}
|
||||
|
||||
void CommandScheduler::BindCurrent() {
|
||||
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
|
||||
m_command.Bind(*m_registers, *m_user_config, *m_shaders);
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::BeginCommand() {
|
||||
EXIT_IF(!m_command.IsInvalid());
|
||||
m_command.m_buffer = m_command_pool.Commit();
|
||||
@@ -381,14 +361,12 @@ uint64_t CommandScheduler::Submit(SubmitInfo submit) {
|
||||
submit.AddSignal(m_master.Handle(), tick);
|
||||
|
||||
vk::TimelineSemaphoreSubmitInfo timeline_info {};
|
||||
timeline_info.sType = vk::StructureType::eTimelineSemaphoreSubmitInfo;
|
||||
timeline_info.waitSemaphoreValueCount = submit.num_wait_semaphores;
|
||||
timeline_info.pWaitSemaphoreValues = submit.wait_ticks.data();
|
||||
timeline_info.signalSemaphoreValueCount = submit.num_signal_semaphores;
|
||||
timeline_info.pSignalSemaphoreValues = submit.signal_ticks.data();
|
||||
|
||||
vk::SubmitInfo submit_info {};
|
||||
submit_info.sType = vk::StructureType::eSubmitInfo;
|
||||
submit_info.pNext = &timeline_info;
|
||||
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
|
||||
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
|
||||
@@ -414,8 +392,7 @@ uint64_t CommandScheduler::Submit(SubmitInfo submit) {
|
||||
}
|
||||
|
||||
void CommandScheduler::BeginNext() {
|
||||
EXIT_IF(!m_command.IsInvalid());
|
||||
BindCurrent();
|
||||
CheckActive();
|
||||
BeginCommand();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
||||
|
||||
[[nodiscard]] bool Active() const noexcept { return m_registers != nullptr; }
|
||||
[[nodiscard]] bool Active() const noexcept { return m_command.m_registers != nullptr; }
|
||||
void CheckActive() const;
|
||||
CommandBuffer& Current();
|
||||
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
|
||||
@@ -80,9 +80,7 @@ private:
|
||||
uint64_t tick = 0;
|
||||
};
|
||||
|
||||
void BindCurrent();
|
||||
void BeginNext();
|
||||
void PopPendingOperations(bool refresh_gpu_tick);
|
||||
void PriorityOperationsThread(std::stop_token stop);
|
||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||
|
||||
@@ -99,9 +97,6 @@ private:
|
||||
bool m_priority_active = false;
|
||||
uint64_t m_priority_active_tick = 0;
|
||||
OperationState m_operation_state = OperationState::Open;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
HW::Shader* m_shaders = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -34,10 +33,7 @@ void CommandBuffer::Begin() {
|
||||
auto buffer = Handle();
|
||||
|
||||
vk::CommandBufferBeginInfo begin_info {};
|
||||
begin_info.sType = vk::StructureType::eCommandBufferBeginInfo;
|
||||
begin_info.pNext = nullptr;
|
||||
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
||||
begin_info.pInheritanceInfo = nullptr;
|
||||
begin_info.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
||||
|
||||
auto result = buffer.begin(&begin_info);
|
||||
|
||||
@@ -75,7 +71,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
std::array<vk::RenderingAttachmentInfo, RENDER_COLOR_ATTACHMENTS_MAX> colors {};
|
||||
for (uint32_t i = 0; i < state.num_color_attachments; i++) {
|
||||
const auto& attachment = state.color_attachments[i];
|
||||
colors[i].sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
colors[i].imageView = attachment.image_view;
|
||||
colors[i].imageLayout = attachment.image_layout;
|
||||
colors[i].loadOp =
|
||||
@@ -86,7 +81,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
|
||||
const auto& depth_stencil = state.depth_stencil_attachment;
|
||||
vk::RenderingAttachmentInfo depth {};
|
||||
depth.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth.imageView = depth_stencil.image_view;
|
||||
depth.imageLayout = depth_stencil.image_layout;
|
||||
depth.loadOp =
|
||||
@@ -95,7 +89,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
|
||||
|
||||
vk::RenderingAttachmentInfo stencil {};
|
||||
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
stencil.imageView = depth_stencil.image_view;
|
||||
stencil.imageLayout = depth_stencil.image_layout;
|
||||
stencil.loadOp =
|
||||
@@ -104,7 +97,6 @@ void CommandBuffer::BeginRendering(const RenderState& state) const {
|
||||
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
|
||||
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = {state.width, state.height};
|
||||
rendering.layerCount = state.num_layers;
|
||||
rendering.colorAttachmentCount = state.num_color_attachments;
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
static std::atomic<uint32_t> g_scissor_default_log_count = 0;
|
||||
|
||||
uint32_t render_target_mask_slot(uint32_t mask, uint32_t slot) {
|
||||
return (mask >> (slot * 4u)) & 0x0fu;
|
||||
}
|
||||
@@ -387,81 +385,6 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
z.size.valid ? "true" : "false");
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
|
||||
const HW::RenderControl& rc) {
|
||||
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
|
||||
rc.depth_clear_enable || rc.copy_depth_to_color;
|
||||
const bool stencil_active =
|
||||
dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color;
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(rc.copy_depth_to_color || rc.copy_stencil_to_color);
|
||||
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
|
||||
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
|
||||
if (z.z_info.format == Prospero::DepthFormat::kInvalid) {
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.format != Prospero::DepthFormat::kInvalid);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.format != Prospero::DepthFormat::kZ16 &&
|
||||
z.z_info.format != Prospero::DepthFormat::kZ32F);
|
||||
if (z.z_info.num_samples != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: using native num_samples=0x%08" PRIx32 "\n",
|
||||
z.z_info.num_samples);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
}
|
||||
|
||||
if (z.stencil_info.format == Prospero::StencilFormat::kInvalid) {
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != Prospero::StencilFormat::k8UInt);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
|
||||
}
|
||||
|
||||
if (z.z_info.format != Prospero::DepthFormat::kInvalid ||
|
||||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
|
||||
EXIT_NOT_IMPLEMENTED(z.shading_rate_encoding != 0);
|
||||
|
||||
if (z.depth_view.current_mip_level != 0x00000000) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 current mip level=0x%08" PRIx32 "\n",
|
||||
z.depth_view.current_mip_level);
|
||||
}
|
||||
}
|
||||
if (z.depth_view.depth_write_disable || z.depth_view.stencil_write_disable) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("DepthTarget: honoring write disable depth=%s, stencil=%s\n",
|
||||
z.depth_view.depth_write_disable ? "true" : "false",
|
||||
z.depth_view.stencil_write_disable ? "true" : "false");
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable &&
|
||||
z.z_read_base_addr != z.z_write_base_addr);
|
||||
EXIT_NOT_IMPLEMENTED(!z.depth_view.stencil_write_disable &&
|
||||
z.stencil_read_base_addr != z.stencil_write_base_addr);
|
||||
EXIT_NOT_IMPLEMENTED(!z.depth_view.depth_write_disable && z.z_write_base_addr == 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.htile_data_base_addr == 0);
|
||||
EXIT_NOT_IMPLEMENTED(!z.size.valid);
|
||||
}
|
||||
}
|
||||
|
||||
static void ClipPrint(const char* func, const HW::ClipControl& c) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
@@ -546,16 +469,6 @@ static void McCheck(const HW::ModeControl& c) {
|
||||
// EXIT_NOT_IMPLEMENTED(c.cull_front != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.cull_back != false);
|
||||
// EXIT_NOT_IMPLEMENTED(c.face != false);
|
||||
if (c.poly_mode != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_MODE is not fully implemented; continuing "
|
||||
"with filled polygons\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(c.polymode_front_ptype != 0 && c.polymode_front_ptype != 2);
|
||||
EXIT_NOT_IMPLEMENTED(c.polymode_back_ptype != 0 && c.polymode_back_ptype != 2);
|
||||
if (c.vtx_window_offset_enable) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -565,7 +478,6 @@ static void McCheck(const HW::ModeControl& c) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(c.provoking_vtx_last != false);
|
||||
EXIT_NOT_IMPLEMENTED(c.persp_corr_dis != false);
|
||||
}
|
||||
|
||||
@@ -845,15 +757,6 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
|
||||
// EXIT_NOT_IMPLEMENTED(smc.vport_scissor_enable);
|
||||
EXIT_NOT_IMPLEMENTED(smc.line_stipple_enable);
|
||||
|
||||
if (vp.viewports[0].zmin > 0.000000 || vp.viewports[0].zmax != 1.000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("\t warning: non-default viewport depth clamp zmin = %f, zmax = %f; using "
|
||||
"viewport scale/offset for Vulkan depth range\n",
|
||||
vp.viewports[0].zmin, vp.viewports[0].zmax);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].xscale != 960.000000);
|
||||
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].xoffset != 960.000000);
|
||||
// EXIT_NOT_IMPLEMENTED(vp.viewports[0].yscale != -540.000000);
|
||||
@@ -873,7 +776,7 @@ static void VpCheck(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc
|
||||
// EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_horz_clip - 33.133327f) > 0.001f);
|
||||
// EXIT_NOT_IMPLEMENTED(fabsf(vp.guard_band_vert_clip - 59.629623f) > 0.001f);
|
||||
|
||||
if (vp.guard_band_horz_discard != 0.0f || vp.guard_band_vert_discard != 0.0f) {
|
||||
if (vp.guard_band_horz_discard != 1.0f || vp.guard_band_vert_discard != 1.0f) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 16) {
|
||||
LOGF("\t warning: unsupported PS5 guard band discard = %f, %f, continuing\n",
|
||||
@@ -893,10 +796,6 @@ static bool ScissorRectValid(const ScissorRect& r) {
|
||||
return r.right > r.left && r.bottom > r.top;
|
||||
}
|
||||
|
||||
static bool ScissorRectSet(const ScissorRect& r) {
|
||||
return r.left != 0 || r.top != 0 || r.right != 0 || r.bottom != 0;
|
||||
}
|
||||
|
||||
static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) {
|
||||
r.left += x;
|
||||
r.right += x;
|
||||
@@ -906,8 +805,8 @@ static ScissorRect ScissorRectOffset(ScissorRect r, int x, int y) {
|
||||
}
|
||||
|
||||
static ScissorRect ScissorRectIntersect(const ScissorRect& a, const ScissorRect& b) {
|
||||
return {a.left > b.left ? a.left : b.left, a.top > b.top ? a.top : b.top,
|
||||
a.right < b.right ? a.right : b.right, a.bottom < b.bottom ? a.bottom : b.bottom};
|
||||
return {std::max(a.left, b.left), std::max(a.top, b.top),
|
||||
std::min(a.right, b.right), std::min(a.bottom, b.bottom)};
|
||||
}
|
||||
|
||||
static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t height) {
|
||||
@@ -954,49 +853,26 @@ static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
|
||||
}
|
||||
|
||||
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
|
||||
vk::Extent2D extent) {
|
||||
ScissorRect screen {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
|
||||
vp.screen_scissor_bottom};
|
||||
ScissorRect final = screen;
|
||||
|
||||
if (!ScissorRectSet(screen)) {
|
||||
final = {0, 0, static_cast<int>(extent.width), static_cast<int>(extent.height)};
|
||||
|
||||
auto log_id = g_scissor_default_log_count.fetch_add(1);
|
||||
if (log_id < 32) {
|
||||
LOGF("temporary: default unset screen scissor to framebuffer extent %ux%u\n",
|
||||
extent.width, extent.height);
|
||||
vk::Extent2D extent, uint32_t viewport_index) {
|
||||
EXIT_IF(viewport_index >= std::size(vp.viewports));
|
||||
ScissorRect final {vp.screen_scissor_left, vp.screen_scissor_top, vp.screen_scissor_right,
|
||||
vp.screen_scissor_bottom};
|
||||
const auto intersect = [&](ScissorRect rect, bool window_offset) {
|
||||
if (window_offset) {
|
||||
rect = ScissorRectOffset(rect, vp.window_offset_x, vp.window_offset_y);
|
||||
}
|
||||
}
|
||||
final = ScissorRectIntersect(final, rect);
|
||||
};
|
||||
intersect({vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
|
||||
vp.window_scissor_bottom}, vp.window_scissor_window_offset_enable);
|
||||
intersect({vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
|
||||
vp.generic_scissor_bottom}, vp.generic_scissor_window_offset_enable);
|
||||
|
||||
ScissorRect window {vp.window_scissor_left, vp.window_scissor_top, vp.window_scissor_right,
|
||||
vp.window_scissor_bottom};
|
||||
if (ScissorRectSet(window)) {
|
||||
if (vp.window_scissor_window_offset_enable) {
|
||||
window = ScissorRectOffset(window, vp.window_offset_x, vp.window_offset_y);
|
||||
}
|
||||
final = ScissorRectIntersect(final, window);
|
||||
}
|
||||
|
||||
ScissorRect generic {vp.generic_scissor_left, vp.generic_scissor_top, vp.generic_scissor_right,
|
||||
vp.generic_scissor_bottom};
|
||||
if (ScissorRectSet(generic)) {
|
||||
if (vp.generic_scissor_window_offset_enable) {
|
||||
generic = ScissorRectOffset(generic, vp.window_offset_x, vp.window_offset_y);
|
||||
}
|
||||
final = ScissorRectIntersect(final, generic);
|
||||
}
|
||||
|
||||
const auto& viewport = vp.viewports[0];
|
||||
ScissorRect viewport_scissor {viewport.viewport_scissor_left, viewport.viewport_scissor_top,
|
||||
viewport.viewport_scissor_right,
|
||||
viewport.viewport_scissor_bottom};
|
||||
if (smc.vport_scissor_enable && ScissorRectSet(viewport_scissor)) {
|
||||
if (viewport.viewport_scissor_window_offset_enable) {
|
||||
viewport_scissor =
|
||||
ScissorRectOffset(viewport_scissor, vp.window_offset_x, vp.window_offset_y);
|
||||
}
|
||||
final = ScissorRectIntersect(final, viewport_scissor);
|
||||
const auto& viewport = vp.viewports[viewport_index];
|
||||
if (smc.vport_scissor_enable) {
|
||||
intersect({viewport.viewport_scissor_left, viewport.viewport_scissor_top,
|
||||
viewport.viewport_scissor_right, viewport.viewport_scissor_bottom},
|
||||
viewport.viewport_scissor_window_offset_enable);
|
||||
}
|
||||
|
||||
if (vp.clip_rect_rule == 0) {
|
||||
@@ -1009,12 +885,8 @@ ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeC
|
||||
continue;
|
||||
}
|
||||
|
||||
ScissorRect clip {vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
|
||||
vp.clip_rect_bottom[i]};
|
||||
if (vp.clip_rect_window_offset_enable[i]) {
|
||||
clip = ScissorRectOffset(clip, vp.window_offset_x, vp.window_offset_y);
|
||||
}
|
||||
final = ScissorRectIntersect(final, clip);
|
||||
intersect({vp.clip_rect_left[i], vp.clip_rect_top[i], vp.clip_rect_right[i],
|
||||
vp.clip_rect_bottom[i]}, vp.clip_rect_window_offset_enable[i]);
|
||||
}
|
||||
} else {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
@@ -1035,7 +907,6 @@ void hw_check(const CommandBuffer& buffer) {
|
||||
const auto& bc = hw.GetBlendControl(rt_slot);
|
||||
const auto& bclr = hw.GetBlendColor();
|
||||
const auto& vp = hw.GetScreenViewport();
|
||||
const auto& z = hw.GetDepthRenderTarget();
|
||||
const auto& c = hw.GetClipControl();
|
||||
const auto& rc = hw.GetRenderControl();
|
||||
const auto& d = hw.GetDepthControl();
|
||||
@@ -1061,8 +932,6 @@ void hw_check(const CommandBuffer& buffer) {
|
||||
RtCheck(rt);
|
||||
log_phase("vp");
|
||||
VpCheck(vp, smc);
|
||||
log_phase("z");
|
||||
ZCheck(z, d, rc);
|
||||
log_phase("clip");
|
||||
ClipCheck(c);
|
||||
log_phase("rc");
|
||||
|
||||
@@ -39,7 +39,7 @@ void hw_print(const CommandBuffer& buffer);
|
||||
void hw_check(const CommandBuffer& buffer);
|
||||
void LogDrawPhase(const char* draw_name, const char* phase);
|
||||
ScissorRect calc_final_scissor(const HW::ScreenViewport& vp, const HW::ScanModeControl& smc,
|
||||
vk::Extent2D extent);
|
||||
vk::Extent2D extent, uint32_t viewport_index);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -97,10 +97,8 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderDepthInfo& r) {
|
||||
void RenderExecutor::ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& r) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
(void)submit_id;
|
||||
const auto& hw = buffer.GetRegisters();
|
||||
const auto& z = hw.GetDepthRenderTarget();
|
||||
const auto& rc = hw.GetRenderControl();
|
||||
@@ -108,7 +106,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
const auto& sc = hw.GetStencilControl();
|
||||
const auto& sm = hw.GetStencilMask();
|
||||
const bool has_stencil = z.stencil_info.format != Prospero::StencilFormat::kInvalid;
|
||||
const bool depth_active = dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable ||
|
||||
const bool depth_active = dc.z_enable || dc.depth_bounds_enable ||
|
||||
rc.depth_clear_enable || rc.copy_depth_to_color;
|
||||
const bool stencil_active =
|
||||
has_stencil && (dc.stencil_enable || rc.stencil_clear_enable || rc.copy_stencil_to_color);
|
||||
@@ -144,6 +142,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
return;
|
||||
}
|
||||
const bool has_htile = z.z_info.htile_acceleration;
|
||||
const bool unsupported_shading_rate_encoding =
|
||||
z.shading_rate_encoding > 1 || (z.shading_rate_encoding != 0 && !has_htile);
|
||||
const auto samples = render_sample_count(z.z_info.num_samples);
|
||||
if (samples == 0) {
|
||||
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
|
||||
@@ -162,7 +162,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled ||
|
||||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
|
||||
z.depth_view.current_mip_level != 0 || z.shading_rate_encoding != 0 ||
|
||||
z.depth_view.current_mip_level != 0 || unsupported_shading_rate_encoding ||
|
||||
z.z_read_base_addr == 0 ||
|
||||
(!z.depth_view.depth_write_disable && z.z_write_base_addr != z.z_read_base_addr) ||
|
||||
(z.z_read_base_addr & 0xffffu) != 0 ||
|
||||
@@ -201,10 +201,10 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
DepthFatal("unsupported depth/stencil format pair");
|
||||
}
|
||||
const auto ideal_format = DepthAttachmentFormat(*policy, has_stencil);
|
||||
r.format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
|
||||
if (r.format == vk::Format::eUndefined) {
|
||||
const auto format = ResolveHostDepthAttachmentFormat(buffer, *policy, has_stencil, samples);
|
||||
if (format == vk::Format::eUndefined) {
|
||||
DepthFatal("no host depth/stencil format supports required usage for %s",
|
||||
VulkanToString(ideal_format).c_str());
|
||||
vk::to_string(ideal_format).c_str());
|
||||
}
|
||||
const auto guest_format = policy->guest_format;
|
||||
const uint32_t bytes = policy->bytes_per_element;
|
||||
@@ -232,22 +232,12 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
(has_htile && !GuestRange {z.htile_data_base_addr, htile_backing_size}.Valid())) {
|
||||
DepthFatal("layered depth backing range is invalid");
|
||||
}
|
||||
r.htile = has_htile;
|
||||
r.width = width;
|
||||
r.height = height;
|
||||
r.samples = samples;
|
||||
r.depth_buffer_size = depth_backing_size;
|
||||
r.depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_meta_clear_enable = false;
|
||||
r.depth_load_clear_enable = r.depth_clear_enable;
|
||||
r.depth_clear_value = hw.GetDepthClearValue();
|
||||
r.depth_test_enable = dc.z_enable;
|
||||
r.depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
|
||||
r.depth_write_enable = r.depth_test_enable && dc.z_write_enable &&
|
||||
!z.depth_view.depth_write_disable && !r.depth_clear_enable;
|
||||
r.depth_compare_op = static_cast<vk::CompareOp>(dc.zfunc);
|
||||
|
||||
r.depth_bounds_test_enable = dc.depth_bounds_enable;
|
||||
@@ -292,18 +282,12 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
r.stencil_dynamic_back = r.stencil_dynamic_front;
|
||||
}
|
||||
}
|
||||
r.vaddr_num = has_stencil ? 2 : 1;
|
||||
r.vaddr[0] = r.depth_buffer_vaddr;
|
||||
r.size[0] = r.depth_buffer_size;
|
||||
if (has_stencil) {
|
||||
r.vaddr[1] = r.stencil_buffer_vaddr;
|
||||
r.size[1] = r.stencil_buffer_size;
|
||||
}
|
||||
TextureCache::ImageDesc desc {};
|
||||
desc.type = TextureCache::BindingType::DepthTarget;
|
||||
desc.info.data = {r.depth_buffer_vaddr, r.depth_buffer_size};
|
||||
desc.info.stencil = {r.stencil_buffer_vaddr, r.stencil_buffer_size};
|
||||
desc.info.pixel_format = r.format;
|
||||
desc.info.data = {z.z_read_base_addr, depth_backing_size};
|
||||
desc.info.stencil =
|
||||
has_stencil ? GuestRange {z.stencil_read_base_addr, stencil_backing_size} : GuestRange {};
|
||||
desc.info.pixel_format = format;
|
||||
desc.info.guest_format = guest_format;
|
||||
desc.info.type = Prospero::ImageType::kColor2D;
|
||||
desc.info.extent = {width, height, 1};
|
||||
@@ -312,15 +296,16 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
desc.info.bytes_per_block = bytes;
|
||||
desc.info.samples = samples;
|
||||
desc.info.tile_mode = Prospero::TileMode::kDepth;
|
||||
desc.info.mip_layout[0] = {0, r.depth_buffer_size, pitch, height};
|
||||
desc.info.metadata.range = {r.htile_buffer_vaddr, r.htile_buffer_size};
|
||||
desc.info.mip_layout[0] = {0, depth_backing_size, pitch, height};
|
||||
desc.info.metadata.range =
|
||||
has_htile ? GuestRange {z.htile_data_base_addr, htile_backing_size} : GuestRange {};
|
||||
desc.info.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
|
||||
desc.info.metadata.stencil_compressed =
|
||||
has_stencil && has_htile && !z.stencil_info.htile_stencil_disabled;
|
||||
desc.view_info.format = r.format;
|
||||
desc.view_info.format = format;
|
||||
desc.view_info.type =
|
||||
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
|
||||
desc.view_info.aspect = ImageViewOps::DepthAspectMask(r.format);
|
||||
desc.view_info.aspect = ImageViewOps::DepthAspectMask(format);
|
||||
desc.view_info.base_level = 0;
|
||||
desc.view_info.level_count = 1;
|
||||
desc.view_info.base_layer = view.base_layer;
|
||||
@@ -329,11 +314,11 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer&
|
||||
r.desc = std::move(desc);
|
||||
auto& cache = m_context.GetTextureCache();
|
||||
r.image_id = cache.FindImage(r.desc);
|
||||
r.image_view = nullptr;
|
||||
BindRenderTarget(r.image_id);
|
||||
}
|
||||
|
||||
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
const auto format = desc.view_info.format;
|
||||
if (format == vk::Format::eUndefined) {
|
||||
return {};
|
||||
}
|
||||
@@ -341,7 +326,7 @@ vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
|
||||
const auto available = ImageViewOps::DepthAspectMask(format);
|
||||
vk::ImageAspectFlags writes {};
|
||||
if ((available & vk::ImageAspectFlagBits::eDepth) &&
|
||||
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
|
||||
(depth_load_clear_enable || depth_write_enable)) {
|
||||
writes |= vk::ImageAspectFlagBits::eDepth;
|
||||
}
|
||||
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
|
||||
|
||||
@@ -19,26 +19,13 @@ inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stenc
|
||||
}
|
||||
|
||||
struct RenderDepthInfo {
|
||||
// Discovery keeps guest image information but can remap the view into a larger cache image.
|
||||
TextureCache::ImageDesc desc;
|
||||
vk::Format format = vk::Format::eUndefined;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t samples = 1;
|
||||
bool htile = false;
|
||||
uint64_t depth_buffer_size = 0;
|
||||
uint64_t depth_buffer_vaddr = 0;
|
||||
uint64_t depth_tile_swizzle = 0;
|
||||
uint64_t stencil_buffer_size = 0;
|
||||
uint64_t stencil_buffer_vaddr = 0;
|
||||
uint64_t stencil_tile_swizzle = 0;
|
||||
uint64_t htile_buffer_size = 0;
|
||||
uint64_t htile_buffer_vaddr = 0;
|
||||
uint64_t htile_tile_swizzle = 0;
|
||||
bool depth_clear_enable = false;
|
||||
bool depth_load_clear_enable = false;
|
||||
bool depth_meta_clear_enable = false;
|
||||
float depth_clear_value = 0.0f;
|
||||
bool depth_test_enable = false;
|
||||
// Effective draw writes; discovery applies test, target-write and clear controls.
|
||||
bool depth_write_enable = false;
|
||||
vk::CompareOp depth_compare_op = vk::CompareOp::eNever;
|
||||
bool depth_bounds_test_enable = false;
|
||||
@@ -52,24 +39,17 @@ struct RenderDepthInfo {
|
||||
PipelineStencilDynamicState stencil_dynamic_front;
|
||||
PipelineStencilDynamicState stencil_dynamic_back;
|
||||
ImageId image_id;
|
||||
vk::ImageView image_view = nullptr;
|
||||
uint64_t vaddr[3] = {};
|
||||
uint64_t size[3] = {};
|
||||
int vaddr_num = 0;
|
||||
|
||||
[[nodiscard]] vk::ImageAspectFlags AttachmentWriteAspects() const;
|
||||
};
|
||||
|
||||
inline bool depth_attachment_read_only(const RenderDepthInfo& depth) {
|
||||
return !depth.AttachmentWriteAspects();
|
||||
}
|
||||
|
||||
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
|
||||
const auto available = ImageViewOps::DepthAspectMask(depth.format);
|
||||
const auto available = ImageViewOps::DepthAspectMask(depth.desc.view_info.format);
|
||||
const auto writes = depth.AttachmentWriteAspects();
|
||||
const bool has_depth = static_cast<bool>(available & vk::ImageAspectFlagBits::eDepth);
|
||||
const bool has_stencil = static_cast<bool>(available & vk::ImageAspectFlagBits::eStencil);
|
||||
const bool depth_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eDepth);
|
||||
// LoadOp clears are separate from guest depth writes.
|
||||
const bool depth_write = depth.depth_write_enable;
|
||||
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
|
||||
if (!has_stencil) {
|
||||
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
|
||||
|
||||
@@ -23,7 +23,6 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
texture_binding.stageFlags = vk::ShaderStageFlagBits::eFragment;
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = 1;
|
||||
descriptor_info.pBindings = &texture_binding;
|
||||
@@ -32,7 +31,6 @@ BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||
"create BlitHelper descriptor layout");
|
||||
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
RequireVulkanSuccess(
|
||||
@@ -65,7 +63,6 @@ BlitHelper::~BlitHelper() {
|
||||
vk::ShaderModule BlitHelper::CreateShader(const uint32_t* code, size_t words) const {
|
||||
EXIT_IF(code == nullptr || words == 0);
|
||||
vk::ShaderModuleCreateInfo create {};
|
||||
create.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create.codeSize = words * sizeof(uint32_t);
|
||||
create.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
@@ -84,51 +81,37 @@ vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
|
||||
EXIT_IF(samples == vk::SampleCountFlagBits {} || key.format == vk::Format::eUndefined);
|
||||
|
||||
std::array<vk::PipelineShaderStageCreateInfo, 2> stages {};
|
||||
stages[0].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
|
||||
stages[0].module = m_vertex_shader;
|
||||
stages[0].pName = "main";
|
||||
stages[1].sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
|
||||
stages[1].module = m_fragment_shader;
|
||||
stages[1].pName = "main";
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input {};
|
||||
vertex_input.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
|
||||
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
|
||||
input_assembly.topology = vk::PrimitiveTopology::eTriangleList;
|
||||
vk::PipelineViewportStateCreateInfo viewport {};
|
||||
viewport.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
|
||||
viewport.viewportCount = 1;
|
||||
viewport.scissorCount = 1;
|
||||
vk::PipelineRasterizationStateCreateInfo rasterization {};
|
||||
rasterization.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
rasterization.polygonMode = vk::PolygonMode::eFill;
|
||||
rasterization.cullMode = vk::CullModeFlagBits::eNone;
|
||||
rasterization.lineWidth = 1.0f;
|
||||
rasterization.lineWidth = 1.0f;
|
||||
vk::PipelineMultisampleStateCreateInfo multisample {};
|
||||
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
|
||||
multisample.rasterizationSamples = samples;
|
||||
vk::PipelineDepthStencilStateCreateInfo depth {};
|
||||
depth.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
|
||||
depth.depthTestEnable = VK_TRUE;
|
||||
depth.depthWriteEnable = VK_TRUE;
|
||||
depth.depthCompareOp = vk::CompareOp::eAlways;
|
||||
vk::PipelineColorBlendStateCreateInfo color_blend {};
|
||||
color_blend.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
const std::array dynamic_states {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
|
||||
vk::PipelineDynamicStateCreateInfo dynamic {};
|
||||
dynamic.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
|
||||
dynamic.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||
dynamic.pDynamicStates = dynamic_states.data();
|
||||
|
||||
vk::PipelineRenderingCreateInfo rendering {};
|
||||
rendering.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
||||
rendering.depthAttachmentFormat = key.format;
|
||||
|
||||
vk::GraphicsPipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
create.pNext = &rendering;
|
||||
create.stageCount = static_cast<uint32_t>(stages.size());
|
||||
create.pStages = stages.data();
|
||||
@@ -186,7 +169,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
command);
|
||||
|
||||
vk::RenderingAttachmentInfo depth_attachment {};
|
||||
depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
depth_attachment.imageView = destination_view;
|
||||
depth_attachment.imageLayout = ColorToMsDepthLayout;
|
||||
depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
|
||||
@@ -194,7 +176,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
depth_attachment.clearValue.depthStencil = {0.0f, 0};
|
||||
|
||||
vk::RenderingInfo rendering {};
|
||||
rendering.sType = vk::StructureType::eRenderingInfo;
|
||||
rendering.renderArea.extent = {destination_info.extent.width, destination_info.extent.height};
|
||||
rendering.layerCount = 1;
|
||||
rendering.pDepthAttachment = &depth_attachment;
|
||||
@@ -204,7 +185,6 @@ void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
|
||||
descriptor_image.imageView = source_view;
|
||||
descriptor_image.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
vk::WriteDescriptorSet descriptor_write {};
|
||||
descriptor_write.sType = vk::StructureType::eWriteDescriptorSet;
|
||||
descriptor_write.dstBinding = 0;
|
||||
descriptor_write.descriptorCount = 1;
|
||||
descriptor_write.descriptorType = vk::DescriptorType::eSampledImage;
|
||||
|
||||
@@ -26,12 +26,13 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const ImageInfo& info) {
|
||||
[[nodiscard]] vk::ImageCreateFlags ImageCreateFlags(const GraphicContext& graphics,
|
||||
const ImageInfo& info) {
|
||||
vk::ImageCreateFlags flags {};
|
||||
if (DepthAspectTransferFormat(info.pixel_format) == vk::Format::eUndefined) {
|
||||
flags |= vk::ImageCreateFlagBits::eMutableFormat;
|
||||
flags |= vk::ImageCreateFlagBits::eExtendedUsage;
|
||||
if (Prospero::BlockCompressedBytesPerBlock(info.guest_format) != 0) {
|
||||
if (info.IsBlock() && graphics.supports_block_texel_view) {
|
||||
flags |= vk::ImageCreateFlagBits::eBlockTexelViewCompatible;
|
||||
}
|
||||
}
|
||||
@@ -47,6 +48,10 @@ namespace {
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(GraphicContext& graphics, const ImageInfo& info) {
|
||||
if (info.IsBlock()) {
|
||||
return vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst |
|
||||
vk::ImageUsageFlagBits::eSampled;
|
||||
}
|
||||
const auto properties = graphics.GetFormatProperties(info.pixel_format);
|
||||
auto usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eSampledImage)) {
|
||||
@@ -54,6 +59,9 @@ namespace {
|
||||
}
|
||||
if (DepthAspectTransferFormat(info.pixel_format) != vk::Format::eUndefined) {
|
||||
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||
if (graphics.attachment_feedback_loop_enabled && (usage & vk::ImageUsageFlagBits::eSampled)) {
|
||||
usage |= vk::ImageUsageFlagBits::eAttachmentFeedbackLoopEXT;
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) {
|
||||
@@ -202,9 +210,7 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
|
||||
if (barriers.empty()) {
|
||||
return;
|
||||
}
|
||||
if (m_scheduler != nullptr) {
|
||||
m_scheduler->EndRendering();
|
||||
}
|
||||
m_scheduler.EndRendering();
|
||||
vk::DependencyInfo dependency {};
|
||||
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(barriers.size());
|
||||
dependency.pImageMemoryBarriers = barriers.data();
|
||||
@@ -213,8 +219,8 @@ void Image::Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destina
|
||||
|
||||
void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer, uint64_t offset,
|
||||
uint64_t size) {
|
||||
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
|
||||
m_scheduler->EndRendering();
|
||||
EXIT_IF(copies.empty() || buffer == nullptr || size == 0);
|
||||
m_scheduler.EndRendering();
|
||||
vk::BufferMemoryBarrier2 buffer_barrier {};
|
||||
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
buffer_barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
|
||||
@@ -234,7 +240,7 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
|
||||
dependency.pBufferMemoryBarriers = &buffer_barrier;
|
||||
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
|
||||
dependency.pImageMemoryBarriers = image_barriers.data();
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.copyBufferToImage(buffer, backing.image, vk::ImageLayout::eTransferDstOptimal,
|
||||
static_cast<uint32_t>(copies.size()), copies.data());
|
||||
@@ -252,8 +258,8 @@ void Image::Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffe
|
||||
|
||||
void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
|
||||
uint64_t offset, uint64_t size) {
|
||||
EXIT_IF(m_scheduler == nullptr || copies.empty() || buffer == nullptr || size == 0);
|
||||
m_scheduler->EndRendering();
|
||||
EXIT_IF(copies.empty() || buffer == nullptr || size == 0);
|
||||
m_scheduler.EndRendering();
|
||||
vk::BufferMemoryBarrier2 buffer_barrier {};
|
||||
buffer_barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
|
||||
buffer_barrier.srcAccessMask =
|
||||
@@ -274,7 +280,7 @@ void Image::Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buf
|
||||
dependency.pBufferMemoryBarriers = &buffer_barrier;
|
||||
dependency.imageMemoryBarrierCount = static_cast<uint32_t>(image_barriers.size());
|
||||
dependency.pImageMemoryBarriers = image_barriers.data();
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
command.pipelineBarrier2(dependency);
|
||||
command.copyImageToBuffer(backing.image, vk::ImageLayout::eTransferSrcOptimal, buffer,
|
||||
static_cast<uint32_t>(copies.size()), copies.data());
|
||||
@@ -311,8 +317,8 @@ std::pair<uint32_t, uint32_t> Image::SanitizeCopyLayers(const Image& source,
|
||||
}
|
||||
|
||||
void Image::CopyImage(Image& source) {
|
||||
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples);
|
||||
m_scheduler->EndRendering();
|
||||
EXIT_IF(source.backing.samples != backing.samples);
|
||||
m_scheduler.EndRendering();
|
||||
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
|
||||
const uint32_t base_depth = backing.image_type == vk::ImageType::e3D
|
||||
? backing.extent.depth
|
||||
@@ -351,7 +357,7 @@ void Image::CopyImage(Image& source) {
|
||||
if (copies.empty()) {
|
||||
return;
|
||||
}
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
@@ -364,8 +370,7 @@ void Image::CopyImage(Image& source) {
|
||||
|
||||
void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
const ImageSubresourceRange& destination_range) {
|
||||
EXIT_IF(m_scheduler == nullptr || backing.samples != 1 ||
|
||||
source.backing.image_type != vk::ImageType::e2D ||
|
||||
EXIT_IF(backing.samples != 1 || source.backing.image_type != vk::ImageType::e2D ||
|
||||
backing.image_type != vk::ImageType::e2D || source_range.level_count != 1 ||
|
||||
destination_range.level_count != 1 ||
|
||||
source_range.base_level >= source.backing.mip_levels ||
|
||||
@@ -393,8 +398,8 @@ void Image::Resolve(Image& source, const ImageSubresourceRange& source_range,
|
||||
resolved_destination_range.layer_count = layers;
|
||||
const vk::Extent3D resolve_extent {info.extent.width, info.extent.height, 1};
|
||||
|
||||
m_scheduler->EndRendering();
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
|
||||
resolved_source_range, command);
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
|
||||
@@ -430,9 +435,8 @@ uint32_t Image::CopyRows(uint64_t row_size, uint32_t rows, uint64_t capacity) no
|
||||
}
|
||||
|
||||
void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
|
||||
EXIT_IF(m_scheduler == nullptr || buffer.Handle() == nullptr || source.backing.samples != 1 ||
|
||||
backing.samples != 1);
|
||||
m_scheduler->EndRendering();
|
||||
EXIT_IF(buffer.Handle() == nullptr || source.backing.samples != 1 || backing.samples != 1);
|
||||
m_scheduler.EndRendering();
|
||||
const uint32_t levels = std::min(source.backing.mip_levels, backing.mip_levels);
|
||||
const auto source_aspect =
|
||||
FullAspectMask(source.backing.format) & ~vk::ImageAspectFlagBits::eStencil;
|
||||
@@ -462,7 +466,7 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
|
||||
dependency.dependencyFlags = vk::DependencyFlagBits::eByRegion;
|
||||
dependency.bufferMemoryBarrierCount = 1;
|
||||
dependency.pBufferMemoryBarriers = &barrier;
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
@@ -522,9 +526,9 @@ void Image::CopyImageWithBuffer(Image& source, Buffer& buffer) {
|
||||
}
|
||||
|
||||
void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
|
||||
EXIT_IF(m_scheduler == nullptr || source.backing.samples != backing.samples ||
|
||||
mip >= backing.mip_levels || layer >= backing.layers);
|
||||
m_scheduler->EndRendering();
|
||||
EXIT_IF(source.backing.samples != backing.samples || mip >= backing.mip_levels ||
|
||||
layer >= backing.layers);
|
||||
m_scheduler.EndRendering();
|
||||
const auto width = std::max(backing.extent.width >> mip, 1u);
|
||||
const auto height = std::max(backing.extent.height >> mip, 1u);
|
||||
const auto depth = std::max(backing.extent.depth >> mip, 1u);
|
||||
@@ -544,7 +548,7 @@ void Image::CopyMip(Image& source, uint32_t mip, uint32_t layer) {
|
||||
copy.dstSubresource = {aspect, mip, layer, destination_layers};
|
||||
copy.extent = {width, height, depth};
|
||||
}
|
||||
auto command = m_scheduler->Current().Handle();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, command);
|
||||
source.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {},
|
||||
command);
|
||||
@@ -648,7 +652,7 @@ Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) {
|
||||
} // namespace ImageOps
|
||||
|
||||
Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& image_info)
|
||||
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
|
||||
: info(image_info), m_graphics(graphics), m_scheduler(scheduler) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
ImageOps::Validate(info);
|
||||
m_cpu_dirty =
|
||||
@@ -657,29 +661,17 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
|
||||
return;
|
||||
}
|
||||
|
||||
backing.format = info.pixel_format;
|
||||
backing.image_type = HostImageType(info.type);
|
||||
backing.extent = info.extent;
|
||||
backing.guest_pitch = info.pitch;
|
||||
backing.layers = info.IsVolume() ? 1u : info.resources.layers;
|
||||
backing.mip_levels = info.resources.levels;
|
||||
backing.samples = info.samples;
|
||||
backing.flags = ImageCreateFlags(info);
|
||||
backing.usage = ImageUsageFlags(graphics, info);
|
||||
|
||||
vk::ImageCreateInfo create {};
|
||||
create.sType = vk::StructureType::eImageCreateInfo;
|
||||
create.flags = backing.flags;
|
||||
create.imageType = backing.image_type;
|
||||
create.extent = backing.extent;
|
||||
create.mipLevels = backing.mip_levels;
|
||||
create.arrayLayers = backing.layers;
|
||||
create.format = backing.format;
|
||||
create.flags = ImageCreateFlags(graphics, info);
|
||||
create.imageType = HostImageType(info.type);
|
||||
create.extent = info.extent;
|
||||
create.mipLevels = info.resources.levels;
|
||||
create.arrayLayers = info.IsVolume() ? 1u : info.resources.layers;
|
||||
create.format = info.pixel_format;
|
||||
create.tiling = vk::ImageTiling::eOptimal;
|
||||
create.initialLayout = backing.state.layout;
|
||||
create.usage = backing.usage;
|
||||
create.sharingMode = vk::SharingMode::eExclusive;
|
||||
create.samples = vulkan_sample_count(backing.samples);
|
||||
create.initialLayout = vk::ImageLayout::eUndefined;
|
||||
create.usage = ImageUsageFlags(graphics, info);
|
||||
create.samples = vulkan_sample_count(info.samples);
|
||||
|
||||
vk::ImageFormatProperties properties {};
|
||||
if (graphics.GetImageFormatProperties(create.format, create.imageType, create.tiling,
|
||||
@@ -690,10 +682,9 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
|
||||
"flags=0x%x samples=%u\n",
|
||||
static_cast<int>(create.format), static_cast<int>(create.imageType),
|
||||
static_cast<vk::ImageUsageFlags::MaskType>(create.usage),
|
||||
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), backing.samples);
|
||||
static_cast<vk::ImageCreateFlags::MaskType>(create.flags), info.samples);
|
||||
}
|
||||
|
||||
backing.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
if (!graphics.CreateImage(create, backing)) {
|
||||
EXIT("failed to create image: extent=%ux%ux%u format=%d layers=%u levels=%u\n",
|
||||
create.extent.width, create.extent.height, create.extent.depth,
|
||||
@@ -721,16 +712,13 @@ uint64_t Image::HashGuestEdges() const {
|
||||
|
||||
Image::~Image() {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
if (m_graphics == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& cached: views) {
|
||||
if (cached.view != nullptr) {
|
||||
m_graphics->device.destroyImageView(cached.view, nullptr);
|
||||
m_graphics.device.destroyImageView(cached.view, nullptr);
|
||||
}
|
||||
}
|
||||
if (backing.image != nullptr) {
|
||||
m_graphics->DeleteImage(backing);
|
||||
m_graphics.DeleteImage(backing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,13 @@ struct ImageUsage {
|
||||
};
|
||||
|
||||
struct ImageBinding {
|
||||
bool is_bound = false;
|
||||
bool is_target = false;
|
||||
bool needs_rebind = false;
|
||||
bool force_general = false;
|
||||
bool shader_write = false;
|
||||
vk::ImageLayout attachment_layout = vk::ImageLayout::eUndefined;
|
||||
vk::AccessFlags2 attachment_access;
|
||||
bool is_bound = false;
|
||||
bool is_target = false;
|
||||
bool needs_rebind = false;
|
||||
bool force_general = false;
|
||||
bool shader_write = false;
|
||||
};
|
||||
|
||||
class Image final {
|
||||
@@ -49,7 +51,6 @@ public:
|
||||
KYTY_CLASS_NO_COPY(Image);
|
||||
|
||||
[[nodiscard]] vk::ImageView FindView(const ImageViewInfo& view_info);
|
||||
void AssociateDepth(ImageId image_id) { depth_id = image_id; }
|
||||
using Barriers = std::vector<vk::ImageMemoryBarrier2>;
|
||||
[[nodiscard]] Barriers GetBarriers(vk::ImageLayout destination_layout,
|
||||
vk::AccessFlags2 destination_access,
|
||||
@@ -127,9 +128,6 @@ public:
|
||||
return pages ? ImagePageRangesOverlap(info.data.address, info.data.size, address, size)
|
||||
: ImageRangeOverlaps(info.data.address, info.data.size, address, size);
|
||||
}
|
||||
[[nodiscard]] bool GpuOverlaps(uint64_t address, uint64_t size) const noexcept {
|
||||
return IsGpuModified() && Overlaps(address, size);
|
||||
}
|
||||
[[nodiscard]] bool SafeToDownload() const noexcept {
|
||||
return IsGpuModified() && !IsBufferModified() && !IsCpuDirty();
|
||||
}
|
||||
@@ -161,8 +159,8 @@ private:
|
||||
[[nodiscard]] static std::pair<uint32_t, uint32_t>
|
||||
SanitizeCopyLayers(const Image& source, const Image& destination, uint32_t depth);
|
||||
|
||||
GraphicContext* m_graphics = nullptr;
|
||||
CommandScheduler* m_scheduler = nullptr;
|
||||
GraphicContext& m_graphics;
|
||||
CommandScheduler& m_scheduler;
|
||||
uint64_t m_maybe_cpu_hash = 0;
|
||||
bool m_cpu_dirty = false;
|
||||
bool m_maybe_cpu_dirty = false;
|
||||
|
||||
@@ -23,8 +23,11 @@ struct ImageMetadataInfo {
|
||||
GuestRange range;
|
||||
ImageMetadataKind kind = ImageMetadataKind::None;
|
||||
uint32_t control = 0;
|
||||
uint32_t dcc_clear_word = 0;
|
||||
VideoOutCompression compression = VideoOutCompression::Uncompressed;
|
||||
bool stencil_compressed = false;
|
||||
bool dcc_clear_register_valid = false;
|
||||
bool dcc_alpha_msb = true;
|
||||
};
|
||||
|
||||
struct ImageSubresources {
|
||||
@@ -44,6 +47,7 @@ struct ImageSubresourceRange {
|
||||
struct ImageMipInfo {
|
||||
uint64_t offset = 0;
|
||||
uint64_t size = 0;
|
||||
// Padded dimensions in storage elements (compressed blocks for BC formats).
|
||||
uint32_t pitch = 0;
|
||||
uint32_t height = 0;
|
||||
auto operator<=>(const ImageMipInfo&) const = default;
|
||||
|
||||
@@ -21,10 +21,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCompatibleViewFormat(vk::Format image_format, vk::Format view_format) {
|
||||
return ImageViewOps::FormatsCompatible(image_format, view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsStencilViewFormat(vk::Format format) {
|
||||
switch (format) {
|
||||
case vk::Format::eS8Uint:
|
||||
@@ -327,7 +323,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
}
|
||||
normalized.usage = is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
|
||||
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
|
||||
IsCompatibleViewFormat(image.format, normalized.format);
|
||||
ImageViewOps::FormatsCompatible(image.format, normalized.format);
|
||||
const bool slice_view =
|
||||
image.image_type == vk::ImageType::e3D && (normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
@@ -362,13 +358,11 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
}
|
||||
|
||||
vk::ImageViewUsageCreateInfo usage {};
|
||||
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
|
||||
usage.usage = image.usage;
|
||||
if (!is_storage) {
|
||||
usage.usage &= ~vk::ImageUsageFlagBits::eStorage;
|
||||
}
|
||||
vk::ImageViewCreateInfo create {};
|
||||
create.sType = vk::StructureType::eImageViewCreateInfo;
|
||||
create.pNext = &usage;
|
||||
create.image = image.image;
|
||||
create.viewType = normalized.type;
|
||||
@@ -381,7 +375,7 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
|
||||
create.subresourceRange.layerCount = normalized.layer_count;
|
||||
|
||||
vk::ImageView view = nullptr;
|
||||
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
|
||||
const auto result = m_graphics.device.createImageView(&create, nullptr, &view);
|
||||
if (result != vk::Result::eSuccess || view == nullptr) {
|
||||
EXIT("failed to create image view: result=%d image_format=%d view_format=%d type=%d "
|
||||
"aspect=0x%x mip=%u+%u layer=%u+%u usage=0x%x\n",
|
||||
|
||||
@@ -92,29 +92,18 @@ SelectSampledColorView(vk::Format image_format, vk::Format view_format, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t
|
||||
SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
if (IsSupportedSampledDepthView(image_format, view_format, swizzle)) {
|
||||
return swizzle;
|
||||
}
|
||||
EXIT("unsupported sampled depth image view: image_format=%d view_format=%d swizzle=0x%03x\n",
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image &&
|
||||
resource.kind != ShaderRecompiler::IR::ResourceKind::ImageUint) {
|
||||
if (resource.resource_class != ShaderRecompiler::IR::ImageResourceClass::Sampled) {
|
||||
return false;
|
||||
}
|
||||
if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint && resource.depth_compare) {
|
||||
if (resource.numeric_class == Prospero::TextureNumericClass::Unsupported) {
|
||||
return false;
|
||||
}
|
||||
return (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaa ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DMsaaArray) &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
if (resource.numeric_class != Prospero::TextureNumericClass::Float && resource.depth_compare) {
|
||||
return false;
|
||||
}
|
||||
return resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic;
|
||||
}
|
||||
|
||||
@@ -128,8 +117,9 @@ inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_fo
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
|
||||
return resource.resource_class == ShaderRecompiler::IR::ImageResourceClass::Storage &&
|
||||
(resource.numeric_class == Prospero::TextureNumericClass::Float ||
|
||||
resource.numeric_class == Prospero::TextureNumericClass::Uint) &&
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
@@ -141,19 +131,19 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
|
||||
resource.mip_count != 0u)) &&
|
||||
resource.written &&
|
||||
(!resource.atomic ||
|
||||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
|
||||
resource.read)) &&
|
||||
(resource.numeric_class == Prospero::TextureNumericClass::Uint && resource.read)) &&
|
||||
!resource.depth_compare;
|
||||
}
|
||||
|
||||
inline void
|
||||
ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
if (!IsSupportedStorageImageResource(resource)) {
|
||||
EXIT("unsupported storage color image resource: kind=%u dimension=%u mip=%u "
|
||||
EXIT("unsupported storage color image resource: class=%u numeric=%u dimension=%u mip=%u "
|
||||
"read=%d written=%d atomic=%d depth_compare=%d\n",
|
||||
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
|
||||
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
|
||||
resource.atomic, resource.depth_compare);
|
||||
static_cast<uint32_t>(resource.resource_class),
|
||||
static_cast<uint32_t>(resource.numeric_class),
|
||||
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
|
||||
resource.read, resource.written, resource.atomic, resource.depth_compare);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/host_gpu/renderer/image/tiler.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -104,18 +105,14 @@ vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
|
||||
return vk::ComponentSwizzle::eIdentity;
|
||||
}
|
||||
|
||||
static uint32_t TextureGetDstSel(uint32_t swizzle, uint32_t channel) {
|
||||
return (swizzle >> (channel * 3u)) & 0x7u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
|
||||
vk::ComponentMapping components {};
|
||||
components.r = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 0)));
|
||||
components.g = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 1)));
|
||||
components.b = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 2)));
|
||||
components.a = TextureGetComponentSwizzle(static_cast<uint8_t>(TextureGetDstSel(swizzle, 3)));
|
||||
components.r = TextureGetComponentSwizzle(GetDstSel(swizzle, 0));
|
||||
components.g = TextureGetComponentSwizzle(GetDstSel(swizzle, 1));
|
||||
components.b = TextureGetComponentSwizzle(GetDstSel(swizzle, 2));
|
||||
components.a = TextureGetComponentSwizzle(GetDstSel(swizzle, 3));
|
||||
return components;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
nullptr};
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
|
||||
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
descriptor_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
|
||||
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
descriptor_info.pBindings = bindings.data();
|
||||
@@ -50,7 +49,6 @@ TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||
|
||||
const vk::PushConstantRange push_range {vk::ShaderStageFlagBits::eCompute, 0, sizeof(Push)};
|
||||
vk::PipelineLayoutCreateInfo layout_info {};
|
||||
layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
layout_info.setLayoutCount = 1;
|
||||
layout_info.pSetLayouts = &m_descriptor_layout;
|
||||
layout_info.pushConstantRangeCount = 1;
|
||||
@@ -92,11 +90,9 @@ TileManager::~TileManager() {
|
||||
TileManager::Scratch TileManager::AllocateScratch(uint64_t size) {
|
||||
EXIT_IF(size == 0);
|
||||
vk::BufferCreateInfo create {};
|
||||
create.sType = vk::StructureType::eBufferCreateInfo;
|
||||
create.size = size;
|
||||
create.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
|
||||
vk::BufferUsageFlagBits::eTransferDst;
|
||||
create.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
VmaAllocationCreateInfo allocate {};
|
||||
allocate.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
@@ -264,20 +260,17 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
|
||||
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = shaders[family_index].words * sizeof(uint32_t);
|
||||
module_info.pCode = shaders[family_index].code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create TileManager shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
@@ -287,7 +280,7 @@ vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
|
||||
return m_pipelines[slot];
|
||||
}
|
||||
|
||||
void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
|
||||
void TileManager::Record(vk::Buffer source, uint64_t source_offset,
|
||||
uint64_t source_capacity, vk::Buffer target, uint64_t target_offset,
|
||||
uint64_t target_capacity, std::span<Dispatch> dispatches,
|
||||
bool clear_target) {
|
||||
@@ -307,7 +300,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier barriers[3] {};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
@@ -316,7 +308,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
|
||||
barriers[0].offset = source_offset;
|
||||
barriers[0].size = source_capacity;
|
||||
barriers[1] = barriers[0];
|
||||
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite;
|
||||
barriers[1].dstAccessMask =
|
||||
clear_target ? vk::AccessFlagBits::eTransferWrite
|
||||
: vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
@@ -352,7 +343,6 @@ void TileManager::Record(bool tile, vk::Buffer source, uint64_t source_offset,
|
||||
const vk::DescriptorBufferInfo infos[] {source_info, target_info, params_info};
|
||||
std::array<vk::WriteDescriptorSet, 3> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = index == 2 ? vk::DescriptorType::eUniformBuffer
|
||||
@@ -384,7 +374,7 @@ TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
|
||||
Prepare(false, tiled_capacity, linear_capacity, infos, source_base, 0, dispatches);
|
||||
auto scratch = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
|
||||
DeferDestroy(scratch);
|
||||
Record(false, tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
|
||||
Record(tiled, tiled_offset, tiled_capacity, scratch.buffer, 0, scratch.size, dispatches,
|
||||
true);
|
||||
return {scratch.buffer, 0, linear_capacity};
|
||||
}
|
||||
@@ -399,7 +389,7 @@ void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linea
|
||||
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
|
||||
std::vector<Dispatch> dispatches;
|
||||
Prepare(true, tiled_capacity, linear_capacity, infos, source_base, target_base, dispatches);
|
||||
Record(true, linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
Record(linear, linear_offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
}
|
||||
|
||||
@@ -423,7 +413,7 @@ void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> r
|
||||
if (transform == ColorTransform::SwapBgra16) {
|
||||
source = SwapBgra16(source);
|
||||
}
|
||||
Record(true, source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
Record(source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
|
||||
dispatches, false);
|
||||
}
|
||||
|
||||
@@ -482,20 +472,17 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
|
||||
}
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = words * sizeof(uint32_t);
|
||||
module_info.pCode = code;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create D16 conversion shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
stage.pSpecializationInfo = &specialization;
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
@@ -532,7 +519,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
m_scheduler.EndRendering();
|
||||
auto command = m_scheduler.Current().Handle();
|
||||
vk::BufferMemoryBarrier barriers[2] {};
|
||||
barriers[0].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[0].buffer = source.buffer;
|
||||
@@ -542,7 +528,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barriers[1].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[1].buffer = target.buffer;
|
||||
@@ -596,7 +581,6 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
@@ -627,19 +611,16 @@ void TileManager::ConvertD16(Result source, Result target, D16Direction directio
|
||||
void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
|
||||
if (m_swap_bgra16 == nullptr) {
|
||||
vk::ShaderModuleCreateInfo module_info {};
|
||||
module_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
module_info.codeSize = std::size(GPU_TILER_SWAP_BGRA16_SPV) * sizeof(uint32_t);
|
||||
module_info.pCode = GPU_TILER_SWAP_BGRA16_SPV;
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(m_graphics.device.createShaderModule(&module_info, nullptr, &module),
|
||||
"create BGRA16 swap shader module");
|
||||
vk::PipelineShaderStageCreateInfo stage {};
|
||||
stage.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
stage.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
stage.module = module;
|
||||
stage.pName = "main";
|
||||
vk::ComputePipelineCreateInfo create {};
|
||||
create.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
create.stage = stage;
|
||||
create.layout = m_pipeline_layout;
|
||||
const auto result =
|
||||
@@ -658,7 +639,6 @@ void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
|
||||
};
|
||||
std::array<vk::WriteDescriptorSet, 2> writes {};
|
||||
for (uint32_t index = 0; index < writes.size(); index++) {
|
||||
writes[index].sType = vk::StructureType::eWriteDescriptorSet;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = vk::DescriptorType::eStorageBuffer;
|
||||
@@ -666,7 +646,6 @@ void TileManager::SwapBgra16(Result input, Result output, uint32_t pixels) {
|
||||
}
|
||||
vk::BufferMemoryBarrier barriers[2] {};
|
||||
for (uint32_t index = 0; index < 2; index++) {
|
||||
barriers[index].sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barriers[index].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[index].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barriers[index].buffer = infos[index].buffer;
|
||||
|
||||
@@ -125,7 +125,7 @@ private:
|
||||
void Prepare(bool tile, uint64_t tiled_capacity, uint64_t linear_capacity,
|
||||
std::span<const GpuTileInfo> infos, uint64_t source_base, uint64_t target_base,
|
||||
std::vector<Dispatch>& dispatches);
|
||||
void Record(bool tile, vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
void Record(vk::Buffer source, uint64_t source_offset, uint64_t source_capacity,
|
||||
vk::Buffer target, uint64_t target_offset, uint64_t target_capacity,
|
||||
std::span<Dispatch> dispatches, bool clear_target);
|
||||
[[nodiscard]] vk::Pipeline GetPipeline(uint32_t slot);
|
||||
|
||||
@@ -7,12 +7,10 @@ namespace Libs::Graphics {
|
||||
|
||||
MasterSemaphore::MasterSemaphore(GraphicContext& graphics): m_graphics(graphics) {
|
||||
vk::SemaphoreTypeCreateInfo type_info {};
|
||||
type_info.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
|
||||
type_info.semaphoreType = vk::SemaphoreType::eTimeline;
|
||||
type_info.initialValue = 0;
|
||||
|
||||
vk::SemaphoreCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eSemaphoreCreateInfo;
|
||||
create_info.pNext = &type_info;
|
||||
|
||||
const auto result = m_graphics.device.createSemaphore(&create_info, nullptr, &m_semaphore);
|
||||
@@ -47,7 +45,6 @@ void MasterSemaphore::Wait(uint64_t tick) {
|
||||
}
|
||||
|
||||
vk::SemaphoreWaitInfo wait_info {};
|
||||
wait_info.sType = vk::StructureType::eSemaphoreWaitInfo;
|
||||
wait_info.semaphoreCount = 1;
|
||||
wait_info.pSemaphores = &m_semaphore;
|
||||
wait_info.pValues = &tick;
|
||||
|
||||
@@ -63,7 +63,6 @@ bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
|
||||
layouts.fill(layout);
|
||||
|
||||
vk::DescriptorSetAllocateInfo allocate {};
|
||||
allocate.sType = vk::StructureType::eDescriptorSetAllocateInfo;
|
||||
allocate.descriptorPool = m_current_pool;
|
||||
allocate.pSetLayouts = layouts.data();
|
||||
|
||||
@@ -85,8 +84,6 @@ bool DescriptorHeap::Allocate(vk::DescriptorSetLayout layout, Batch& batch) {
|
||||
|
||||
void DescriptorHeap::CreatePool() {
|
||||
vk::DescriptorPoolCreateInfo create {};
|
||||
create.sType = vk::StructureType::eDescriptorPoolCreateInfo;
|
||||
create.flags = {};
|
||||
create.maxSets = DescriptorHeapCount;
|
||||
create.poolSizeCount = static_cast<uint32_t>(DescriptorPoolSizes.size());
|
||||
create.pPoolSizes = DescriptorPoolSizes.data();
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -16,15 +16,7 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
struct ResourceSnapshot;
|
||||
}
|
||||
|
||||
struct BufferView {
|
||||
vk::Buffer buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
};
|
||||
struct ShaderStageRuntime;
|
||||
|
||||
struct TextureBinding {
|
||||
ImageId image_id;
|
||||
@@ -34,23 +26,25 @@ struct TextureBinding {
|
||||
std::vector<vk::ImageView> mip_views;
|
||||
};
|
||||
|
||||
struct NativeDescriptors {
|
||||
std::vector<BufferView> buffers;
|
||||
std::vector<TextureBinding> images;
|
||||
std::vector<vk::Sampler> samplers;
|
||||
BufferView gds;
|
||||
BufferView flattened_srt;
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
const ShaderRecompiler::IR::Program* program = nullptr;
|
||||
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
|
||||
NativeDescriptors resources;
|
||||
std::vector<BufferId> buffer_ids;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
bool committed = false;
|
||||
struct BufferSource {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
BufferId id;
|
||||
};
|
||||
|
||||
// The draw owns the immutable compiled-program/runtime-snapshot association through commit.
|
||||
const ShaderStageRuntime* runtime = nullptr;
|
||||
// Keep the resolved guest range through cache preparation; only the host buffer ID may
|
||||
// become stale and need resolving again when bindings are rebound.
|
||||
std::vector<BufferSource> buffer_sources;
|
||||
std::vector<vk::DescriptorBufferInfo> buffers;
|
||||
std::vector<TextureBinding> images;
|
||||
std::vector<vk::Sampler> samplers;
|
||||
vk::DescriptorBufferInfo gds {nullptr, 0, VK_WHOLE_SIZE};
|
||||
vk::DescriptorBufferInfo flattened_srt;
|
||||
vk::DescriptorBufferInfo shader_data_buffer;
|
||||
std::vector<uint32_t> shader_data;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::DescriptorType
|
||||
@@ -70,20 +64,8 @@ template <typename T>
|
||||
return result;
|
||||
}
|
||||
|
||||
struct TargetTextureViewInfo {
|
||||
vk::ImageViewType type = static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM);
|
||||
uint32_t base_layer = 0;
|
||||
uint32_t layer_count = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] TargetTextureViewInfo
|
||||
ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
|
||||
|
||||
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const Image& image, bool r128 = false);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
|
||||
const Image& image, bool r128 = false);
|
||||
bool r128 = false);
|
||||
[[nodiscard]] bool
|
||||
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, const Image& image);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -11,28 +12,54 @@
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/shaderCompiler.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kytyGitVersion.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <spirv-tools/libspirv.hpp>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
vk::PolygonMode ResolvePolygonMode(const HW::ModeControl& mode, bool cull_front, bool cull_back) {
|
||||
// CxPrimitiveSetup::PolygonMode disables both per-face modes when it is zero.
|
||||
if (mode.poly_mode == 0) {
|
||||
return vk::PolygonMode::eFill;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(mode.poly_mode != 1);
|
||||
if (cull_front && cull_back) {
|
||||
return vk::PolygonMode::eFill;
|
||||
}
|
||||
if (!cull_front && !cull_back && mode.polymode_front_ptype != mode.polymode_back_ptype) {
|
||||
EXIT("Pipeline: different polygon modes for two visible faces are unsupported\n");
|
||||
}
|
||||
// Vulkan has one polygon mode. A culled face does not constrain that mode.
|
||||
const auto polygon_mode = cull_front ? mode.polymode_back_ptype : mode.polymode_front_ptype;
|
||||
switch (polygon_mode) {
|
||||
case 0: return vk::PolygonMode::ePoint;
|
||||
case 1: return vk::PolygonMode::eLine;
|
||||
case 2: return vk::PolygonMode::eFill;
|
||||
default: EXIT("Pipeline: invalid polygon mode %u\n", polygon_mode);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DriverCacheSignature(const vk::PhysicalDeviceProperties& properties) {
|
||||
constexpr char hex[] = "0123456789abcdef";
|
||||
std::string uuid(VK_UUID_SIZE * 2, '0');
|
||||
@@ -70,97 +97,304 @@ void PipelineCacheLog(fmt::format_string<Args...> format, Args&&... args) {
|
||||
Log::Flush();
|
||||
}
|
||||
|
||||
void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_params) {
|
||||
static_params.viewport_scale[0] = 0.5f;
|
||||
static_params.viewport_scale[1] = 0.5f;
|
||||
static_params.viewport_scale[2] = 1.0f;
|
||||
static_params.viewport_offset[0] = 0.5f;
|
||||
static_params.viewport_offset[1] = 0.5f;
|
||||
static_params.viewport_offset[2] = 0.0f;
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
|
||||
return value != nullptr &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
|
||||
}
|
||||
|
||||
static_params.scissor_ltrb[0] = 0;
|
||||
static_params.scissor_ltrb[1] = 0;
|
||||
static_params.scissor_ltrb[2] = 1;
|
||||
static_params.scissor_ltrb[3] = 1;
|
||||
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
static std::atomic_int id = 0;
|
||||
const auto path = Config::GetShaderLogFolder() / fmt::format("{:04d}_new_shader_{}_{:016x}.spv",
|
||||
id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(path.parent_path());
|
||||
Common::File file(path);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
return;
|
||||
}
|
||||
file.Write(spirv.data(), spirv.size() * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
|
||||
std::span<const uint32_t> code, const std::string& decoded_dump) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(code.empty());
|
||||
static std::atomic_int id = 0;
|
||||
const auto base = Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(base.parent_path());
|
||||
for (const auto& [suffix, data, size]: {
|
||||
std::tuple {".bin", static_cast<const void*>(code.data()), code.size_bytes()},
|
||||
std::tuple {".rdna2", static_cast<const void*>(decoded_dump.data()),
|
||||
decoded_dump.size()},
|
||||
}) {
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
auto path = base;
|
||||
path += suffix;
|
||||
Common::File file(path);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
} else {
|
||||
file.Write(data, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
if (!Config::ShaderValidationEnabled()) {
|
||||
return true;
|
||||
}
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_3);
|
||||
std::string messages;
|
||||
tools.SetMessageConsumer([&messages](spv_message_level_t, const char*,
|
||||
const spv_position_t& position, const char* message) {
|
||||
messages += fmt::format("{}: {} ({}) {}\n", static_cast<int>(position.line),
|
||||
static_cast<int>(position.column), static_cast<int>(position.index),
|
||||
message);
|
||||
});
|
||||
if (tools.Validate(spirv)) {
|
||||
return true;
|
||||
}
|
||||
spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2);
|
||||
std::string text;
|
||||
disassembler.Disassemble(spirv, &text,
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COMMENT) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR));
|
||||
LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s",
|
||||
label, shader_hash, messages.c_str());
|
||||
LOGF("%s\n", text.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct PipelineCache::ProgramCache {
|
||||
struct SourceKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
struct ProgramKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
uint32_t user_data_count = 0;
|
||||
uint32_t code_size = 0;
|
||||
std::vector<uint32_t> static_state;
|
||||
|
||||
bool operator==(const SourceKey&) const = default;
|
||||
bool operator==(const ProgramKey&) const = default;
|
||||
};
|
||||
|
||||
struct Permutation {
|
||||
std::vector<uint32_t> static_key;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
ShaderProgram handle;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
ShaderRecompiler::IR::CompiledShaderInfo program;
|
||||
ShaderProgram handle;
|
||||
};
|
||||
|
||||
struct SourceKeyHash {
|
||||
std::size_t operator()(const SourceKey& key) const {
|
||||
struct SourceEntry {
|
||||
explicit SourceEntry(ShaderRecompiler::IR::ResourcePlan plan)
|
||||
: resource_plan(std::move(plan)) {
|
||||
permutations.reserve(8);
|
||||
}
|
||||
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
std::vector<Permutation> permutations;
|
||||
};
|
||||
|
||||
struct ProgramKeyHash {
|
||||
std::size_t operator()(const ProgramKey& key) const {
|
||||
std::size_t hash = static_cast<std::size_t>(key.stage);
|
||||
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
|
||||
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
|
||||
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.user_data_count);
|
||||
PipelineKeyHash::Mix(hash, key.code_size);
|
||||
PipelineKeyHash::Mix(hash, key.static_state.size());
|
||||
// Bucket same-shape static variants by source. ProgramKey equality performs the one
|
||||
// exact state comparison needed on a stable hit without hashing up to 429 words first.
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
static uint64_t MixId(uint64_t hash, uint64_t value) {
|
||||
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
|
||||
static constexpr std::size_t MaxStaticKeyWords = 13 + ShaderVertexInputInfo::RES_MAX * 13;
|
||||
|
||||
Permutation CompilePermutation(const ShaderParams& params,
|
||||
const ShaderRecompiler::CompileOptions& options,
|
||||
ShaderRecompiler::TranslateResult translated,
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization,
|
||||
uint32_t push_data_start_dword) {
|
||||
const char* stage_name = nullptr;
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex: stage_name = "vs"; break;
|
||||
case ShaderType::Mesh: stage_name = "ms"; break;
|
||||
case ShaderType::Pixel: stage_name = "ps"; break;
|
||||
case ShaderType::Compute: stage_name = "cs"; break;
|
||||
default: EXIT("invalid pipeline shader stage\n");
|
||||
}
|
||||
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
|
||||
specialization, push_data_start_dword);
|
||||
DumpShaderOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
|
||||
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
|
||||
options.shader_hash);
|
||||
}
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
|
||||
create_info.pCode = result.spirv.data();
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
|
||||
"create recompiled shader module");
|
||||
EXIT_IF(module == nullptr);
|
||||
if (options.dump_ir) {
|
||||
if (!options.early_dump) {
|
||||
LOGF("%s decoded RDNA2:\n%s", options.dump_label, result.decoded_dump.c_str());
|
||||
LOGF("%s IR:\n%s", options.dump_label, result.ir_dump.c_str());
|
||||
}
|
||||
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
|
||||
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
|
||||
}
|
||||
return {
|
||||
.specialization = std::move(specialization),
|
||||
.program = std::move(result.program).TakeCompiledInfo(),
|
||||
.handle = {.id = ++next_shader_id, .module = module},
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr std::size_t MaxStaticKeyWords =
|
||||
12 + ShaderVertexInputInfo::RES_MAX * 17;
|
||||
|
||||
template <typename InputInfo>
|
||||
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
|
||||
constexpr ShaderType stage = [] {
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
return ShaderType::Vertex;
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
|
||||
return ShaderType::Pixel;
|
||||
} else {
|
||||
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
|
||||
return ShaderType::Compute;
|
||||
}
|
||||
}();
|
||||
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info,
|
||||
uint32_t& push_data_cursor) {
|
||||
ShaderType stage;
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
stage = input_info.mesh.threads_num[0] != 0 ? ShaderType::Mesh : ShaderType::Vertex;
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
|
||||
stage = ShaderType::Pixel;
|
||||
} else {
|
||||
static_assert(std::is_same_v<InputInfo, ShaderComputeInputInfo>);
|
||||
stage = ShaderType::Compute;
|
||||
}
|
||||
|
||||
BuildStageStaticKey(input_info, key_scratch);
|
||||
auto& permutations = programs[{stage, params.hash}];
|
||||
for (const auto& permutation: permutations) {
|
||||
if (permutation.static_key == key_scratch &&
|
||||
MaterializeProgram(permutation.program, params, input_info)) {
|
||||
return permutation.handle;
|
||||
lookup_key.stage = stage;
|
||||
lookup_key.hash = params.hash;
|
||||
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
|
||||
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
|
||||
BuildStageStaticKey(input_info, lookup_key.static_state);
|
||||
auto entry = programs.find(lookup_key);
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime {
|
||||
.user_data = params.user_data,
|
||||
.shader_base = params.Base(),
|
||||
.read_specialization_memory = ReadShaderGuestMemory,
|
||||
};
|
||||
if (entry != programs.end()) {
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, resources, specialization));
|
||||
if (const auto permutation = std::ranges::find_if(
|
||||
entry->second.permutations, [&](const Permutation& candidate) {
|
||||
const auto& layout = candidate.program.bindings;
|
||||
return layout.push_data_start_dword ==
|
||||
ShaderRecompiler::IR::PushData::StartFor(
|
||||
push_data_cursor, layout.ShaderDataDwords()) &&
|
||||
candidate.specialization == specialization;
|
||||
});
|
||||
permutation != entry->second.permutations.end()) {
|
||||
input_info.stage = {.program = &permutation->program,
|
||||
.resources = std::move(resources)};
|
||||
permutation->program.bindings.AdvancePushData(push_data_cursor);
|
||||
return permutation->handle;
|
||||
}
|
||||
}
|
||||
|
||||
const auto module = CompileProgram(device, params, input_info);
|
||||
EXIT_IF(module == nullptr || !input_info.stage);
|
||||
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
|
||||
if (id == 0) {
|
||||
id = 1;
|
||||
ShaderStageInputInfo stage_input {};
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
stage_input.vertex = &input_info;
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
|
||||
stage_input.pixel = &input_info;
|
||||
} else {
|
||||
stage_input.compute = &input_info;
|
||||
}
|
||||
const ShaderProgram handle {.id = id, .module = module};
|
||||
permutations.push_back({key_scratch, input_info.stage.program, handle});
|
||||
const char* label = nullptr;
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: label = "ShaderRecompiler VS"; break;
|
||||
case ShaderType::Mesh: label = "ShaderRecompiler MS"; break;
|
||||
case ShaderType::Pixel: label = "ShaderRecompiler PS"; break;
|
||||
case ShaderType::Compute: label = "ShaderRecompiler CS"; break;
|
||||
default: EXIT("invalid pipeline shader stage\n");
|
||||
}
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.shader_hash = params.hash;
|
||||
options.user_data = params.user_data;
|
||||
options.back_code = params.back_code;
|
||||
options.dump_ir = Config::GetShaderLogDirection() != Config::ShaderLogDirection::Silent;
|
||||
options.early_dump = options.dump_ir;
|
||||
options.dump_label = label;
|
||||
options.input_info = stage_input;
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
options.user_data_base = 8;
|
||||
if (stage == ShaderType::Mesh) {
|
||||
options.user_data_base = 0;
|
||||
options.wave_size = input_info.mesh.wave_size;
|
||||
options.scratch_dwords = input_info.mesh.scratch_size_dwords;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderComputeInputInfo>) {
|
||||
options.wave_size = input_info.wave_size;
|
||||
}
|
||||
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
if (entry == programs.end()) {
|
||||
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
|
||||
specialization));
|
||||
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
|
||||
}
|
||||
entry->second.permutations.push_back(CompilePermutation(
|
||||
params, options, std::move(translated), std::move(specialization), push_data_cursor));
|
||||
const auto& permutation = entry->second.permutations.back();
|
||||
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
|
||||
permutation.program.bindings.AdvancePushData(push_data_cursor);
|
||||
|
||||
std::printf("Num compiled %u shaders\n", ++num_compiled);
|
||||
return handle;
|
||||
std::array<size_t, static_cast<size_t>(ShaderType::Mesh) + 1> counts {};
|
||||
for (const auto& [key, source]: programs) {
|
||||
counts[static_cast<size_t>(key.stage)] += source.permutations.size();
|
||||
}
|
||||
// Guest geometry shaders are compiled through the host mesh stage.
|
||||
std::printf("Shaders: VS %zu | PS %zu | CS %zu | GS %zu\n",
|
||||
counts[static_cast<size_t>(ShaderType::Vertex)],
|
||||
counts[static_cast<size_t>(ShaderType::Pixel)],
|
||||
counts[static_cast<size_t>(ShaderType::Compute)],
|
||||
counts[static_cast<size_t>(ShaderType::Mesh)]);
|
||||
return permutation.handle;
|
||||
}
|
||||
|
||||
explicit ProgramCache(vk::Device device): device(device) {
|
||||
key_scratch.reserve(MaxStaticKeyWords);
|
||||
lookup_key.static_state.reserve(MaxStaticKeyWords);
|
||||
}
|
||||
~ProgramCache() {
|
||||
for (const auto& [key, entry]: programs) {
|
||||
(void)key;
|
||||
for (const auto& permutation: entry.permutations) {
|
||||
device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
|
||||
std::vector<uint32_t> key_scratch;
|
||||
vk::Device device;
|
||||
uint32_t num_compiled = 0;
|
||||
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
|
||||
ProgramKey lookup_key;
|
||||
vk::Device device;
|
||||
uint64_t next_shader_id = 0;
|
||||
};
|
||||
|
||||
PipelineCache::PipelineCache(GraphicContext& graphics)
|
||||
@@ -181,12 +415,6 @@ PipelineCache::~PipelineCache() {
|
||||
};
|
||||
destroy(m_graphics_pipelines);
|
||||
destroy(m_compute_pipelines);
|
||||
for (const auto& [key, permutations]: m_program_cache->programs) {
|
||||
(void)key;
|
||||
for (const auto& permutation: permutations) {
|
||||
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_driver_cache != nullptr) {
|
||||
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
|
||||
}
|
||||
@@ -212,7 +440,7 @@ void PipelineCache::InitializeDriverCache() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
|
||||
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
|
||||
const auto path = Common::PathToString(m_driver_cache_path);
|
||||
const bool cache_exists = Common::File::IsFileExisting(m_driver_cache_path);
|
||||
if (cache_exists) {
|
||||
@@ -224,7 +452,7 @@ void PipelineCache::InitializeDriverCache() {
|
||||
if (cache_exists) {
|
||||
Common::File file(m_driver_cache_path, Common::File::Mode::Read);
|
||||
const auto file_size = file.IsInvalid() ? 0 : file.Size();
|
||||
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
if (file_size >= signature.size() + sizeof(uint64_t) &&
|
||||
file_size <= std::numeric_limits<uint32_t>::max()) {
|
||||
std::string cached_signature(signature.size(), '\0');
|
||||
@@ -236,7 +464,8 @@ void PipelineCache::InitializeDriverCache() {
|
||||
file.Read(cached_signature.data(), static_cast<uint32_t>(cached_signature.size()),
|
||||
&signature_read);
|
||||
file.Read(&payload_hash, sizeof(payload_hash), &hash_read);
|
||||
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()), &payload_read);
|
||||
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()),
|
||||
&payload_read);
|
||||
file.Close();
|
||||
if (signature_read != cached_signature.size() || hash_read != sizeof(payload_hash) ||
|
||||
payload_read != initial_data.size() || cached_signature != signature ||
|
||||
@@ -248,26 +477,24 @@ void PipelineCache::InitializeDriverCache() {
|
||||
}
|
||||
} else {
|
||||
file.Close();
|
||||
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)",
|
||||
path);
|
||||
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)", path);
|
||||
}
|
||||
}
|
||||
|
||||
vk::PipelineCacheCreateInfo create {};
|
||||
create.sType = vk::StructureType::ePipelineCacheCreateInfo;
|
||||
create.initialDataSize = initial_data.size();
|
||||
create.pInitialData = initial_data.empty() ? nullptr : initial_data.data();
|
||||
auto result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
|
||||
if (result != vk::Result::eSuccess && !initial_data.empty()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: driver rejected {} ({}); starting empty", path,
|
||||
VulkanToString(result));
|
||||
vk::to_string(result));
|
||||
initial_data.clear();
|
||||
create.initialDataSize = 0;
|
||||
create.pInitialData = nullptr;
|
||||
result = m_graphics.device.createPipelineCache(&create, nullptr, &m_driver_cache);
|
||||
}
|
||||
if (result != vk::Result::eSuccess) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: disabled ({})", VulkanToString(result));
|
||||
PipelineCacheLog("Vulkan pipeline cache: disabled ({})", vk::to_string(result));
|
||||
m_driver_cache = nullptr;
|
||||
return;
|
||||
}
|
||||
@@ -304,11 +531,11 @@ void PipelineCache::Save() {
|
||||
if (result != vk::Result::eSuccess || size == 0 ||
|
||||
size > std::numeric_limits<uint32_t>::max()) {
|
||||
PipelineCacheLog("Vulkan pipeline cache: save failed ({}, {} bytes)",
|
||||
VulkanToString(result), size);
|
||||
vk::to_string(result), size);
|
||||
return;
|
||||
}
|
||||
payload.resize(size);
|
||||
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
const auto payload_hash = XXH3_64bits(payload.data(), payload.size());
|
||||
prefix.append(reinterpret_cast<const char*>(&payload_hash), sizeof(payload_hash));
|
||||
if (!Common::File::CreateDirectories(m_driver_cache_path.parent_path())) {
|
||||
@@ -338,15 +565,39 @@ void PipelineCache::Save() {
|
||||
m_driver_cache = nullptr;
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context,
|
||||
ShaderVertexInputInfo& input_info) {
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
PipelineCache::GraphicsPrograms PipelineCache::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,
|
||||
ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
|
||||
const auto vertex_params = PrepareProgram(vertex_regs, context, user_config, vertex_info);
|
||||
const bool mesh_active = vertex_info.mesh.threads_num[0] != 0;
|
||||
if (mesh_active) {
|
||||
EXIT_NOT_IMPLEMENTED(!m_graphics.mesh_shader_enabled);
|
||||
auto& mesh = vertex_info.mesh;
|
||||
mesh.host_subgroup_size = m_graphics.subgroup_size;
|
||||
const auto& limits = m_graphics.mesh_shader_properties;
|
||||
const auto logical_threads =
|
||||
mesh.threads_num[0] * mesh.threads_num[1] * mesh.threads_num[2];
|
||||
const auto host_threads = ((logical_threads + mesh.wave_size - 1u) / mesh.wave_size) *
|
||||
std::min(mesh.host_subgroup_size, mesh.wave_size);
|
||||
if (host_threads > limits.maxMeshWorkGroupInvocations ||
|
||||
host_threads > limits.maxMeshWorkGroupSize[0] ||
|
||||
mesh.max_vertices > limits.maxMeshOutputVertices ||
|
||||
mesh.max_primitives > limits.maxMeshOutputPrimitives ||
|
||||
mesh.lds_size_dwords * sizeof(uint32_t) > limits.maxMeshSharedMemorySize) {
|
||||
EXIT("mesh shader exceeds host limits: threads=%u vertices=%u primitives=%u LDS=%u\n",
|
||||
host_threads, mesh.max_vertices, mesh.max_primitives, mesh.lds_size_dwords);
|
||||
}
|
||||
}
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
}
|
||||
if (context.GetClipControl().clip_disable) {
|
||||
const auto& viewport = context.GetScreenViewport().viewports[0];
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
auto& clip = input_info.clip_space;
|
||||
auto& clip = vertex_info.clip_space;
|
||||
clip.scale[0] = viewport.xscale;
|
||||
clip.scale[1] = viewport.yscale;
|
||||
clip.offset[0] = viewport.xoffset;
|
||||
@@ -358,33 +609,31 @@ ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
clip.enabled = true;
|
||||
}
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetPixelProgram(
|
||||
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info) {
|
||||
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
uint32_t push_data_cursor =
|
||||
mesh_active ? ShaderRecompiler::IR::PushData::MeshDrawDwordCount : 0;
|
||||
GraphicsPrograms result;
|
||||
if (pixel_active) {
|
||||
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
|
||||
}
|
||||
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
|
||||
return result;
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info) {
|
||||
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
input_info.host_subgroup_size = m_graphics.SupportsComputeWave64() ? 64u : 32u;
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
uint32_t push_data_cursor = 0;
|
||||
return m_program_cache->Get(params, input_info, push_data_cursor);
|
||||
}
|
||||
|
||||
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
|
||||
return std::memcmp(this, &other, sizeof(*this)) == 0;
|
||||
}
|
||||
|
||||
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
PipelineCache::Pipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
@@ -401,51 +650,45 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto& ctx = command.GetRegisters();
|
||||
|
||||
const HW::BlendColor& bclr = ctx.GetBlendColor();
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
color_mask[i] =
|
||||
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
|
||||
ctx.GetRenderTargetMask(), colors[i].target_slot))
|
||||
: 0);
|
||||
}
|
||||
const HW::ModeControl& mc = ctx.GetModeControl();
|
||||
|
||||
const auto vs_id = vertex_program.id;
|
||||
const auto ps_id = ps_active ? pixel_program.id : 0;
|
||||
|
||||
PipelineStaticParameters static_params {};
|
||||
GraphicsPipeline p {};
|
||||
p.ps_shader_id = ps_id;
|
||||
p.vs_shader_id = vs_id;
|
||||
|
||||
static_params.color_count = color_count;
|
||||
PipelineRenderingState rendering {};
|
||||
GraphicsPipelineKey key {};
|
||||
key.vs_shader_id = vs_id;
|
||||
key.ps_shader_id = ps_id;
|
||||
auto& static_params = key.static_params;
|
||||
auto& rendering = key.rendering;
|
||||
rendering.color_count = color_count;
|
||||
uint32_t attachment_samples = 0;
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
EXIT_IF(!colors[i].image_id || colors[i].format == vk::Format::eUndefined);
|
||||
rendering.color_formats[i] = colors[i].format;
|
||||
EXIT_IF(!colors[i].image_id || colors[i].desc.view_info.format == vk::Format::eUndefined);
|
||||
static_params.color_mask[i] = colors[i].export_mapping.ApplyMask(
|
||||
render_target_mask_slot(ctx.GetRenderTargetMask(), colors[i].target_slot));
|
||||
rendering.color_formats[i] = colors[i].desc.view_info.format;
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = colors[i].samples;
|
||||
} else if (attachment_samples != colors[i].samples) {
|
||||
attachment_samples = colors[i].desc.info.samples;
|
||||
} else if (attachment_samples != colors[i].desc.info.samples) {
|
||||
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
|
||||
attachment_samples, colors[i].samples);
|
||||
attachment_samples, colors[i].desc.info.samples);
|
||||
}
|
||||
}
|
||||
const bool with_depth =
|
||||
depth.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
|
||||
depth.desc.view_info.format != vk::Format::eUndefined && static_cast<bool>(depth.image_id);
|
||||
if (with_depth) {
|
||||
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
|
||||
rendering.depth_format =
|
||||
aspects & vk::ImageAspectFlagBits::eDepth ? depth.format : vk::Format::eUndefined;
|
||||
rendering.stencil_format =
|
||||
aspects & vk::ImageAspectFlagBits::eStencil ? depth.format : vk::Format::eUndefined;
|
||||
const auto aspects = ImageViewOps::DepthAspectMask(depth.desc.view_info.format);
|
||||
rendering.depth_format = aspects & vk::ImageAspectFlagBits::eDepth
|
||||
? depth.desc.view_info.format
|
||||
: vk::Format::eUndefined;
|
||||
rendering.stencil_format = aspects & vk::ImageAspectFlagBits::eStencil
|
||||
? depth.desc.view_info.format
|
||||
: vk::Format::eUndefined;
|
||||
if (attachment_samples == 0) {
|
||||
attachment_samples = depth.samples;
|
||||
} else if (attachment_samples != depth.samples) {
|
||||
attachment_samples = depth.desc.info.samples;
|
||||
} else if (attachment_samples != depth.desc.info.samples) {
|
||||
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
|
||||
depth.samples);
|
||||
depth.desc.info.samples);
|
||||
}
|
||||
}
|
||||
if (color_count == 0 && !with_depth) {
|
||||
@@ -475,23 +718,19 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
|
||||
EXIT("Pipeline: sample-rate shading is required but unsupported by the host\n");
|
||||
}
|
||||
static_params.with_depth = with_depth;
|
||||
static_params.depth_test_enable = depth.depth_test_enable;
|
||||
static_params.depth_write_enable = (depth.depth_write_enable && !depth.depth_clear_enable);
|
||||
static_params.depth_compare_op = depth.depth_compare_op;
|
||||
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
|
||||
static_params.depth_min_bounds = depth.depth_min_bounds;
|
||||
static_params.depth_max_bounds = depth.depth_max_bounds;
|
||||
static_params.stencil_test_enable = depth.stencil_test_enable;
|
||||
static_params.stencil_front = depth.stencil_static_front;
|
||||
static_params.stencil_back = depth.stencil_static_back;
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
static_params.color_mask[i] = color_mask[i];
|
||||
}
|
||||
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
|
||||
static_params.cull_back = !rect_list && mc.cull_back;
|
||||
static_params.cull_front = !rect_list && mc.cull_front;
|
||||
static_params.face = mc.face;
|
||||
static_params.provoking_vtx_last = mc.provoking_vtx_last;
|
||||
static_params.polygon_mode =
|
||||
ResolvePolygonMode(mc, static_params.cull_front, static_params.cull_back);
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
const auto& rt = ctx.GetRenderTarget(colors[i].target_slot);
|
||||
@@ -506,18 +745,32 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
static_params.blend_enable[i] = bc.enable;
|
||||
static_params.blend_bypass[i] = rt.info.blend_bypass;
|
||||
}
|
||||
static_params.blend_color_red = bclr.red;
|
||||
static_params.blend_color_green = bclr.green;
|
||||
static_params.blend_color_blue = bclr.blue;
|
||||
static_params.blend_color_alpha = bclr.alpha;
|
||||
|
||||
NormalizeStaticParamsForDynamicState(static_params);
|
||||
|
||||
GraphicsPipelineKey key {};
|
||||
key.rendering = rendering;
|
||||
key.vs_shader_id = p.vs_shader_id;
|
||||
key.ps_shader_id = p.ps_shader_id;
|
||||
key.static_params = static_params;
|
||||
if (vs_input_info.stage.program->stage != ShaderType::Mesh) {
|
||||
EXIT_IF(vs_input_info.buffers_num < 0 ||
|
||||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX ||
|
||||
vs_input_info.resources_num < 0 ||
|
||||
vs_input_info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.vertex_input.binding_count = static_cast<uint8_t>(vs_input_info.buffers_num);
|
||||
key.vertex_input.attribute_count = static_cast<uint8_t>(vs_input_info.resources_num);
|
||||
uint32_t attributes_num = 0;
|
||||
for (int binding = 0; binding < vs_input_info.buffers_num; binding++) {
|
||||
const auto& buffer = vs_input_info.buffers[binding];
|
||||
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
|
||||
attributes_num += static_cast<uint32_t>(buffer.attr_num);
|
||||
EXIT_IF(attributes_num > static_cast<uint32_t>(vs_input_info.resources_num));
|
||||
key.vertex_input.bindings[binding] = {.stride = buffer.stride,
|
||||
.instance = buffer.fetch_index != 0};
|
||||
for (int attribute = 0; attribute < buffer.attr_num; attribute++) {
|
||||
const auto index = buffer.attr_indices[attribute];
|
||||
EXIT_IF(index < 0 || index >= vs_input_info.resources_num);
|
||||
key.vertex_input.attributes[index] = {
|
||||
.offset = buffer.attr_offsets[attribute],
|
||||
.binding = static_cast<uint8_t>(binding),
|
||||
};
|
||||
}
|
||||
}
|
||||
EXIT_IF(attributes_num != static_cast<uint32_t>(vs_input_info.resources_num));
|
||||
}
|
||||
|
||||
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
|
||||
return *iter->second;
|
||||
@@ -528,16 +781,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
if (ps_active) {
|
||||
ShaderDbgDumpInputInfo(*ps_input_info);
|
||||
}
|
||||
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
|
||||
" module=%p\n",
|
||||
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64 " module=%p\n",
|
||||
vs_id, static_cast<void*>(vertex_program.module), ps_id,
|
||||
static_cast<void*>(pixel_program.module));
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<GraphicsPipeline>(p);
|
||||
auto cached = std::make_unique<Pipeline>();
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
|
||||
ps_input_info, pixel_program.module, static_params, m_driver_cache);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
|
||||
vertex_program, ps_input_info, pixel_program, static_params,
|
||||
m_driver_cache);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
@@ -549,22 +802,17 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
return *iter->second;
|
||||
}
|
||||
|
||||
PipelineCache::ComputePipeline&
|
||||
PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program) {
|
||||
PipelineCache::Pipeline&
|
||||
PipelineCache::CreateComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program) {
|
||||
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Compute)", profiler::colors::RedA100);
|
||||
|
||||
EXIT_IF(!compute_program);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
ComputePipeline p {};
|
||||
p.cs_shader_id = compute_program.id;
|
||||
|
||||
ComputePipelineKey key {};
|
||||
key.cs_shader_id = p.cs_shader_id;
|
||||
|
||||
if (auto iter = m_compute_pipelines.find(key); iter != m_compute_pipelines.end()) {
|
||||
if (auto iter = m_compute_pipelines.find(compute_program.id);
|
||||
iter != m_compute_pipelines.end()) {
|
||||
return *iter->second;
|
||||
}
|
||||
|
||||
@@ -572,13 +820,13 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
ShaderDbgDumpInputInfo(input_info);
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<ComputePipeline>(p);
|
||||
auto cached = std::make_unique<Pipeline>();
|
||||
CreatePipelineInternal(m_graphics, *cached, input_info, compute_program.module, m_driver_cache);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
|
||||
|
||||
auto [iter, inserted] = m_compute_pipelines.emplace(std::move(key), std::move(cached));
|
||||
auto [iter, inserted] = m_compute_pipelines.emplace(compute_program.id, std::move(cached));
|
||||
EXIT_IF(!inserted);
|
||||
|
||||
return *iter->second;
|
||||
|
||||
@@ -26,36 +26,31 @@ class CommandBuffer;
|
||||
namespace HW {
|
||||
class Context;
|
||||
class Shader;
|
||||
class UserConfig;
|
||||
struct ComputeShaderInfo;
|
||||
} // namespace HW
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct PipelineStaticParameters {
|
||||
float viewport_scale[3] = {};
|
||||
float viewport_offset[3] = {};
|
||||
bool negative_one_to_one = false;
|
||||
bool depth_clip_enable = true;
|
||||
int scissor_ltrb[4] = {0};
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
bool primitive_restart_enable = false;
|
||||
uint32_t samples = 1;
|
||||
bool sample_shading_enable = false;
|
||||
bool with_depth = false;
|
||||
bool depth_test_enable = false;
|
||||
bool depth_write_enable = false;
|
||||
vk::CompareOp depth_compare_op = vk::CompareOp::eNever;
|
||||
bool depth_bounds_test_enable = false;
|
||||
float depth_min_bounds = 0.0f;
|
||||
float depth_max_bounds = 0.0f;
|
||||
bool stencil_test_enable = false;
|
||||
PipelineStencilStaticState stencil_front;
|
||||
PipelineStencilStaticState stencil_back;
|
||||
uint32_t color_count = 1;
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool cull_front = false;
|
||||
bool cull_back = false;
|
||||
bool face = false;
|
||||
bool provoking_vtx_last = false;
|
||||
vk::PolygonMode polygon_mode = vk::PolygonMode::eFill;
|
||||
uint8_t color_srcblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint8_t color_comb_fcn[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint8_t color_destblend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
@@ -65,10 +60,6 @@ struct PipelineStaticParameters {
|
||||
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_bypass[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
float blend_color_red = 0.0f;
|
||||
float blend_color_green = 0.0f;
|
||||
float blend_color_blue = 0.0f;
|
||||
float blend_color_alpha = 0.0f;
|
||||
|
||||
bool operator==(const PipelineStaticParameters& other) const noexcept;
|
||||
};
|
||||
@@ -78,14 +69,7 @@ struct PipelineStaticParameters {
|
||||
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) ==
|
||||
sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) * 2 + sizeof(int[4]) +
|
||||
sizeof(vk::PrimitiveTopology) + sizeof(bool) + sizeof(uint32_t) +
|
||||
sizeof(bool) * 4 + sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 +
|
||||
sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
|
||||
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
|
||||
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
|
||||
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
|
||||
static_assert(sizeof(PipelineStaticParameters) == 166);
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
@@ -96,13 +80,31 @@ struct PipelineRenderingState {
|
||||
bool operator==(const PipelineRenderingState&) const = default;
|
||||
};
|
||||
|
||||
struct PipelineVertexInputState {
|
||||
struct Binding {
|
||||
uint32_t stride = 0;
|
||||
bool instance = false;
|
||||
bool operator==(const Binding&) const = default;
|
||||
};
|
||||
struct Attribute {
|
||||
uint32_t offset = 0;
|
||||
uint8_t binding = 0;
|
||||
bool operator==(const Attribute&) const = default;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
explicit operator bool() const {
|
||||
return id != 0 && module != nullptr;
|
||||
}
|
||||
explicit operator bool() const { return id != 0 && module != nullptr; }
|
||||
};
|
||||
|
||||
class PipelineCache {
|
||||
@@ -119,36 +121,30 @@ public:
|
||||
bool uses_push_descriptors = false;
|
||||
};
|
||||
|
||||
struct GraphicsPipeline: Pipeline {
|
||||
uint64_t vs_shader_id = 0;
|
||||
uint64_t ps_shader_id = 0;
|
||||
struct GraphicsPrograms {
|
||||
ShaderProgram vertex;
|
||||
ShaderProgram pixel;
|
||||
};
|
||||
|
||||
struct ComputePipeline: Pipeline {
|
||||
uint64_t cs_shader_id = 0;
|
||||
};
|
||||
|
||||
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context,
|
||||
ShaderVertexInputInfo& input_info);
|
||||
ShaderProgram
|
||||
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info);
|
||||
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, ShaderVertexInputInfo& vertex_info,
|
||||
ShaderPixelInputInfo& pixel_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
GraphicsPipeline& CreateGraphicsPipeline(
|
||||
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool primitive_restart_enable, const ShaderProgram& vertex_program,
|
||||
const ShaderProgram& pixel_program);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
Pipeline&
|
||||
CreateGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::PrimitiveTopology topology, bool primitive_restart_enable,
|
||||
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
|
||||
Pipeline& CreateComputePipeline(const ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
|
||||
private:
|
||||
struct ProgramCache;
|
||||
@@ -157,19 +153,13 @@ private:
|
||||
PipelineRenderingState rendering;
|
||||
uint64_t vs_shader_id = 0;
|
||||
uint64_t ps_shader_id = 0;
|
||||
PipelineVertexInputState vertex_input;
|
||||
PipelineStaticParameters static_params;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
|
||||
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
|
||||
}
|
||||
};
|
||||
|
||||
struct ComputePipelineKey {
|
||||
uint64_t cs_shader_id = 0;
|
||||
|
||||
bool operator==(const ComputePipelineKey& other) const {
|
||||
return cs_shader_id == other.cs_shader_id;
|
||||
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
|
||||
static_params == other.static_params;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -202,28 +192,28 @@ private:
|
||||
PipelineKeyHash::MixRendering(hash, key.rendering);
|
||||
PipelineKeyHash::Mix(hash, key.vs_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].stride);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].instance);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attribute_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.attribute_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].offset);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].binding);
|
||||
}
|
||||
PipelineKeyHash::MixStaticParams(hash, key.static_params);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct ComputePipelineKeyHash {
|
||||
std::size_t operator()(const ComputePipelineKey& key) const {
|
||||
std::size_t hash = 0;
|
||||
PipelineKeyHash::Mix(hash, key.cs_shader_id);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
GraphicContext& m_graphics;
|
||||
std::unique_ptr<ProgramCache> m_program_cache;
|
||||
vk::PipelineCache m_driver_cache = nullptr;
|
||||
std::filesystem::path m_driver_cache_path;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
|
||||
GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>, ComputePipelineKeyHash>
|
||||
m_compute_pipelines;
|
||||
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<Pipeline>, GraphicsPipelineKeyHash>
|
||||
m_graphics_pipelines;
|
||||
std::unordered_map<uint64_t, std::unique_ptr<Pipeline>> m_compute_pipelines;
|
||||
Common::Mutex m_mutex;
|
||||
|
||||
void InitializeDriverCache();
|
||||
@@ -231,14 +221,14 @@ private:
|
||||
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
|
||||
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
|
||||
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
|
||||
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
|
||||
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
|
||||
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
|
||||
if (stages & vk::ShaderStageFlagBits::eVertex) {
|
||||
result |= vk::PipelineStageFlagBits::eVertexShader;
|
||||
}
|
||||
if (stages & vk::ShaderStageFlagBits::eMeshEXT) {
|
||||
result |= vk::PipelineStageFlagBits::eMeshShaderEXT;
|
||||
}
|
||||
if (stages & vk::ShaderStageFlagBits::eFragment) {
|
||||
result |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
@@ -25,7 +28,6 @@ vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages) {
|
||||
|
||||
VulkanMemoryBarrier MakeShaderWriteDependency() {
|
||||
VulkanMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eMemoryBarrier;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
|
||||
vk::AccessFlagBits::eVertexAttributeRead |
|
||||
@@ -38,7 +40,6 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
|
||||
|
||||
VulkanMemoryBarrier MakeShaderAccessDependency() {
|
||||
VulkanMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eMemoryBarrier;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
return barrier;
|
||||
@@ -46,7 +47,6 @@ VulkanMemoryBarrier MakeShaderAccessDependency() {
|
||||
|
||||
VulkanMemoryBarrier MakeShaderWriteHazardDependency() {
|
||||
VulkanMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eMemoryBarrier;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
return barrier;
|
||||
@@ -56,7 +56,6 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
|
||||
vk::BufferMemoryBarrier barrier {};
|
||||
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite | vk::AccessFlagBits::eTransferWrite |
|
||||
vk::AccessFlagBits::eShaderWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||
@@ -68,11 +67,12 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
return barrier;
|
||||
}
|
||||
|
||||
std::vector<ShaderBufferWriteRange>
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot& resources) {
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& resources = runtime.resources;
|
||||
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
|
||||
std::vector<ShaderBufferWriteRange> writes;
|
||||
bool has_writes = false;
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
if (!program.info.buffers[i].written) {
|
||||
continue;
|
||||
@@ -81,23 +81,11 @@ CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
EXIT_IF(value.dword_count < 4);
|
||||
ShaderBufferResource descriptor;
|
||||
std::memcpy(descriptor.fields, value.dwords.data(), sizeof(descriptor.fields));
|
||||
const auto address = descriptor.Base48();
|
||||
const auto records = static_cast<uint64_t>(descriptor.NumRecords());
|
||||
const auto stride = static_cast<uint64_t>(descriptor.Stride());
|
||||
if (stride != 0 && records > UINT64_MAX / stride) {
|
||||
EXIT("shader resource barrier buffer footprint overflow\n");
|
||||
}
|
||||
const auto size = stride == 0 ? records : stride * records;
|
||||
if (address != 0 && size != 0) {
|
||||
writes.push_back({address, size});
|
||||
}
|
||||
// A zero stride means byte addressing. For either addressing mode a nonzero record
|
||||
// count is exactly the condition for a nonempty descriptor range.
|
||||
has_writes |= descriptor.Base48() != 0 && descriptor.NumRecords() != 0;
|
||||
}
|
||||
return writes;
|
||||
}
|
||||
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
|
||||
return has_writes;
|
||||
}
|
||||
|
||||
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
|
||||
@@ -118,13 +106,11 @@ void ShaderWriteHazardBarrier(vk::CommandBuffer vk_buffer,
|
||||
void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
|
||||
EXIT_IF(vk_buffer == nullptr || !source_stages);
|
||||
const auto barrier = MakeShaderWriteDependency();
|
||||
vk_buffer.pipelineBarrier(
|
||||
source_stages,
|
||||
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexInput |
|
||||
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
|
||||
vk::PipelineStageFlagBits::eTransfer |
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
vk_buffer.pipelineBarrier(source_stages,
|
||||
vk::PipelineStageFlagBits::eComputeShader |
|
||||
vk::PipelineStageFlagBits::eAllGraphics |
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -4,27 +4,15 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct ShaderStageRuntime;
|
||||
|
||||
struct ShaderBufferWriteRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
|
||||
bool operator==(const ShaderBufferWriteRange& other) const = default;
|
||||
};
|
||||
|
||||
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
|
||||
VulkanMemoryBarrier MakeShaderAccessDependency();
|
||||
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
|
||||
VulkanMemoryBarrier MakeShaderWriteDependency();
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
|
||||
std::vector<ShaderBufferWriteRange>
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot& resources);
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
|
||||
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
|
||||
void ShaderWriteHazardBarrier(vk::CommandBuffer vk_buffer,
|
||||
|
||||
@@ -103,7 +103,7 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
|
||||
size = 4;
|
||||
if (NarrowInputFormat(format, size, used_components)) {
|
||||
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
|
||||
VulkanToString(format).c_str(), used_components);
|
||||
vk::to_string(format).c_str(), used_components);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
|
||||
size = 2;
|
||||
if (NarrowInputFormat(format, size, used_components)) {
|
||||
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
|
||||
VulkanToString(format).c_str(), used_components);
|
||||
vk::to_string(format).c_str(), used_components);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -390,7 +390,7 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
}
|
||||
|
||||
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
vk::ShaderStageFlagBits stage) {
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
descriptor_bindings.push_back(
|
||||
@@ -408,7 +408,6 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
pipeline.uses_push_descriptors = descriptor_count <= graphics.max_push_descriptors;
|
||||
|
||||
vk::DescriptorSetLayoutCreateInfo create {};
|
||||
create.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
|
||||
create.flags = pipeline.uses_push_descriptors
|
||||
? vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR
|
||||
: vk::DescriptorSetLayoutCreateFlags {};
|
||||
@@ -420,21 +419,27 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
|
||||
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache) {
|
||||
GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
|
||||
const ShaderVertexInputInfo& vs_input_info, const ShaderProgram& vertex_program,
|
||||
const ShaderPixelInputInfo* ps_input_info, const ShaderProgram& pixel_program,
|
||||
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == 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;
|
||||
EXIT_NOT_IMPLEMENTED(mesh && !graphics.mesh_shader_enabled);
|
||||
const auto vertex_stage =
|
||||
mesh ? vk::ShaderStageFlagBits::eMeshEXT : vk::ShaderStageFlagBits::eVertex;
|
||||
|
||||
const bool rect_list = static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
const bool rect_list = !mesh && static_params.topology == vk::PrimitiveTopology::ePatchList;
|
||||
|
||||
vk::ShaderModule tess_control_shader_module = nullptr;
|
||||
vk::ShaderModule tess_eval_shader_module = nullptr;
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
vk::Result result {};
|
||||
if (rect_list) {
|
||||
const auto shaders =
|
||||
@@ -445,7 +450,7 @@ void CreatePipelineInternal(
|
||||
graphics.device.createShaderModule(&create_info, nullptr, &tess_control_shader_module);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TCS done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(tess_control_shader_module));
|
||||
vk::to_string(result).c_str(), static_cast<void*>(tess_control_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
@@ -455,7 +460,7 @@ void CreatePipelineInternal(
|
||||
graphics.device.createShaderModule(&create_info, nullptr, &tess_eval_shader_module);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateShaderModule RectList TES done result=%s module=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(tess_eval_shader_module));
|
||||
vk::to_string(result).c_str(), static_cast<void*>(tess_eval_shader_module));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
}
|
||||
@@ -464,32 +469,21 @@ void CreatePipelineInternal(
|
||||
rect_list && (tess_control_shader_module == nullptr || tess_eval_shader_module == nullptr));
|
||||
|
||||
vk::PipelineShaderStageCreateInfo vert_shader_stage_info {};
|
||||
|
||||
vert_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
vert_shader_stage_info.pNext = nullptr;
|
||||
vert_shader_stage_info.flags = {};
|
||||
vert_shader_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
|
||||
vert_shader_stage_info.module = vertex_module;
|
||||
vert_shader_stage_info.pName = "main";
|
||||
vert_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
vert_shader_stage_info.stage = vertex_stage;
|
||||
vert_shader_stage_info.module = vertex_program.module;
|
||||
vert_shader_stage_info.pName = "main";
|
||||
|
||||
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
|
||||
frag_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
frag_shader_stage_info.pNext = nullptr;
|
||||
frag_shader_stage_info.flags = {};
|
||||
frag_shader_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
|
||||
frag_shader_stage_info.module = pixel_module;
|
||||
frag_shader_stage_info.pName = "main";
|
||||
frag_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
frag_shader_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
|
||||
frag_shader_stage_info.module = pixel_program.module;
|
||||
frag_shader_stage_info.pName = "main";
|
||||
|
||||
vk::PipelineShaderStageCreateInfo tess_control_shader_stage_info {};
|
||||
tess_control_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
tess_control_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationControl;
|
||||
tess_control_shader_stage_info.module = tess_control_shader_module;
|
||||
tess_control_shader_stage_info.pName = "main";
|
||||
|
||||
vk::PipelineShaderStageCreateInfo tess_eval_shader_stage_info {};
|
||||
tess_eval_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
tess_eval_shader_stage_info.stage = vk::ShaderStageFlagBits::eTessellationEvaluation;
|
||||
tess_eval_shader_stage_info.module = tess_eval_shader_module;
|
||||
tess_eval_shader_stage_info.pName = "main";
|
||||
@@ -505,182 +499,81 @@ void CreatePipelineInternal(
|
||||
shader_stages[shader_stage_count++] = frag_shader_stage_info;
|
||||
}
|
||||
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX];
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX] {};
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX] {};
|
||||
|
||||
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
|
||||
const auto& b = vs_input_info.buffers[bi];
|
||||
input_desc[bi].binding = bi;
|
||||
input_desc[bi].stride = b.stride;
|
||||
input_desc[bi].inputRate =
|
||||
(b.fetch_index == 0 ? vk::VertexInputRate::eVertex : vk::VertexInputRate::eInstance);
|
||||
for (uint32_t binding = 0; binding < vertex_input.binding_count; binding++) {
|
||||
input_desc[binding].binding = binding;
|
||||
input_desc[binding].stride = vertex_input.bindings[binding].stride;
|
||||
input_desc[binding].inputRate = vertex_input.bindings[binding].instance
|
||||
? vk::VertexInputRate::eInstance
|
||||
: vk::VertexInputRate::eVertex;
|
||||
}
|
||||
for (uint32_t index = 0; index < vertex_input.attribute_count; index++) {
|
||||
input_attr[index].binding = vertex_input.attributes[index].binding;
|
||||
input_attr[index].location = index;
|
||||
input_attr[index].offset = vertex_input.attributes[index].offset;
|
||||
|
||||
for (int ai = 0; ai < b.attr_num; ai++) {
|
||||
auto index = b.attr_indices[ai];
|
||||
input_attr[index].binding = bi;
|
||||
input_attr[index].location = index;
|
||||
input_attr[index].offset = b.attr_offsets[ai];
|
||||
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));
|
||||
|
||||
uint32_t attr_size = 4;
|
||||
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];
|
||||
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[bi].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_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
|
||||
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
|
||||
|
||||
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](
|
||||
uint32_t swizzle, uint32_t expected) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF(
|
||||
"VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
|
||||
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
|
||||
", expected=0x%03" PRIx32 ")\n",
|
||||
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
|
||||
logged = true;
|
||||
}
|
||||
};
|
||||
|
||||
switch (registers_num) {
|
||||
case 1: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelX();
|
||||
if (swizzle != DstSel(4)) {
|
||||
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXY();
|
||||
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected = DstSel(4, 5, 6);
|
||||
switch (attr_size) {
|
||||
case 1: expected = DstSel(4, 0, 0); break;
|
||||
case 2: expected = DstSel(4, 5, 0); break;
|
||||
default: break;
|
||||
}
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZW();
|
||||
auto expected = DstSel(4, 5, 6, 7);
|
||||
bool supported = false;
|
||||
switch (attr_size) {
|
||||
case 1:
|
||||
expected = DstSel(4, 0, 0, 1);
|
||||
supported = (swizzle == expected);
|
||||
break;
|
||||
case 2:
|
||||
expected = DstSel(4, 5, 0, 1);
|
||||
supported = (swizzle == expected);
|
||||
break;
|
||||
case 3:
|
||||
expected = DstSel(4, 5, 6, 1);
|
||||
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
|
||||
break;
|
||||
default:
|
||||
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
|
||||
swizzle == DstSel(4, 5, 6, 0));
|
||||
break;
|
||||
}
|
||||
if (!supported) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: EXIT("invalid registers_num");
|
||||
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_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
|
||||
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
|
||||
|
||||
EXIT_IF(registers_num < 1 || registers_num > 4);
|
||||
}
|
||||
|
||||
vk::PipelineVertexInputStateCreateInfo vertex_input_info {};
|
||||
vertex_input_info.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
|
||||
vertex_input_info.pNext = nullptr;
|
||||
vertex_input_info.flags = {};
|
||||
vertex_input_info.vertexBindingDescriptionCount = vs_input_info.buffers_num;
|
||||
vertex_input_info.vertexBindingDescriptionCount = vertex_input.binding_count;
|
||||
vertex_input_info.pVertexBindingDescriptions = input_desc;
|
||||
vertex_input_info.vertexAttributeDescriptionCount = vs_input_info.resources_num;
|
||||
vertex_input_info.vertexAttributeDescriptionCount = vertex_input.attribute_count;
|
||||
vertex_input_info.pVertexAttributeDescriptions = input_attr;
|
||||
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
|
||||
input_assembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo;
|
||||
input_assembly.pNext = nullptr;
|
||||
input_assembly.flags = {};
|
||||
input_assembly.topology = static_params.topology;
|
||||
input_assembly.primitiveRestartEnable =
|
||||
static_params.primitive_restart_enable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
vk::Viewport viewport {};
|
||||
viewport.x = static_params.viewport_offset[0] - static_params.viewport_scale[0];
|
||||
viewport.y = static_params.viewport_offset[1] - static_params.viewport_scale[1];
|
||||
viewport.width = static_params.viewport_scale[0] * 2.0f;
|
||||
viewport.height = static_params.viewport_scale[1] * 2.0f;
|
||||
viewport.minDepth = static_params.viewport_offset[2];
|
||||
viewport.maxDepth = static_params.viewport_scale[2] + static_params.viewport_offset[2];
|
||||
|
||||
vk::Rect2D scissor {};
|
||||
scissor.offset = {static_params.scissor_ltrb[0], static_params.scissor_ltrb[1]};
|
||||
scissor.extent = {
|
||||
static_cast<uint32_t>(static_params.scissor_ltrb[2] - static_params.scissor_ltrb[0]),
|
||||
static_cast<uint32_t>(static_params.scissor_ltrb[3] - static_params.scissor_ltrb[1])};
|
||||
|
||||
vk::PipelineViewportDepthClipControlCreateInfoEXT depth_clip_control {};
|
||||
depth_clip_control.sType = vk::StructureType::ePipelineViewportDepthClipControlCreateInfoEXT;
|
||||
depth_clip_control.pNext = nullptr;
|
||||
depth_clip_control.negativeOneToOne = (static_params.negative_one_to_one ? VK_TRUE : VK_FALSE);
|
||||
|
||||
vk::PipelineViewportStateCreateInfo viewport_state {};
|
||||
viewport_state.sType = vk::StructureType::ePipelineViewportStateCreateInfo;
|
||||
viewport_state.pNext = &depth_clip_control;
|
||||
viewport_state.flags = {};
|
||||
viewport_state.viewportCount = 1;
|
||||
viewport_state.pViewports = &viewport;
|
||||
viewport_state.scissorCount = 1;
|
||||
viewport_state.pScissors = &scissor;
|
||||
viewport_state.pNext = &depth_clip_control;
|
||||
|
||||
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
|
||||
if (static_params.cull_back) {
|
||||
@@ -694,65 +587,38 @@ void CreatePipelineInternal(
|
||||
(static_params.face ? vk::FrontFace::eClockwise : vk::FrontFace::eCounterClockwise);
|
||||
|
||||
vk::PipelineRasterizationDepthClipStateCreateInfoEXT clip_ext {};
|
||||
clip_ext.sType = vk::StructureType::ePipelineRasterizationDepthClipStateCreateInfoEXT;
|
||||
clip_ext.pNext = nullptr;
|
||||
clip_ext.flags = {};
|
||||
clip_ext.depthClipEnable = static_params.depth_clip_enable ? VK_TRUE : VK_FALSE;
|
||||
|
||||
vk::PipelineRasterizationStateCreateInfo rasterizer {};
|
||||
rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_depth_clip_enable; omit the depth-clip struct on macOS and accept
|
||||
// Vulkan's default depth clipping (enabled) instead of the PS5's clamp behavior.
|
||||
#if defined(__APPLE__)
|
||||
rasterizer.pNext = nullptr;
|
||||
#else
|
||||
#if !defined(__APPLE__)
|
||||
rasterizer.pNext = &clip_ext;
|
||||
#endif
|
||||
rasterizer.flags = {};
|
||||
rasterizer.depthClampEnable = VK_FALSE;
|
||||
rasterizer.rasterizerDiscardEnable = VK_FALSE;
|
||||
rasterizer.polygonMode = vk::PolygonMode::eFill;
|
||||
rasterizer.cullMode = cull_mode;
|
||||
rasterizer.frontFace = front_face;
|
||||
rasterizer.depthBiasEnable = VK_FALSE;
|
||||
rasterizer.depthBiasConstantFactor = 0.0f;
|
||||
rasterizer.depthBiasClamp = 0.0f;
|
||||
rasterizer.depthBiasSlopeFactor = 0.0f;
|
||||
rasterizer.lineWidth = 1.0f;
|
||||
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex {};
|
||||
EXIT_NOT_IMPLEMENTED(static_params.provoking_vtx_last &&
|
||||
!graphics.provoking_vertex_last_enabled);
|
||||
if (graphics.provoking_vertex_last_enabled) {
|
||||
provoking_vertex.provokingVertexMode = static_params.provoking_vtx_last
|
||||
? vk::ProvokingVertexModeEXT::eLastVertex : vk::ProvokingVertexModeEXT::eFirstVertex;
|
||||
provoking_vertex.pNext = rasterizer.pNext;
|
||||
rasterizer.pNext = &provoking_vertex;
|
||||
}
|
||||
rasterizer.cullMode = cull_mode;
|
||||
rasterizer.frontFace = front_face;
|
||||
rasterizer.polygonMode = static_params.polygon_mode;
|
||||
rasterizer.lineWidth = 1.0f;
|
||||
|
||||
vk::PipelineMultisampleStateCreateInfo multisampling {};
|
||||
multisampling.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
|
||||
multisampling.pNext = nullptr;
|
||||
multisampling.flags = {};
|
||||
multisampling.sampleShadingEnable = static_params.sample_shading_enable ? VK_TRUE : VK_FALSE;
|
||||
multisampling.rasterizationSamples = vulkan_sample_count(static_params.samples);
|
||||
multisampling.minSampleShading = 1.0f;
|
||||
multisampling.pSampleMask = nullptr;
|
||||
multisampling.alphaToCoverageEnable = VK_FALSE;
|
||||
multisampling.alphaToOneEnable = VK_FALSE;
|
||||
multisampling.sampleShadingEnable = static_params.sample_shading_enable ? VK_TRUE : VK_FALSE;
|
||||
multisampling.rasterizationSamples = vulkan_sample_count(static_params.samples);
|
||||
multisampling.minSampleShading = 1.0f;
|
||||
|
||||
vk::PipelineColorBlendAttachmentState color_blend_attachment[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < static_params.color_count; i++) {
|
||||
vk::ColorComponentFlags color_write_mask = {};
|
||||
for (uint32_t i = 0; i < rendering.color_count; i++) {
|
||||
EXIT_NOT_IMPLEMENTED((static_params.color_mask[i] & ~0x0fu) != 0);
|
||||
if ((static_params.color_mask[i] & 0x1u) != 0) {
|
||||
color_write_mask |=
|
||||
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eR);
|
||||
}
|
||||
if ((static_params.color_mask[i] & 0x2u) != 0) {
|
||||
color_write_mask |=
|
||||
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eG);
|
||||
}
|
||||
if ((static_params.color_mask[i] & 0x4u) != 0) {
|
||||
color_write_mask |=
|
||||
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eB);
|
||||
}
|
||||
if ((static_params.color_mask[i] & 0x8u) != 0) {
|
||||
color_write_mask |=
|
||||
static_cast<vk::ColorComponentFlags>(vk::ColorComponentFlagBits::eA);
|
||||
}
|
||||
|
||||
color_blend_attachment[i].colorWriteMask = color_write_mask;
|
||||
color_blend_attachment[i].colorWriteMask =
|
||||
vk::ColorComponentFlags {static_params.color_mask[i]};
|
||||
color_blend_attachment[i].blendEnable =
|
||||
(static_params.blend_enable[i] && !static_params.blend_bypass[i]) ? VK_TRUE : VK_FALSE;
|
||||
color_blend_attachment[i].srcColorBlendFactor =
|
||||
@@ -773,54 +639,37 @@ void CreatePipelineInternal(
|
||||
}
|
||||
|
||||
vk::Bool32 color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < static_params.color_count; i++) {
|
||||
for (uint32_t i = 0; i < rendering.color_count; i++) {
|
||||
color_write_enable[i] = VK_TRUE;
|
||||
}
|
||||
|
||||
vk::PipelineColorWriteCreateInfoEXT color_write {};
|
||||
color_write.sType = vk::StructureType::ePipelineColorWriteCreateInfoEXT;
|
||||
color_write.pNext = nullptr;
|
||||
color_write.attachmentCount = static_params.color_count;
|
||||
color_write.attachmentCount = rendering.color_count;
|
||||
color_write.pColorWriteEnables = color_write_enable;
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo color_blending {};
|
||||
color_blending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo;
|
||||
// MoltenVK lacks VK_EXT_color_write_enable; drop the dynamic color-write struct on macOS
|
||||
// and rely on each attachment's static colorWriteMask (all channels enabled by default).
|
||||
#if defined(__APPLE__)
|
||||
color_blending.pNext = nullptr;
|
||||
#else
|
||||
#if !defined(__APPLE__)
|
||||
color_blending.pNext = &color_write;
|
||||
#endif
|
||||
color_blending.flags = {};
|
||||
color_blending.logicOpEnable = VK_FALSE;
|
||||
color_blending.logicOp = vk::LogicOp::eCopy;
|
||||
color_blending.attachmentCount = static_params.color_count;
|
||||
color_blending.pAttachments = color_blend_attachment;
|
||||
color_blending.blendConstants[0] = static_params.blend_color_red;
|
||||
color_blending.blendConstants[1] = static_params.blend_color_green;
|
||||
color_blending.blendConstants[2] = static_params.blend_color_blue;
|
||||
color_blending.blendConstants[3] = static_params.blend_color_alpha;
|
||||
color_blending.logicOp = vk::LogicOp::eCopy;
|
||||
color_blending.attachmentCount = rendering.color_count;
|
||||
color_blending.pAttachments = color_blend_attachment;
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program,
|
||||
vk::ShaderStageFlagBits::eVertex);
|
||||
AddLayoutBindings(descriptor_bindings, *vs_input_info.stage.program, vertex_stage);
|
||||
if (ps_active) {
|
||||
EXIT_IF(!ps_input_info->stage);
|
||||
AddLayoutBindings(descriptor_bindings, *ps_input_info->stage.program,
|
||||
vk::ShaderStageFlagBits::eFragment);
|
||||
}
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
constexpr auto GraphicsStages =
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
|
||||
const vk::PushConstantRange push_constants {
|
||||
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const auto GraphicsStages = vertex_stage | vk::ShaderStageFlagBits::eFragment;
|
||||
const vk::PushConstantRange push_constants {GraphicsStages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.pNext = nullptr;
|
||||
pipeline_layout_info.flags = {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
@@ -831,25 +680,19 @@ void CreatePipelineInternal(
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" set_layouts=1 push_constants=%" PRIu32 "\n",
|
||||
pipeline.vs_shader_id, pipeline.ps_shader_id, 1u);
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0, 1u);
|
||||
}
|
||||
result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout done result=%s layout=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
|
||||
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::PipelineDepthStencilStateCreateInfo depth_stencil_info {};
|
||||
depth_stencil_info.sType = vk::StructureType::ePipelineDepthStencilStateCreateInfo;
|
||||
depth_stencil_info.pNext = nullptr;
|
||||
depth_stencil_info.flags = {};
|
||||
depth_stencil_info.depthTestEnable = (static_params.depth_test_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.depthWriteEnable = (static_params.depth_write_enable ? VK_TRUE : VK_FALSE);
|
||||
depth_stencil_info.depthCompareOp = static_params.depth_compare_op;
|
||||
depth_stencil_info.depthBoundsTestEnable =
|
||||
#if defined(__APPLE__)
|
||||
VK_FALSE; // MoltenVK lacks the depthBounds feature; depth-bounds testing is disabled
|
||||
@@ -868,84 +711,72 @@ void CreatePipelineInternal(
|
||||
depth_stencil_info.minDepthBounds = static_params.depth_min_bounds;
|
||||
depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds;
|
||||
|
||||
const vk::DynamicState dynamic_states[] = {
|
||||
vk::DynamicState::eViewport,
|
||||
vk::DynamicState::eScissor,
|
||||
std::vector<vk::DynamicState> dynamic_states {
|
||||
vk::DynamicState::eViewportWithCount,
|
||||
vk::DynamicState::eScissorWithCount,
|
||||
vk::DynamicState::eLineWidth,
|
||||
vk::DynamicState::eDepthTestEnable,
|
||||
vk::DynamicState::eDepthWriteEnable,
|
||||
vk::DynamicState::eDepthCompareOp,
|
||||
vk::DynamicState::eDepthBiasEnable,
|
||||
vk::DynamicState::eDepthBias,
|
||||
vk::DynamicState::eStencilCompareMask,
|
||||
vk::DynamicState::eStencilReference,
|
||||
vk::DynamicState::eStencilWriteMask,
|
||||
#if !defined(__APPLE__)
|
||||
// Keep last so depth-only pipelines can omit this dynamic state.
|
||||
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
|
||||
#endif
|
||||
vk::DynamicState::eBlendConstants,
|
||||
};
|
||||
auto dynamic_states_count =
|
||||
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
|
||||
#if !defined(__APPLE__)
|
||||
if (static_params.color_count == 0) {
|
||||
dynamic_states_count--;
|
||||
if (rendering.color_count != 0) {
|
||||
dynamic_states.push_back(vk::DynamicState::eColorWriteEnableEXT);
|
||||
}
|
||||
#endif
|
||||
if (graphics.attachment_feedback_loop_enabled) {
|
||||
dynamic_states.push_back(vk::DynamicState::eAttachmentFeedbackLoopEnableEXT);
|
||||
}
|
||||
|
||||
vk::PipelineDynamicStateCreateInfo dynamic_state {};
|
||||
dynamic_state.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
|
||||
dynamic_state.pNext = nullptr;
|
||||
dynamic_state.flags = {};
|
||||
dynamic_state.dynamicStateCount = dynamic_states_count;
|
||||
dynamic_state.pDynamicStates = dynamic_states;
|
||||
dynamic_state.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||
dynamic_state.pDynamicStates = dynamic_states.data();
|
||||
|
||||
vk::GraphicsPipelineCreateInfo pipeline_info {};
|
||||
vk::PipelineRenderingCreateInfo rendering_info {};
|
||||
rendering_info.sType = vk::StructureType::ePipelineRenderingCreateInfo;
|
||||
rendering_info.colorAttachmentCount = rendering.color_count;
|
||||
rendering_info.pColorAttachmentFormats = rendering.color_formats.data();
|
||||
rendering_info.depthAttachmentFormat = rendering.depth_format;
|
||||
rendering_info.stencilAttachmentFormat = rendering.stencil_format;
|
||||
pipeline_info.sType = vk::StructureType::eGraphicsPipelineCreateInfo;
|
||||
pipeline_info.pNext = &rendering_info;
|
||||
pipeline_info.flags = {};
|
||||
pipeline_info.stageCount = shader_stage_count;
|
||||
pipeline_info.pStages = shader_stages;
|
||||
pipeline_info.pVertexInputState = &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = &input_assembly;
|
||||
pipeline_info.pVertexInputState = mesh ? nullptr : &vertex_input_info;
|
||||
pipeline_info.pInputAssemblyState = mesh ? nullptr : &input_assembly;
|
||||
vk::PipelineTessellationStateCreateInfo tessellation_state {};
|
||||
tessellation_state.sType = vk::StructureType::ePipelineTessellationStateCreateInfo;
|
||||
tessellation_state.patchControlPoints = 3;
|
||||
pipeline_info.pTessellationState = (rect_list ? &tessellation_state : nullptr);
|
||||
pipeline_info.pViewportState = &viewport_state;
|
||||
pipeline_info.pRasterizationState = &rasterizer;
|
||||
pipeline_info.pMultisampleState = &multisampling;
|
||||
pipeline_info.pDepthStencilState = (static_params.with_depth ? &depth_stencil_info : nullptr);
|
||||
pipeline_info.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_state;
|
||||
pipeline_info.layout = pipeline.pipeline_layout;
|
||||
pipeline_info.renderPass = nullptr;
|
||||
pipeline_info.subpass = 0;
|
||||
pipeline_info.basePipelineHandle = nullptr;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
pipeline_info.pDepthStencilState = (with_depth ? &depth_stencil_info : nullptr);
|
||||
pipeline_info.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_state;
|
||||
pipeline_info.layout = pipeline.pipeline_layout;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
|
||||
EXIT_IF(pipeline.pipeline != nullptr);
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines begin VS=%" PRIu64 " PS=%" PRIu64
|
||||
" topology=%" PRIu32 " color_mask=0x%08" PRIx32
|
||||
" depth=%s blend=%s dyn_states=%" PRIu32 " viewport=%f,%f %fx%f"
|
||||
" scissor=%d,%d %ux%u\n",
|
||||
pipeline.vs_shader_id, pipeline.ps_shader_id,
|
||||
static_cast<uint32_t>(static_params.topology),
|
||||
static_params.color_mask[0], (static_params.with_depth ? "true" : "false"),
|
||||
(static_params.blend_enable[0] ? "true" : "false"), dynamic_states_count, viewport.x,
|
||||
viewport.y, viewport.width, viewport.height, scissor.offset.x, scissor.offset.y,
|
||||
scissor.extent.width, scissor.extent.height);
|
||||
" depth=%s blend=%s dyn_states=%" PRIu32 "\n",
|
||||
vertex_program.id, ps_active ? pixel_program.id : 0,
|
||||
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);
|
||||
}
|
||||
result = graphics.device.createGraphicsPipelines(driver_cache, 1, &pipeline_info, nullptr,
|
||||
&pipeline.pipeline);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("PipelineTrace: vkCreateGraphicsPipelines done result=%s pipeline=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
@@ -960,26 +791,20 @@ void CreatePipelineInternal(
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::Pipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache) {
|
||||
EXIT_IF(compute_module == nullptr);
|
||||
|
||||
vk::PipelineShaderStageCreateInfo comp_shader_stage_info {};
|
||||
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo comp_subgroup_size {};
|
||||
comp_shader_stage_info.sType = vk::StructureType::ePipelineShaderStageCreateInfo;
|
||||
comp_shader_stage_info.pNext = nullptr;
|
||||
comp_shader_stage_info.flags = {};
|
||||
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
comp_shader_stage_info.module = compute_module;
|
||||
comp_shader_stage_info.pName = "main";
|
||||
comp_shader_stage_info.pSpecializationInfo = nullptr;
|
||||
comp_shader_stage_info.stage = vk::ShaderStageFlagBits::eCompute;
|
||||
comp_shader_stage_info.module = compute_module;
|
||||
comp_shader_stage_info.pName = "main";
|
||||
EXIT_IF(!input_info.stage);
|
||||
const auto wave_size = input_info.stage.program->wave_size;
|
||||
if (graphics.compute_subgroup_size_control_enabled &&
|
||||
wave_size >= graphics.min_subgroup_size && wave_size <= graphics.max_subgroup_size) {
|
||||
comp_subgroup_size.sType =
|
||||
vk::StructureType::ePipelineShaderStageRequiredSubgroupSizeCreateInfo;
|
||||
comp_subgroup_size.requiredSubgroupSize = wave_size;
|
||||
comp_shader_stage_info.pNext = &comp_subgroup_size;
|
||||
}
|
||||
@@ -992,9 +817,6 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
pipeline_layout_info.pNext = nullptr;
|
||||
pipeline_layout_info.flags = {};
|
||||
pipeline_layout_info.setLayoutCount = 1;
|
||||
pipeline_layout_info.pSetLayouts = &pipeline.descriptor_set_layout;
|
||||
pipeline_layout_info.pushConstantRangeCount = 1;
|
||||
@@ -1007,19 +829,15 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
|
||||
auto result = graphics.device.createPipelineLayout(&pipeline_layout_info, nullptr,
|
||||
&pipeline.pipeline_layout);
|
||||
LOGF("PipelineTrace: vkCreatePipelineLayout CS done result=%s layout=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline_layout));
|
||||
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 {};
|
||||
info.sType = vk::StructureType::eComputePipelineCreateInfo;
|
||||
info.pNext = nullptr;
|
||||
info.flags = {};
|
||||
info.stage = comp_shader_stage_info;
|
||||
info.layout = pipeline.pipeline_layout;
|
||||
info.basePipelineHandle = nullptr;
|
||||
info.basePipelineIndex = -1;
|
||||
info.stage = comp_shader_stage_info;
|
||||
info.layout = pipeline.pipeline_layout;
|
||||
info.basePipelineIndex = -1;
|
||||
|
||||
EXIT_IF(pipeline.pipeline != nullptr);
|
||||
|
||||
@@ -1028,7 +846,7 @@ void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipe
|
||||
result = graphics.device.createComputePipelines(driver_cache, 1, &info, nullptr,
|
||||
&pipeline.pipeline);
|
||||
LOGF("PipelineTrace: vkCreateComputePipelines done result=%s pipeline=%p\n",
|
||||
VulkanToString(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
vk::to_string(result).c_str(), static_cast<void*>(pipeline.pipeline));
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pipeline.pipeline == nullptr);
|
||||
|
||||
@@ -48,6 +48,31 @@ enum class CommandBufferDebugOp : uint32_t {
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class DrawOffsetSource : uint8_t {
|
||||
DrawState,
|
||||
IndirectArgs,
|
||||
};
|
||||
|
||||
struct DrawIndexArgs {
|
||||
uint32_t index_count = 0;
|
||||
const void* index_addr = nullptr;
|
||||
uint32_t instance_count = 0;
|
||||
uint32_t index_type_and_size = 0;
|
||||
int32_t base_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
};
|
||||
|
||||
struct DrawAutoArgs {
|
||||
uint32_t vertex_count = 0;
|
||||
uint32_t instance_count = 0;
|
||||
uint32_t first_vertex = 0;
|
||||
uint32_t first_instance = 0;
|
||||
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
|
||||
uint32_t render_target_slice_offset = 0;
|
||||
};
|
||||
|
||||
struct SubmitInfo {
|
||||
static constexpr uint32_t MaxSemaphores = 3;
|
||||
|
||||
@@ -129,13 +154,6 @@ public:
|
||||
explicit RenderExecutor(RenderContext& context): m_context(context) {}
|
||||
KYTY_CLASS_NO_COPY(RenderExecutor);
|
||||
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_type_and_size,
|
||||
uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 1, uint32_t render_target_slice_offset = 0,
|
||||
int32_t vertex_offset_add = 0, uint32_t first_instance = 0);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
|
||||
uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||
void DispatchDirect(uint64_t submit_id, CommandBuffer& buffer, uint32_t thread_group_x,
|
||||
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
|
||||
|
||||
@@ -148,6 +166,9 @@ public:
|
||||
std::span<PreparedBindings* const> bindings);
|
||||
|
||||
private:
|
||||
void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
|
||||
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
|
||||
|
||||
struct GraphicsBindings {
|
||||
PreparedBindings vertex;
|
||||
std::optional<PreparedBindings> pixel;
|
||||
@@ -158,13 +179,11 @@ private:
|
||||
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
|
||||
const ShaderStageRuntime& pixel,
|
||||
bool pixel_active);
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
|
||||
uint32_t render_target_slot = UINT32_MAX,
|
||||
void ResolveRenderColorTarget(CommandBuffer& buffer, RenderColorInfo& target,
|
||||
uint32_t render_target_slice_offset, uint32_t render_target_slot,
|
||||
bool ignore_target_mask = false, bool exact_format = false);
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer& buffer,
|
||||
RenderDepthInfo& target);
|
||||
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, CommandBuffer& buffer,
|
||||
void ResolveRenderDepthTarget(CommandBuffer& buffer, RenderDepthInfo& target);
|
||||
[[nodiscard]] bool PrepareDrawRenderState(CommandBuffer& buffer,
|
||||
const DrawCallInfo& draw,
|
||||
uint32_t render_target_slice_offset,
|
||||
bool log_setup_phases, DrawRenderState& state);
|
||||
@@ -174,8 +193,9 @@ private:
|
||||
bool primitive_restart_enable, bool log_pipeline_phase,
|
||||
bool set_bind_debug, bool set_auto_debug);
|
||||
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
|
||||
uint32_t color_count, RenderDepthInfo& depth);
|
||||
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer,
|
||||
uint32_t color_count, RenderDepthInfo& depth,
|
||||
const std::optional<PreparedBindings>& pixel = std::nullopt);
|
||||
[[nodiscard]] bool ResolveColorTargets(CommandBuffer& buffer,
|
||||
uint32_t render_target_slice_offset);
|
||||
void BindImage(ImageId id, bool storage);
|
||||
void BindRenderTarget(ImageId id);
|
||||
@@ -183,6 +203,9 @@ private:
|
||||
void ResetBindings();
|
||||
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer);
|
||||
[[nodiscard]] bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input,
|
||||
CommandBuffer& command, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode);
|
||||
|
||||
RenderContext& m_context;
|
||||
std::vector<ImageId> m_bound_images;
|
||||
@@ -190,13 +213,12 @@ private:
|
||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
std::vector<uint32_t> m_image_occurrences;
|
||||
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
|
||||
m_push_constants {};
|
||||
|
||||
friend class CommandProcessor;
|
||||
friend struct RenderExecutorTestAccess;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
[[nodiscard]] bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode,
|
||||
ShaderBufferResource& descriptor,
|
||||
uint32_t& packed_clear, uint64_t& size);
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -37,16 +39,25 @@ namespace Libs::Graphics {
|
||||
static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
|
||||
const uint64_t records = descriptor.NumRecords();
|
||||
const uint64_t stride = descriptor.Stride();
|
||||
if (stride != 0 && records > UINT64_MAX / stride) {
|
||||
EXIT("compute buffer descriptor footprint overflow\n");
|
||||
}
|
||||
return stride == 0 ? records : records * stride;
|
||||
}
|
||||
|
||||
static bool FillSourcesDisjoint(std::span<const ShaderRecompiler::IR::DescriptorValue> sources,
|
||||
GuestRange destination, uint32_t output_buffer = UINT32_MAX) {
|
||||
for (uint32_t i = 0; i < sources.size(); ++i) {
|
||||
if (i == output_buffer) continue;
|
||||
const auto source = DecodeNativeDescriptor<ShaderBufferResource>(sources[i]);
|
||||
const auto bytes = BufferDescriptorSize(source);
|
||||
if (source.Base48() < destination.End() && destination.address < source.Base48() + bytes)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
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");
|
||||
}
|
||||
@@ -76,82 +87,115 @@ bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& in
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
bool ResolveComputeBufferFill(const ShaderComputeInputInfo& input, uint32_t group_x,
|
||||
uint32_t group_y, uint32_t group_z, uint32_t mode,
|
||||
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
|
||||
uint64_t& resolved_size) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
|
||||
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
|
||||
!resources.images.empty() || !resources.samplers.empty()) {
|
||||
const auto& resources = input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
if (fill.kind != ShaderRecompiler::IR::UniformFillKind::Buffer) {
|
||||
return false;
|
||||
}
|
||||
const auto& resource = program.info.buffers.front();
|
||||
const auto& raw = resources.buffers.front();
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(raw);
|
||||
if (!resource.formatted || !resource.written || resource.read || resource.atomic ||
|
||||
resource.scalar || resource.max_byte_extent != 16 || descriptor.Stride() != 16 ||
|
||||
descriptor.Format() != Prospero::BufferFormat::k32_32_32_32UInt ||
|
||||
const auto element_size = fill.words * sizeof(uint32_t);
|
||||
const auto descriptor =
|
||||
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[fill.resource]);
|
||||
constexpr std::array formats {
|
||||
Prospero::BufferFormat::k32UInt, Prospero::BufferFormat::k32_32UInt,
|
||||
Prospero::BufferFormat::k32_32_32UInt, Prospero::BufferFormat::k32_32_32_32UInt};
|
||||
if (descriptor.Stride() != element_size || descriptor.Format() != formats[fill.words - 1] ||
|
||||
descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() ||
|
||||
resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 ||
|
||||
program.user_data_base != 0 || resources.user_data.size() != 8) {
|
||||
descriptor.Base48() == 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < raw.dword_count; i++) {
|
||||
if (raw.dwords[i] != resources.user_data[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const uint32_t clear = resources.user_data[4];
|
||||
if (resources.user_data[5] != clear || resources.user_data[6] != clear ||
|
||||
resources.user_data[7] != clear) {
|
||||
if (input.threads_num[0] == 0 || input.threads_num[0] != fill.group_stride[0] ||
|
||||
input.threads_num[1] != 1 || input.threads_num[2] != 1 || group_x == 0 || group_y != 1 ||
|
||||
group_z != 1 || mode != (input.dispatch_thread_dimensions ? 0x61u : 0x41u)) {
|
||||
return false;
|
||||
}
|
||||
const bool full_dispatch =
|
||||
input.dispatch_thread_dimensions && input.threads_num[0] == 64 &&
|
||||
input.threads_num[1] == 1 && input.threads_num[2] == 1 && group_x != 0 && group_y == 1 &&
|
||||
group_z == 1 && input.dispatch_threads_num[0] == group_x &&
|
||||
input.dispatch_threads_num[1] == 1 && input.dispatch_threads_num[2] == 1 &&
|
||||
input.group_id[0] && !input.group_id[1] && !input.group_id[2] &&
|
||||
input.thread_ids_num == 1 && input.wave_size == 64 && !input.tg_size_en && mode == 0x61u &&
|
||||
group_x % input.threads_num[0] == 0 && descriptor.NumRecords() == group_x;
|
||||
const auto size = BufferDescriptorSize(descriptor);
|
||||
if (!full_dispatch || size == 0) {
|
||||
const uint64_t invocations = input.dispatch_thread_dimensions
|
||||
? group_x
|
||||
: static_cast<uint64_t>(group_x) * input.threads_num[0];
|
||||
const auto size = BufferDescriptorSize(descriptor);
|
||||
if (invocations != descriptor.NumRecords() || size == 0 || size > UINT32_MAX ||
|
||||
(input.dispatch_thread_dimensions &&
|
||||
(group_x % input.threads_num[0] != 0 || input.dispatch_threads_num[0] != group_x ||
|
||||
input.dispatch_threads_num[1] != 1 || input.dispatch_threads_num[2] != 1))) {
|
||||
return false;
|
||||
}
|
||||
if (!FillSourcesDisjoint(resources.buffers, {descriptor.Base48(), size}, fill.resource))
|
||||
return false;
|
||||
resolved_descriptor = descriptor;
|
||||
resolved_clear = clear;
|
||||
resolved_clear = fill.value;
|
||||
resolved_size = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, CommandBuffer& command,
|
||||
uint32_t group_x, uint32_t group_y, uint32_t group_z,
|
||||
uint32_t mode) {
|
||||
bool RenderExecutor::TryConsumeComputeImageClear(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& resources = input.stage.resources;
|
||||
const auto& fill = resources.uniform_fill;
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
if (fill.kind == ShaderRecompiler::IR::UniformFillKind::Image) {
|
||||
if (mode != 0x41u || input.dispatch_thread_dimensions || fill.value > 255 ||
|
||||
input.threads_num[2] != 1)
|
||||
return false;
|
||||
const auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(resources.images[0]);
|
||||
const auto& resource = program.info.images[0];
|
||||
if (descriptor.IsNull() || descriptor.Format() != Prospero::BufferFormat::k8UInt ||
|
||||
descriptor.Type() != Prospero::ImageType::kColor2DArray || descriptor.MetaCompress() ||
|
||||
descriptor.WriteCompress() || descriptor.BaseLevel() > descriptor.LastLevel() ||
|
||||
descriptor.BaseLevel() > descriptor.MaxMip() ||
|
||||
descriptor.BaseArray5() > descriptor.Depth() || descriptor.DstSelX() != 4)
|
||||
return false;
|
||||
const std::array extents {
|
||||
std::max(1u, (descriptor.Width5() + 1u) >> descriptor.BaseLevel()),
|
||||
std::max(1u, (descriptor.Height5() + 1u) >> descriptor.BaseLevel()),
|
||||
descriptor.Depth() - descriptor.BaseArray5() + 1u};
|
||||
const std::array groups {group_x, group_y, group_z};
|
||||
for (uint32_t axis = 0; axis < 3; ++axis) {
|
||||
const uint64_t threads = input.threads_num[axis];
|
||||
// Guest image writes outside the descriptor dimensions are discarded. Only the
|
||||
// final workgroup may extend beyond the selected image view.
|
||||
if (threads == 0 || threads != fill.group_stride[axis] ||
|
||||
groups[axis] != (extents[axis] + threads - 1) / threads ||
|
||||
groups[axis] * threads > UINT32_MAX) return false;
|
||||
}
|
||||
const auto binding = ResolveTexture(resource, resources.images[0]);
|
||||
const auto& destination = binding.desc.info.data;
|
||||
if (!FillSourcesDisjoint(resources.buffers, destination)) return false;
|
||||
std::scoped_lock lock {cache.m_lock};
|
||||
const auto& image = cache.GetImage(binding.image_id);
|
||||
const auto& view = binding.desc.view_info;
|
||||
if (image.backing.format != vk::Format::eD32SfloatS8Uint || image.info.samples != 1 ||
|
||||
image.info.stencil != destination || view.base_level >= image.backing.mip_levels ||
|
||||
view.base_layer >= image.backing.layers || view.layer_count != extents[2] ||
|
||||
view.layer_count > image.backing.layers - view.base_layer ||
|
||||
std::max(1u, image.info.extent.width >> view.base_level) != extents[0] ||
|
||||
std::max(1u, image.info.extent.height >> view.base_level) != extents[1]) return false;
|
||||
const vk::ImageSubresourceRange range {vk::ImageAspectFlagBits::eStencil, view.base_level,
|
||||
1, view.base_layer, view.layer_count};
|
||||
vk::ClearValue clear {};
|
||||
clear.depthStencil = vk::ClearDepthStencilValue {0.0f, fill.value};
|
||||
cache.ClearImage(command, binding.image_id, range, clear);
|
||||
return true;
|
||||
}
|
||||
ShaderBufferResource descriptor;
|
||||
uint32_t packed_clear = 0;
|
||||
uint64_t size = 0;
|
||||
if (!ResolveComputeImageClear(input, group_x, group_y, group_z, mode, descriptor, packed_clear,
|
||||
if (!ResolveComputeBufferFill(input, group_x, group_y, group_z, mode, descriptor, packed_clear,
|
||||
size)) {
|
||||
return false;
|
||||
}
|
||||
auto& cache = command.GetContext().GetTextureCache();
|
||||
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
|
||||
// Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run
|
||||
// before the render target is bound. TryConsumeDccFill either consumes registered state
|
||||
// or retains a PendingDcc fill while allowing the dispatch to run.
|
||||
const bool registered_metadata =
|
||||
cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear);
|
||||
// Track deferred DCC state while the original dispatch writes the metadata allocation.
|
||||
cache.TrackDccFill(descriptor.Base48(), size, packed_clear);
|
||||
static std::atomic<uint32_t> logged_metadata_clears {0};
|
||||
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
|
||||
LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64
|
||||
LOGF("GraphicsRenderDispatchDirect: metadata fill shader=0x%016" PRIx64
|
||||
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
|
||||
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
|
||||
descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
if (registered_metadata) {
|
||||
return true;
|
||||
input.stage.program->shader_hash, descriptor.Base48(), size, packed_clear);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -224,7 +268,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const bool large_workgroup =
|
||||
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
const auto& resources = input_info.stage.resources;
|
||||
if (TryConsumeComputeMetaClear(input_info, buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -236,8 +280,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
}
|
||||
const auto sampled_images = std::count_if(
|
||||
program.info.images.begin(), program.info.images.end(), [](const auto& image) {
|
||||
return image.kind == ShaderRecompiler::IR::ResourceKind::Image ||
|
||||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
|
||||
return image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled;
|
||||
});
|
||||
const bool has_sampler = !program.info.samplers.empty();
|
||||
static std::atomic<uint32_t> dispatch_log_count {0};
|
||||
@@ -250,7 +293,10 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
thread_group_z, mode, input_info.threads_num[0], input_info.threads_num[1],
|
||||
input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(),
|
||||
sampled_images, program.info.images.size() - sampled_images,
|
||||
program.info.samplers.size(), program.bindings.push_constant_size);
|
||||
program.info.samplers.size(),
|
||||
program.bindings.UsesPushData()
|
||||
? static_cast<uint32_t>(sizeof(ShaderRecompiler::IR::PushData))
|
||||
: 0u);
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
const auto r = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
@@ -265,8 +311,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
LOGF(" CS texture[%u]: source=%u usage=%s sampled=%s addr=0x%010" PRIx64
|
||||
" type=%u fmt=%u extent=%ux%u depth=%u levels=%u tile=%u\n",
|
||||
i, image.source, image.written ? "read-write" : "read-only",
|
||||
(image.kind == ShaderRecompiler::IR::ResourceKind::Image ||
|
||||
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint)
|
||||
image.resource_class == ShaderRecompiler::IR::ImageResourceClass::Sampled
|
||||
? "true"
|
||||
: "false",
|
||||
r.Base40(), static_cast<uint32_t>(r.Type()), static_cast<uint32_t>(r.Format()),
|
||||
@@ -347,8 +392,8 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
std::any_of(program.info.images.begin(), program.info.images.end(),
|
||||
[](const auto& image) {
|
||||
return image.written &&
|
||||
(image.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
image.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint);
|
||||
image.resource_class ==
|
||||
ShaderRecompiler::IR::ImageResourceClass::Storage;
|
||||
}) ||
|
||||
has_storage_writes;
|
||||
if (has_storage_writes) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -7,8 +7,9 @@ namespace Libs::Graphics {
|
||||
|
||||
struct ShaderVertexInputInfo;
|
||||
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
|
||||
const ShaderVertexInputInfo& vs_input_info);
|
||||
[[nodiscard]] uint32_t ResolveInstanceOffset(const ShaderVertexInputInfo& vs_input_info);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -13,10 +13,8 @@
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace Libs::Graphics::Sync {
|
||||
|
||||
@@ -55,70 +53,9 @@ uint64_t ReadReferenceClock() {
|
||||
return value;
|
||||
}
|
||||
|
||||
enum class EndOfPipeCompletion { None, Interrupt, Flip, FlipAndInterrupt };
|
||||
|
||||
struct EndOfPipeSignal {
|
||||
CommandBuffer* buffer = nullptr;
|
||||
uint64_t submit_id = 0;
|
||||
CommandBufferDebugOp debug_operation = CommandBufferDebugOp::Unknown;
|
||||
std::array<uint32_t, 4> debug_args = {};
|
||||
uint64_t debug_data = 0;
|
||||
std::optional<uint64_t> destination;
|
||||
EndOfPipeCompletion completion = EndOfPipeCompletion::None;
|
||||
uint64_t completion_data = 0;
|
||||
int interrupt_event_id = 0;
|
||||
};
|
||||
|
||||
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
|
||||
enum class EndOfPipeWriteAction { Write, WriteBack, Interrupt, InterruptWriteBack };
|
||||
|
||||
static void ValidateEndOfPipeSignal(const EndOfPipeSignal& signal) {
|
||||
if (signal.destination.has_value()) {
|
||||
EXIT_IF(*signal.destination == 0);
|
||||
}
|
||||
EXIT_IF(signal.buffer == nullptr);
|
||||
(void)signal.buffer->Handle();
|
||||
}
|
||||
|
||||
static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
|
||||
ValidateEndOfPipeSignal(signal);
|
||||
signal.buffer->SetDebugInfo(static_cast<uint32_t>(signal.debug_operation), signal.submit_id,
|
||||
signal.debug_args[0], signal.debug_args[1], signal.debug_args[2],
|
||||
signal.debug_args[3], signal.debug_data);
|
||||
|
||||
auto& renderer = signal.buffer->GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
if (signal.completion != EndOfPipeCompletion::None) {
|
||||
EXIT_IF(!scheduler.Active() || signal.buffer != &scheduler.Current());
|
||||
}
|
||||
switch (signal.completion) {
|
||||
case EndOfPipeCompletion::None: return;
|
||||
case EndOfPipeCompletion::Interrupt: {
|
||||
const auto context_id = static_cast<uint32_t>(signal.completion_data);
|
||||
const auto event_id = signal.interrupt_event_id;
|
||||
scheduler.DeferPriorityOperation([&renderer, event_id, context_id] {
|
||||
renderer.TriggerInterrupt(event_id, context_id);
|
||||
});
|
||||
return;
|
||||
}
|
||||
case EndOfPipeCompletion::Flip: {
|
||||
const auto request_id = signal.completion_data;
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
return;
|
||||
}
|
||||
case EndOfPipeCompletion::FlipAndInterrupt: {
|
||||
const auto request_id = signal.completion_data;
|
||||
const auto event_id = signal.interrupt_event_id;
|
||||
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
|
||||
renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CommandBufferDebugOp DebugOperation(EndOfPipeWriteAction action) {
|
||||
switch (action) {
|
||||
case EndOfPipeWriteAction::Write: return CommandBufferDebugOp::EopWrite;
|
||||
@@ -139,24 +76,20 @@ static void RecordEndOfPipeWrite(uint64_t submit_id, CommandBuffer& buffer, uint
|
||||
uint64_t value, EndOfPipeWriteSize size,
|
||||
EndOfPipeWriteAction action, int interrupt_event_id = 0,
|
||||
uint32_t context_id = 0) {
|
||||
EXIT_IF(destination == 0);
|
||||
(void)buffer.Handle();
|
||||
|
||||
const auto width = static_cast<uint32_t>(size);
|
||||
const auto value_low = static_cast<uint32_t>(value);
|
||||
const auto value_high = static_cast<uint32_t>(value >> 32u);
|
||||
const bool interrupt = TriggersInterrupt(action);
|
||||
|
||||
EndOfPipeSignal signal {
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = DebugOperation(action),
|
||||
.debug_args = interrupt ? std::array {width, context_id, value_low, value_high}
|
||||
: std::array {width, value_low, value_high, 0u},
|
||||
.debug_data = destination,
|
||||
.destination = destination,
|
||||
.completion = interrupt ? EndOfPipeCompletion::Interrupt : EndOfPipeCompletion::None,
|
||||
.completion_data = interrupt ? context_id : value,
|
||||
.interrupt_event_id = interrupt_event_id,
|
||||
};
|
||||
RecordEndOfPipeSignal(signal);
|
||||
const auto operation = static_cast<uint32_t>(DebugOperation(action));
|
||||
if (TriggersInterrupt(action)) {
|
||||
buffer.SetDebugInfo(operation, submit_id, width, context_id, value_low, value_high,
|
||||
destination);
|
||||
TriggerEopEventAtEndOfPipe(buffer, interrupt_event_id, context_id);
|
||||
} else {
|
||||
buffer.SetDebugInfo(operation, submit_id, width, value_low, value_high, 0, destination);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
@@ -167,15 +100,10 @@ void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst
|
||||
|
||||
void WriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t dw_offset, uint32_t dw_num) {
|
||||
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
|
||||
RecordEndOfPipeSignal({
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = CommandBufferDebugOp::EopWrite,
|
||||
.debug_args = {dw_offset, dw_num, 0, 0},
|
||||
.debug_data = destination,
|
||||
.destination = destination,
|
||||
});
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWrite), submit_id,
|
||||
dw_offset, dw_num, 0, 0, reinterpret_cast<uint64_t>(dst_gpu_addr));
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipe64(uint64_t submit_id, CommandBuffer& buffer, uint64_t* dst_gpu_addr,
|
||||
@@ -273,54 +201,53 @@ void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuf
|
||||
int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id,
|
||||
int event_id) {
|
||||
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
|
||||
RecordEndOfPipeSignal({
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = CommandBufferDebugOp::EopWriteBackFlip,
|
||||
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value},
|
||||
.debug_data = static_cast<uint64_t>(flip_arg),
|
||||
.destination = destination,
|
||||
.completion = EndOfPipeCompletion::FlipAndInterrupt,
|
||||
.completion_data = request_id,
|
||||
.interrupt_event_id = event_id,
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopWriteBackFlip), submit_id,
|
||||
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
|
||||
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation([&renderer, event_id, request_id] {
|
||||
renderer.GetVideoOut().CompleteFlip(request_id);
|
||||
renderer.TriggerInterrupt(event_id, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
|
||||
uint32_t value, int handle, int index, int flip_mode,
|
||||
int64_t flip_arg, uint64_t request_id) {
|
||||
const auto destination = reinterpret_cast<uint64_t>(dst_gpu_addr);
|
||||
RecordEndOfPipeSignal({
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = CommandBufferDebugOp::EopFlip,
|
||||
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value},
|
||||
.debug_data = static_cast<uint64_t>(flip_arg),
|
||||
.destination = destination,
|
||||
.completion = EndOfPipeCompletion::Flip,
|
||||
.completion_data = request_id,
|
||||
});
|
||||
EXIT_IF(dst_gpu_addr == nullptr);
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopFlip), submit_id,
|
||||
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), value, static_cast<uint64_t>(flip_arg));
|
||||
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
}
|
||||
|
||||
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
|
||||
int flip_mode, int64_t flip_arg, uint64_t request_id) {
|
||||
RecordEndOfPipeSignal({
|
||||
.buffer = &buffer,
|
||||
.submit_id = submit_id,
|
||||
.debug_operation = CommandBufferDebugOp::EopOnlyFlip,
|
||||
.debug_args = {static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), 0},
|
||||
.debug_data = static_cast<uint64_t>(flip_arg),
|
||||
.completion = EndOfPipeCompletion::Flip,
|
||||
.completion_data = request_id,
|
||||
});
|
||||
(void)buffer.Handle();
|
||||
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::EopOnlyFlip), submit_id,
|
||||
static_cast<uint32_t>(handle), static_cast<uint32_t>(index),
|
||||
static_cast<uint32_t>(flip_mode), 0, static_cast<uint64_t>(flip_arg));
|
||||
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
scheduler.DeferPriorityOperation(
|
||||
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
|
||||
}
|
||||
|
||||
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, int event_id, uint32_t context_id) {
|
||||
ValidateEndOfPipeSignal({.buffer = &buffer});
|
||||
(void)buffer.Handle();
|
||||
auto& renderer = buffer.GetContext();
|
||||
auto& scheduler = renderer.GetCommandScheduler();
|
||||
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
|
||||
|
||||
+20
-110
@@ -18,45 +18,12 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
struct MemoryStats {
|
||||
std::atomic_uint64_t allocated[VK_MAX_MEMORY_TYPES] {};
|
||||
std::atomic_uint64_t count[VK_MAX_MEMORY_TYPES] {};
|
||||
};
|
||||
|
||||
MemoryStats g_memory_stats;
|
||||
|
||||
void TrackAllocationImpl(const VulkanMemory& memory) {
|
||||
g_memory_stats.allocated[memory.type] += memory.requirements.size;
|
||||
g_memory_stats.count[memory.type]++;
|
||||
}
|
||||
|
||||
void UntrackAllocationImpl(const VulkanMemory& memory) {
|
||||
EXIT_IF(g_memory_stats.allocated[memory.type] < memory.requirements.size);
|
||||
EXIT_IF(g_memory_stats.count[memory.type] == 0);
|
||||
g_memory_stats.allocated[memory.type] -= memory.requirements.size;
|
||||
g_memory_stats.count[memory.type]--;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void VulkanTrackAllocation(const VulkanMemory& memory) {
|
||||
TrackAllocationImpl(memory);
|
||||
}
|
||||
|
||||
void VulkanUntrackAllocation(const VulkanMemory& memory) {
|
||||
UntrackAllocationImpl(memory);
|
||||
}
|
||||
|
||||
bool GraphicContext::CreateAllocator() {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(instance == nullptr || physical_device == nullptr || device == nullptr ||
|
||||
@@ -79,7 +46,7 @@ bool GraphicContext::CreateAllocator() {
|
||||
|
||||
const auto result = static_cast<vk::Result>(vmaCreateAllocator(&info, &allocator));
|
||||
if (result != vk::Result::eSuccess) {
|
||||
LOGF("vmaCreateAllocator failed: %s\n", VulkanToString(result).c_str());
|
||||
LOGF("vmaCreateAllocator failed: %s\n", vk::to_string(result).c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -93,11 +60,6 @@ void GraphicContext::DestroyAllocator() {
|
||||
allocator = nullptr;
|
||||
}
|
||||
|
||||
uint64_t VulkanNextMemoryUniqueId() {
|
||||
static std::atomic_uint64_t sequence = 0;
|
||||
return ++sequence;
|
||||
}
|
||||
|
||||
void GraphicContext::LogMemoryBudget() const {
|
||||
if (allocator == nullptr || physical_device == nullptr) {
|
||||
return;
|
||||
@@ -166,96 +128,44 @@ uint64_t GraphicContext::GetTotalMemoryBudget() const {
|
||||
return std::max(local, available > system_reserve ? available - system_reserve : uint64_t {0});
|
||||
}
|
||||
|
||||
void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || buffer.buffer != nullptr ||
|
||||
buffer.memory.allocation != nullptr || size == 0);
|
||||
|
||||
vk::BufferCreateInfo buffer_info {};
|
||||
buffer_info.sType = vk::StructureType::eBufferCreateInfo;
|
||||
buffer_info.size = size;
|
||||
buffer_info.usage = buffer.usage;
|
||||
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
||||
|
||||
VmaAllocationCreateInfo alloc_info {};
|
||||
alloc_info.requiredFlags =
|
||||
static_cast<vk::MemoryPropertyFlags::MaskType>(buffer.memory.property);
|
||||
alloc_info.preferredFlags =
|
||||
static_cast<vk::MemoryPropertyFlags::MaskType>(buffer.memory.preferred_property);
|
||||
|
||||
vk::Buffer::CType native_buffer = VK_NULL_HANDLE;
|
||||
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
|
||||
allocator, static_cast<const vk::BufferCreateInfo::NativeType*>(buffer_info), &alloc_info,
|
||||
&native_buffer, &buffer.memory.allocation, &buffer.memory.allocation_info));
|
||||
buffer.buffer = native_buffer;
|
||||
if (result != vk::Result::eSuccess) {
|
||||
LogMemoryBudget();
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
||||
|
||||
device.getBufferMemoryRequirements(buffer.buffer, &buffer.memory.requirements);
|
||||
buffer.memory.type = buffer.memory.allocation_info.memoryType;
|
||||
buffer.memory.memory = buffer.memory.allocation_info.deviceMemory;
|
||||
buffer.memory.offset = buffer.memory.allocation_info.offset;
|
||||
buffer.memory.unique_id = VulkanNextMemoryUniqueId();
|
||||
buffer.buffer_size = size;
|
||||
VulkanTrackAllocation(buffer.memory);
|
||||
}
|
||||
|
||||
bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr);
|
||||
EXIT_IF(allocator == nullptr || image.image != nullptr || image.allocation != nullptr);
|
||||
|
||||
auto& memory = image.memory;
|
||||
VmaAllocationCreateInfo alloc_info {};
|
||||
alloc_info.requiredFlags = static_cast<vk::MemoryPropertyFlags::MaskType>(memory.property);
|
||||
alloc_info.preferredFlags =
|
||||
static_cast<vk::MemoryPropertyFlags::MaskType>(memory.preferred_property);
|
||||
alloc_info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
vk::Image::CType native_image = VK_NULL_HANDLE;
|
||||
const auto result = static_cast<vk::Result>(
|
||||
const auto result = static_cast<vk::Result>(
|
||||
vmaCreateImage(allocator, static_cast<const vk::ImageCreateInfo::NativeType*>(image_info),
|
||||
&alloc_info, &native_image, &memory.allocation, &memory.allocation_info));
|
||||
&alloc_info, &native_image, &image.allocation, nullptr));
|
||||
image.image = native_image;
|
||||
if (result != vk::Result::eSuccess) {
|
||||
LogMemoryBudget();
|
||||
return false;
|
||||
}
|
||||
|
||||
device.getImageMemoryRequirements(image.image, &memory.requirements);
|
||||
memory.type = memory.allocation_info.memoryType;
|
||||
memory.memory = memory.allocation_info.deviceMemory;
|
||||
memory.offset = memory.allocation_info.offset;
|
||||
memory.unique_id = VulkanNextMemoryUniqueId();
|
||||
VulkanTrackAllocation(memory);
|
||||
image.format = image_info.format;
|
||||
image.image_type = image_info.imageType;
|
||||
image.extent = image_info.extent;
|
||||
image.layers = image_info.arrayLayers;
|
||||
image.mip_levels = image_info.mipLevels;
|
||||
image.samples = static_cast<uint32_t>(image_info.samples);
|
||||
image.usage = image_info.usage;
|
||||
image.flags = image_info.flags;
|
||||
image.state = {.layout = image_info.initialLayout};
|
||||
image.subresource_states.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicContext::DeleteImage(VulkanImage& image) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || image.image == nullptr || image.memory.allocation == nullptr);
|
||||
EXIT_IF(allocator == nullptr || image.image == nullptr || image.allocation == nullptr);
|
||||
|
||||
auto& memory = image.memory;
|
||||
VulkanUntrackAllocation(memory);
|
||||
vmaDestroyImage(allocator, image.image, memory.allocation);
|
||||
image.image = nullptr;
|
||||
memory.memory = nullptr;
|
||||
memory.allocation = nullptr;
|
||||
memory.allocation_info = {};
|
||||
memory.offset = 0;
|
||||
}
|
||||
|
||||
void GraphicContext::MapMemory(VulkanMemory& memory, void*& data) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || memory.allocation == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(vmaMapMemory(allocator, memory.allocation,
|
||||
&data)) != vk::Result::eSuccess);
|
||||
}
|
||||
|
||||
void GraphicContext::UnmapMemory(VulkanMemory& memory) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(allocator == nullptr || memory.allocation == nullptr);
|
||||
vmaUnmapMemory(allocator, memory.allocation);
|
||||
vmaDestroyImage(allocator, image.image, image.allocation);
|
||||
image.image = nullptr;
|
||||
image.allocation = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_VMA_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_VMA_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
uint64_t VulkanNextMemoryUniqueId();
|
||||
void VulkanTrackAllocation(const VulkanMemory& memory);
|
||||
void VulkanUntrackAllocation(const VulkanMemory& memory);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_SRC_GRAPHICS_HOST_GPU_VMA_H_ */
|
||||
@@ -65,7 +65,6 @@ constexpr FormatMapping kFormatMappings[] = {
|
||||
{Prospero::BufferFormat::k5_6_5UNorm, vk::Format::eB5G6R5UnormPack16},
|
||||
{Prospero::BufferFormat::k5_5_5_1UNorm, vk::Format::eR5G5B5A1UnormPack16},
|
||||
{Prospero::BufferFormat::k4_4_4_4UNorm, vk::Format::eR4G4B4A4UnormPack16},
|
||||
{Prospero::BufferFormat::kFmask8_S4_F4, vk::Format::eR32Sfloat},
|
||||
{Prospero::BufferFormat::kBc1UNorm, vk::Format::eBc1RgbaUnormBlock},
|
||||
{Prospero::BufferFormat::kBc1Srgb, vk::Format::eBc1RgbaSrgbBlock},
|
||||
{Prospero::BufferFormat::kBc2UNorm, vk::Format::eBc2UnormBlock},
|
||||
@@ -96,22 +95,6 @@ constexpr auto kFormatLookup = MakeFormatLookup();
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string VulkanToString(vk::Result value) {
|
||||
return vk::to_string(value);
|
||||
}
|
||||
|
||||
std::string VulkanToString(vk::Format value) {
|
||||
return vk::to_string(value);
|
||||
}
|
||||
|
||||
std::string VulkanToString(vk::ImageLayout value) {
|
||||
return vk::to_string(value);
|
||||
}
|
||||
|
||||
std::string VulkanToString(vk::QueueFlags value) {
|
||||
return vk::to_string(value);
|
||||
}
|
||||
|
||||
vk::Format VulkanFormat(Prospero::BufferFormat guest_format) {
|
||||
const auto index = static_cast<size_t>(guest_format);
|
||||
return index < kFormatLookup.size() ? kFormatLookup[index] : vk::Format::eUndefined;
|
||||
@@ -119,7 +102,7 @@ vk::Format VulkanFormat(Prospero::BufferFormat guest_format) {
|
||||
|
||||
void RequireVulkanSuccess(vk::Result result, const char* operation) {
|
||||
if (result != vk::Result::eSuccess) {
|
||||
EXIT("%s failed: %s (%d)\n", operation, VulkanToString(result).c_str(),
|
||||
EXIT("%s failed: %s (%d)\n", operation, vk::to_string(result).c_str(),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,6 @@ namespace Libs::Graphics {
|
||||
|
||||
using VulkanMemoryBarrier = vk::MemoryBarrier;
|
||||
|
||||
std::string VulkanToString(vk::Result value);
|
||||
std::string VulkanToString(vk::Format value);
|
||||
std::string VulkanToString(vk::ImageLayout value);
|
||||
std::string VulkanToString(vk::QueueFlags value);
|
||||
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
|
||||
void RequireVulkanSuccess(vk::Result result, const char* operation);
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
union SDL_Event;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
|
||||
struct ImeVisualState {
|
||||
bool active;
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
void InitializeImeInput();
|
||||
void ShutdownImeInput();
|
||||
bool ProcessImeInput(const SDL_Event& event);
|
||||
ImeVisualState GetImeVisualState() noexcept;
|
||||
|
||||
class ImeOverlay final {
|
||||
public:
|
||||
explicit ImeOverlay(GraphicContext& graphics);
|
||||
~ImeOverlay();
|
||||
KYTY_CLASS_NO_COPY(ImeOverlay);
|
||||
|
||||
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
|
||||
void Record(vk::CommandBuffer command, vk::ImageView target);
|
||||
void ReleaseVulkan();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
CommandBuffer* producer = nullptr);
|
||||
[[nodiscard]] Frame* PrepareLastFrame();
|
||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||
[[nodiscard]] bool NeedsImeRefresh() const noexcept;
|
||||
[[nodiscard]] bool NeedsSystemOverlayRefresh() const noexcept;
|
||||
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
||||
void Present(Frame& frame, bool reuse = false);
|
||||
void Discard(Frame& frame);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -25,13 +27,12 @@ namespace Libs::Graphics {
|
||||
enum class RenderDocState : uint32_t {
|
||||
Idle,
|
||||
Requested,
|
||||
Starting,
|
||||
Capturing,
|
||||
};
|
||||
|
||||
static RENDERDOC_API_1_6_0* g_api = nullptr;
|
||||
static std::atomic<RenderDocState> g_state = RenderDocState::Idle;
|
||||
static std::atomic_uint32_t g_captured_flips = 0;
|
||||
static uint32_t g_captured_flips = 0;
|
||||
static std::atomic_bool g_unavailable_log = false;
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -132,21 +133,7 @@ void RenderDocRequestCapture() {
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderDocCaptureRequested() {
|
||||
return g_state.load(std::memory_order_acquire) == RenderDocState::Requested;
|
||||
}
|
||||
|
||||
bool RenderDocCaptureInProgress() {
|
||||
return g_state.load(std::memory_order_acquire) == RenderDocState::Capturing;
|
||||
}
|
||||
|
||||
void RenderDocStartCapture() {
|
||||
RenderDocState expected = RenderDocState::Requested;
|
||||
if (g_api == nullptr || !g_state.compare_exchange_strong(expected, RenderDocState::Starting,
|
||||
std::memory_order_acq_rel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static void StartCapture() {
|
||||
if (g_api->IsFrameCapturing() != 0) {
|
||||
g_state.store(RenderDocState::Idle, std::memory_order_release);
|
||||
LOGF("RenderDoc: capture request ignored because a capture is already active\n");
|
||||
@@ -164,29 +151,32 @@ void RenderDocStartCapture() {
|
||||
LOGF("RenderDoc: capture failed to start\n");
|
||||
return;
|
||||
}
|
||||
g_captured_flips.store(0, std::memory_order_release);
|
||||
g_captured_flips = 0;
|
||||
g_state.store(RenderDocState::Capturing, std::memory_order_release);
|
||||
LOGF("RenderDoc: capture started\n");
|
||||
}
|
||||
|
||||
void RenderDocEndCapture() {
|
||||
if (g_api == nullptr || !RenderDocCaptureInProgress()) {
|
||||
void RenderDocOnGuestFlip(RenderContext& renderer) {
|
||||
const auto state = g_state.load(std::memory_order_acquire);
|
||||
if (g_api == nullptr || state == RenderDocState::Idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ok = g_api->EndFrameCapture(nullptr, nullptr);
|
||||
g_state.store(RenderDocState::Idle, std::memory_order_release);
|
||||
LOGF(ok != 0 ? "RenderDoc: capture finished\n" : "RenderDoc: capture failed\n");
|
||||
}
|
||||
|
||||
void RenderDocOnGuestFlip() {
|
||||
if (!RenderDocCaptureInProgress()) {
|
||||
return;
|
||||
if (state == RenderDocState::Capturing) {
|
||||
LOGF("RenderDoc: captured guest flip %u/2\n", ++g_captured_flips);
|
||||
if (g_captured_flips < 2) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto flip = g_captured_flips.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
LOGF("RenderDoc: captured guest flip %u/2\n", flip);
|
||||
if (flip >= 2) {
|
||||
RenderDocEndCapture();
|
||||
|
||||
// Capture boundaries follow presentation and exclude concurrent queue access.
|
||||
Common::LockGuard render_lock(renderer.GetMutex());
|
||||
Common::LockGuard queue_lock(renderer.GetGraphics().queue_mutex);
|
||||
if (state == RenderDocState::Requested) {
|
||||
StartCapture();
|
||||
} else {
|
||||
const auto ok = g_api->EndFrameCapture(nullptr, nullptr);
|
||||
g_state.store(RenderDocState::Idle, std::memory_order_release);
|
||||
LOGF(ok != 0 ? "RenderDoc: capture finished\n" : "RenderDoc: capture failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderContext;
|
||||
|
||||
void RenderDocInit();
|
||||
void RenderDocRequestCapture();
|
||||
void RenderDocStartCapture();
|
||||
void RenderDocEndCapture();
|
||||
void RenderDocOnGuestFlip();
|
||||
|
||||
[[nodiscard]] bool RenderDocCaptureRequested();
|
||||
[[nodiscard]] bool RenderDocCaptureInProgress();
|
||||
// Called by the presentation thread after releasing video-out locks.
|
||||
void RenderDocOnGuestFlip(RenderContext& renderer);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
+164
-69
@@ -1,4 +1,4 @@
|
||||
#include "graphics/presentation/imeOverlay.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
|
||||
#include "SDL.h"
|
||||
#include "common/assert.h"
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/ime.h"
|
||||
#include "libs/imeDialog.h"
|
||||
|
||||
@@ -29,6 +30,7 @@ namespace {
|
||||
|
||||
namespace CoreIme = Libs::Ime;
|
||||
namespace DialogIme = Libs::Dialog::ImeDialog;
|
||||
namespace ErrorDialog = Libs::Dialog::ErrorDialog;
|
||||
|
||||
namespace Ime {
|
||||
|
||||
@@ -101,6 +103,34 @@ bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
|
||||
|
||||
} // namespace Ime
|
||||
|
||||
enum class OverlayKind : uint8_t { None, Ime, Error };
|
||||
|
||||
struct OverlaySession {
|
||||
OverlayKind kind = OverlayKind::None;
|
||||
uint64_t generation = 0;
|
||||
|
||||
bool operator==(const OverlaySession&) const = default;
|
||||
};
|
||||
|
||||
struct OverlaySnapshot {
|
||||
OverlaySession session;
|
||||
Ime::HostSnapshot ime;
|
||||
ErrorDialog::HostSnapshot error;
|
||||
};
|
||||
|
||||
bool GetOverlaySnapshot(OverlaySnapshot* snapshot) {
|
||||
if (ErrorDialog::GetHostSnapshot(&snapshot->error)) {
|
||||
snapshot->session = {OverlayKind::Error, snapshot->error.generation};
|
||||
return true;
|
||||
}
|
||||
if (Ime::GetHostSnapshot(&snapshot->ime)) {
|
||||
snapshot->session = {OverlayKind::Ime, snapshot->ime.generation};
|
||||
return true;
|
||||
}
|
||||
snapshot->session = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr size_t INPUT_QUEUE_CAPACITY = 128;
|
||||
|
||||
enum class InputKind : uint8_t {
|
||||
@@ -113,19 +143,20 @@ enum class InputKind : uint8_t {
|
||||
};
|
||||
|
||||
struct InputEvent {
|
||||
InputKind kind;
|
||||
uint64_t generation;
|
||||
int id;
|
||||
float x;
|
||||
float y;
|
||||
InputKind kind;
|
||||
OverlaySession session;
|
||||
int id;
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct VisibilityUpdate {
|
||||
uint64_t generation;
|
||||
bool visible;
|
||||
bool capture_controller;
|
||||
bool capture_keyboard;
|
||||
bool multiline;
|
||||
OverlaySession session;
|
||||
bool visible;
|
||||
bool capture_controller;
|
||||
bool capture_keyboard;
|
||||
bool text_input;
|
||||
bool multiline;
|
||||
};
|
||||
|
||||
std::atomic<Uint32> g_visibility_event {static_cast<Uint32>(-1)};
|
||||
@@ -137,15 +168,14 @@ size_t g_missing_visibility_wakeups = 0;
|
||||
bool g_input_reset_requested = false;
|
||||
uint16_t g_last_external_keycode = 0;
|
||||
uint32_t g_last_external_status = 0;
|
||||
uint64_t g_input_generation = 0;
|
||||
OverlaySession g_input_session;
|
||||
bool g_input_active = false;
|
||||
bool g_input_controller = false;
|
||||
bool g_input_keyboard = false;
|
||||
bool g_input_multiline = false;
|
||||
bool g_input_lifecycle_active = false;
|
||||
bool g_active = false;
|
||||
bool g_controller_captured = false;
|
||||
uint64_t g_generation = 0;
|
||||
OverlaySession g_session;
|
||||
|
||||
void ClearInputEvents() {
|
||||
std::scoped_lock lock(g_input_mutex);
|
||||
@@ -159,7 +189,7 @@ void QueueInput(InputEvent event) {
|
||||
event.kind == InputKind::Axis || event.kind == InputKind::MousePosition;
|
||||
if (replaceable && !g_input_events.empty()) {
|
||||
auto& last = g_input_events.back();
|
||||
if (last.generation == event.generation && last.kind == event.kind && last.id == event.id) {
|
||||
if (last.session == event.session && last.kind == event.kind && last.id == event.id) {
|
||||
last = event;
|
||||
return;
|
||||
}
|
||||
@@ -192,34 +222,36 @@ void RefreshVisibility() {
|
||||
if (!g_input_lifecycle_active) {
|
||||
return;
|
||||
}
|
||||
Ime::HostSnapshot snapshot;
|
||||
const bool visible = Ime::GetHostSnapshot(&snapshot);
|
||||
const uint64_t generation = visible ? snapshot.generation : g_generation;
|
||||
if (visible == g_active && (!visible || generation == g_generation)) {
|
||||
OverlaySnapshot snapshot;
|
||||
const bool visible = GetOverlaySnapshot(&snapshot);
|
||||
if (snapshot.session == g_session) {
|
||||
return;
|
||||
}
|
||||
g_generation = generation;
|
||||
g_session = snapshot.session;
|
||||
bool capture_controller = false;
|
||||
bool capture_keyboard = false;
|
||||
bool text_input = false;
|
||||
bool multiline = false;
|
||||
if (visible) {
|
||||
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
|
||||
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
|
||||
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
|
||||
if (snapshot.session.kind == OverlayKind::Error) {
|
||||
capture_controller = true;
|
||||
capture_keyboard = true;
|
||||
} else if (snapshot.session.kind == OverlayKind::Ime) {
|
||||
capture_controller = (snapshot.ime.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
|
||||
capture_keyboard = (snapshot.ime.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
|
||||
text_input = capture_keyboard;
|
||||
multiline = (snapshot.ime.option & Ime::OPTION_MULTILINE) != 0;
|
||||
}
|
||||
const bool was_controller = std::exchange(g_controller_captured, capture_controller);
|
||||
if (capture_controller || was_controller) {
|
||||
Controller::ControllerResetInputState();
|
||||
Controller::ResetInputState();
|
||||
}
|
||||
g_active = visible;
|
||||
|
||||
const Uint32 type = g_visibility_event.load(std::memory_order_acquire);
|
||||
if (type != static_cast<Uint32>(-1)) {
|
||||
SDL_Event event {};
|
||||
event.type = type;
|
||||
std::scoped_lock input_lock(g_input_mutex);
|
||||
g_visibility_updates.push_back(
|
||||
{generation, visible, capture_controller, capture_keyboard, multiline});
|
||||
g_visibility_updates.push_back({snapshot.session, visible, capture_controller,
|
||||
capture_keyboard, text_input, multiline});
|
||||
if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) {
|
||||
g_missing_visibility_wakeups++;
|
||||
}
|
||||
@@ -383,7 +415,7 @@ void CheckVulkanResult(VkResult result) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeImeInput() {
|
||||
void InitializeSystemOverlayInput() {
|
||||
const Uint32 type = SDL_RegisterEvents(1);
|
||||
EXIT_IF(type == static_cast<Uint32>(-1));
|
||||
{
|
||||
@@ -393,18 +425,20 @@ void InitializeImeInput() {
|
||||
}
|
||||
CoreIme::SetVisibilityCallback(OnCoreVisibilityChanged);
|
||||
DialogIme::SetVisibilityCallback(OnDialogVisibilityChanged);
|
||||
ErrorDialog::SetVisibilityCallback(RefreshVisibility);
|
||||
RefreshVisibility();
|
||||
}
|
||||
|
||||
void ShutdownImeInput() {
|
||||
void ShutdownSystemOverlayInput() {
|
||||
CoreIme::SetVisibilityCallback(nullptr);
|
||||
DialogIme::SetVisibilityCallback(nullptr);
|
||||
ErrorDialog::SetVisibilityCallback(nullptr);
|
||||
{
|
||||
std::scoped_lock lock(g_visibility_mutex);
|
||||
g_input_lifecycle_active = false;
|
||||
g_active = false;
|
||||
g_session = {};
|
||||
if (std::exchange(g_controller_captured, false)) {
|
||||
Controller::ControllerResetInputState();
|
||||
Controller::ResetInputState();
|
||||
}
|
||||
g_visibility_event.store(static_cast<Uint32>(-1), std::memory_order_release);
|
||||
}
|
||||
@@ -415,6 +449,7 @@ void ShutdownImeInput() {
|
||||
g_missing_visibility_wakeups = 0;
|
||||
}
|
||||
g_input_active = false;
|
||||
g_input_session = {};
|
||||
g_input_controller = false;
|
||||
g_input_keyboard = false;
|
||||
g_input_multiline = false;
|
||||
@@ -423,13 +458,15 @@ void ShutdownImeInput() {
|
||||
}
|
||||
}
|
||||
|
||||
ImeVisualState GetImeVisualState() noexcept {
|
||||
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
|
||||
const auto core = CoreIme::GetVisualState();
|
||||
const auto dialog = DialogIme::GetVisualState();
|
||||
return {core.active || dialog.active, core.revision + dialog.revision};
|
||||
const auto error = ErrorDialog::GetVisualState();
|
||||
return {core.active || dialog.active || error.active,
|
||||
core.revision + dialog.revision + error.revision};
|
||||
}
|
||||
|
||||
bool ProcessImeInput(const SDL_Event& event) {
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
RetryVisibilityWakeup();
|
||||
if (event.type == g_visibility_event.load(std::memory_order_acquire)) {
|
||||
VisibilityUpdate update {};
|
||||
@@ -444,12 +481,12 @@ bool ProcessImeInput(const SDL_Event& event) {
|
||||
ClearInputEvents();
|
||||
g_last_external_keycode = 0;
|
||||
g_last_external_status = 0;
|
||||
g_input_generation = update.generation;
|
||||
g_input_session = update.session;
|
||||
g_input_active = update.visible;
|
||||
g_input_controller = update.capture_controller;
|
||||
g_input_keyboard = update.capture_keyboard;
|
||||
g_input_multiline = update.multiline;
|
||||
if (g_input_keyboard) {
|
||||
if (update.text_input) {
|
||||
SDL_StartTextInput();
|
||||
} else if (SDL_IsTextInputActive() == SDL_TRUE) {
|
||||
SDL_StopTextInput();
|
||||
@@ -458,7 +495,7 @@ bool ProcessImeInput(const SDL_Event& event) {
|
||||
}
|
||||
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
if (g_input_active && g_input_controller) {
|
||||
QueueInput({InputKind::ResetController, g_input_generation, 0, 0.0f, 0.0f});
|
||||
QueueInput({InputKind::ResetController, g_input_session, 0, 0.0f, 0.0f});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -466,7 +503,8 @@ bool ProcessImeInput(const SDL_Event& event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t generation = g_input_generation;
|
||||
const auto session = g_input_session;
|
||||
const uint64_t generation = session.generation;
|
||||
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN ||
|
||||
event.type == SDL_CONTROLLERBUTTONUP ||
|
||||
event.type == SDL_CONTROLLERAXISMOTION;
|
||||
@@ -478,6 +516,13 @@ bool ProcessImeInput(const SDL_Event& event) {
|
||||
if (keyboard_event && !g_input_keyboard) {
|
||||
return false;
|
||||
}
|
||||
if (keyboard_event && session.kind == OverlayKind::Error) {
|
||||
if (event.type == SDL_KEYDOWN && event.key.repeat == 0 &&
|
||||
(event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_KP_ENTER)) {
|
||||
ErrorDialog::HostAccept(generation);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_TEXTINPUT: {
|
||||
const auto text = Utf8ToUtf16(event.text.text);
|
||||
@@ -524,31 +569,33 @@ bool ProcessImeInput(const SDL_Event& event) {
|
||||
case SDL_KEYUP: return true;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
QueueInput({InputKind::Button, generation, event.cbutton.button,
|
||||
QueueInput({InputKind::Button, session, event.cbutton.button,
|
||||
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_CONTROLLERAXISMOTION:
|
||||
QueueInput({InputKind::Axis, generation, event.caxis.axis,
|
||||
QueueInput({InputKind::Axis, session, event.caxis.axis,
|
||||
static_cast<float>(event.caxis.value), 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEMOTION:
|
||||
QueueInput({InputKind::MousePosition, generation, 0, static_cast<float>(event.motion.x),
|
||||
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.motion.x),
|
||||
static_cast<float>(event.motion.y)});
|
||||
return true;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
QueueInput({InputKind::MouseButton, generation, event.button.button,
|
||||
QueueInput({InputKind::MousePosition, session, 0, static_cast<float>(event.button.x),
|
||||
static_cast<float>(event.button.y)});
|
||||
QueueInput({InputKind::MouseButton, session, event.button.button,
|
||||
event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
|
||||
return true;
|
||||
case SDL_MOUSEWHEEL:
|
||||
QueueInput({InputKind::MouseWheel, generation, 0, static_cast<float>(event.wheel.x),
|
||||
QueueInput({InputKind::MouseWheel, session, 0, static_cast<float>(event.wheel.x),
|
||||
static_cast<float>(event.wheel.y)});
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct ImeOverlay::Impl {
|
||||
struct SystemOverlay::Impl {
|
||||
explicit Impl(GraphicContext& context): graphics(context) {}
|
||||
|
||||
~Impl() {
|
||||
@@ -570,8 +617,9 @@ struct ImeOverlay::Impl {
|
||||
io.IniFilename = nullptr;
|
||||
io.LogFilename = nullptr;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
io.ConfigNavCursorVisibleAlways = true;
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||
io.BackendPlatformName = "Kyty IME input";
|
||||
io.BackendPlatformName = "Kyty system overlay input";
|
||||
ImGui::StyleColorsDark();
|
||||
auto& style = ImGui::GetStyle();
|
||||
style.WindowRounding = 10.0f;
|
||||
@@ -609,7 +657,7 @@ struct ImeOverlay::Impl {
|
||||
vulkan_initialized = true;
|
||||
}
|
||||
|
||||
void DrainInput(uint64_t generation) {
|
||||
void DrainInput(OverlaySession session) {
|
||||
std::deque<InputEvent> events;
|
||||
bool reset = false;
|
||||
{
|
||||
@@ -626,16 +674,18 @@ struct ImeOverlay::Impl {
|
||||
right_stick = {};
|
||||
}
|
||||
for (const auto& event: events) {
|
||||
if (event.generation != generation) {
|
||||
if (event.session != session) {
|
||||
continue;
|
||||
}
|
||||
switch (event.kind) {
|
||||
case InputKind::Button: {
|
||||
const bool down = event.x != 0.0f;
|
||||
if (down && event.id == SDL_CONTROLLER_BUTTON_B) {
|
||||
Ime::HostCancel(generation);
|
||||
} else if (down && event.id == SDL_CONTROLLER_BUTTON_Y) {
|
||||
Ime::HostBackspace(generation);
|
||||
if (session.kind == OverlayKind::Ime && down) {
|
||||
if (event.id == SDL_CONTROLLER_BUTTON_B) {
|
||||
Ime::HostCancel(session.generation);
|
||||
} else if (event.id == SDL_CONTROLLER_BUTTON_Y) {
|
||||
Ime::HostBackspace(session.generation);
|
||||
}
|
||||
}
|
||||
const ImGuiKey key = ControllerButtonToKey(event.id);
|
||||
if (key != ImGuiKey_None) {
|
||||
@@ -732,7 +782,7 @@ struct ImeOverlay::Impl {
|
||||
focus_pending = false;
|
||||
}
|
||||
|
||||
void DrawDialog(const Ime::HostSnapshot& snapshot, vk::Extent2D extent) {
|
||||
void DrawIme(const Ime::HostSnapshot& snapshot, vk::Extent2D extent) {
|
||||
const ImVec2 display(static_cast<float>(extent.width), static_cast<float>(extent.height));
|
||||
const bool over_2k = (snapshot.option & Ime::OPTION_USE_OVER_2K) != 0;
|
||||
const float reference_width = over_2k ? 3840.0f : 1920.0f;
|
||||
@@ -851,17 +901,58 @@ struct ImeOverlay::Impl {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void DrawError(const ErrorDialog::HostSnapshot& snapshot, vk::Extent2D extent) {
|
||||
const ImVec2 display(static_cast<float>(extent.width), static_cast<float>(extent.height));
|
||||
const float scale = std::max(std::min(display.x / 1280.0f, display.y / 720.0f), 0.5f);
|
||||
ImGui::GetBackgroundDrawList()->AddRectFilled({0.0f, 0.0f}, display,
|
||||
IM_COL32(0, 0, 0, 160));
|
||||
ImGui::SetNextWindowPos({display.x * 0.5f, display.y * 0.5f}, ImGuiCond_Always,
|
||||
{0.5f, 0.5f});
|
||||
ImGui::SetNextWindowSize(
|
||||
{std::max(std::min(620.0f * scale, display.x - 32.0f), 1.0f), 0.0f}, ImGuiCond_Always);
|
||||
if (focus_pending) {
|
||||
ImGui::SetNextWindowFocus();
|
||||
}
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {24.0f * scale, 24.0f * scale});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {12.0f * scale, 16.0f * scale});
|
||||
ImGui::PushFont(nullptr, 20.0f * scale);
|
||||
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_AlwaysAutoResize;
|
||||
ImGui::Begin("##SystemError", nullptr, flags);
|
||||
ImGui::TextUnformatted("Error");
|
||||
ImGui::Separator();
|
||||
const auto error_code = static_cast<uint32_t>(snapshot.error_code);
|
||||
ImGui::TextWrapped("%s", error_code == 0x80550006u
|
||||
? "You are not signed in to PlayStation Network."
|
||||
: "An error has occurred.");
|
||||
ImGui::TextDisabled("Error code: 0x%08X", error_code);
|
||||
const float button_width = std::min(140.0f * scale, ImGui::GetContentRegionAvail().x);
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x - button_width) * 0.5f);
|
||||
const bool accepted = ImGui::Button("OK", {button_width, 44.0f * scale});
|
||||
if (focus_pending) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
focus_pending = false;
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleVar(2);
|
||||
if (accepted) {
|
||||
ErrorDialog::HostAccept(snapshot.generation);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrepareFrame(vk::Extent2D frame_extent, vk::Format format, uint32_t image_count) {
|
||||
Ime::HostSnapshot snapshot;
|
||||
if (!Ime::GetHostSnapshot(&snapshot)) {
|
||||
OverlaySnapshot snapshot;
|
||||
if (!GetOverlaySnapshot(&snapshot)) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t prepared_generation = snapshot.generation;
|
||||
const auto prepared_session = snapshot.session;
|
||||
EnsureVulkan(format, image_count);
|
||||
if (generation != snapshot.generation) {
|
||||
generation = snapshot.generation;
|
||||
if (session != snapshot.session) {
|
||||
session = snapshot.session;
|
||||
focus_pending = true;
|
||||
shift = (snapshot.option & Ime::OPTION_NO_AUTO_CAPITALIZE) == 0;
|
||||
shift = (snapshot.ime.option & Ime::OPTION_NO_AUTO_CAPITALIZE) == 0;
|
||||
symbol_mode = false;
|
||||
panel_offset = {};
|
||||
right_stick = {};
|
||||
@@ -870,7 +961,7 @@ struct ImeOverlay::Impl {
|
||||
io.ClearInputKeys();
|
||||
io.ClearInputMouse();
|
||||
}
|
||||
DrainInput(snapshot.generation);
|
||||
DrainInput(snapshot.session);
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
io.DisplaySize = {static_cast<float>(frame_extent.width),
|
||||
@@ -883,11 +974,15 @@ struct ImeOverlay::Impl {
|
||||
last_frame = now;
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
if (!Ime::GetHostSnapshot(&snapshot) || snapshot.generation != prepared_generation) {
|
||||
if (!GetOverlaySnapshot(&snapshot) || snapshot.session != prepared_session) {
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
DrawDialog(snapshot, frame_extent);
|
||||
if (snapshot.session.kind == OverlayKind::Error) {
|
||||
DrawError(snapshot.error, frame_extent);
|
||||
} else {
|
||||
DrawIme(snapshot.ime, frame_extent);
|
||||
}
|
||||
ImGui::Render();
|
||||
extent = frame_extent;
|
||||
return true;
|
||||
@@ -934,24 +1029,24 @@ struct ImeOverlay::Impl {
|
||||
float button_height = 42.0f;
|
||||
ImVec2 panel_offset {};
|
||||
ImVec2 right_stick {};
|
||||
uint64_t generation = 0;
|
||||
OverlaySession session;
|
||||
vk::Extent2D extent {};
|
||||
std::chrono::steady_clock::time_point last_frame;
|
||||
};
|
||||
|
||||
ImeOverlay::ImeOverlay(GraphicContext& graphics): m_impl(std::make_unique<Impl>(graphics)) {}
|
||||
SystemOverlay::SystemOverlay(GraphicContext& graphics): m_impl(std::make_unique<Impl>(graphics)) {}
|
||||
|
||||
ImeOverlay::~ImeOverlay() = default;
|
||||
SystemOverlay::~SystemOverlay() = default;
|
||||
|
||||
bool ImeOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
|
||||
bool SystemOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
|
||||
return m_impl->PrepareFrame(extent, format, image_count);
|
||||
}
|
||||
|
||||
void ImeOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
|
||||
void SystemOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
|
||||
m_impl->Record(command, target);
|
||||
}
|
||||
|
||||
void ImeOverlay::ReleaseVulkan() {
|
||||
void SystemOverlay::ReleaseVulkan() {
|
||||
m_impl->ReleaseVulkan();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_SYSTEMOVERLAY_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_SYSTEMOVERLAY_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
union SDL_Event;
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
|
||||
struct SystemOverlayVisualState {
|
||||
bool active;
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
void InitializeSystemOverlayInput();
|
||||
void ShutdownSystemOverlayInput();
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event);
|
||||
SystemOverlayVisualState GetSystemOverlayVisualState() noexcept;
|
||||
|
||||
class SystemOverlay final {
|
||||
public:
|
||||
explicit SystemOverlay(GraphicContext& graphics);
|
||||
~SystemOverlay();
|
||||
KYTY_CLASS_NO_COPY(SystemOverlay);
|
||||
|
||||
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
|
||||
void Record(vk::CommandBuffer command, vk::ImageView target);
|
||||
void ReleaseVulkan();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_SYSTEMOVERLAY_H_
|
||||
@@ -809,7 +809,7 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
|
||||
|
||||
VblankBegin();
|
||||
bool presented = m_flip_queue.Flip(0);
|
||||
if (!presented && m_presenter.NeedsImeRefresh()) {
|
||||
if (!presented && m_presenter.NeedsSystemOverlayRefresh()) {
|
||||
if (auto* frame = m_presenter.PrepareLastFrame(); frame != nullptr) {
|
||||
m_presenter.Present(*frame, true);
|
||||
presented = true;
|
||||
@@ -1123,7 +1123,6 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
m_mutex.Unlock();
|
||||
|
||||
m_presenter.Present(*r.frame);
|
||||
Graphics::RenderDocOnGuestFlip();
|
||||
|
||||
m_mutex.Lock();
|
||||
if (m_requests.empty() || m_requests.front().id != r.id ||
|
||||
@@ -1150,6 +1149,8 @@ bool FlipQueue::Flip(uint32_t micros) {
|
||||
m_mutex.Unlock();
|
||||
r.cfg->mutex.Unlock();
|
||||
|
||||
Graphics::RenderDocOnGuestFlip(m_presenter.Renderer());
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::OutputDirection::Silent) {
|
||||
LOGF("Flip done: %d\n", r.index);
|
||||
|
||||
@@ -194,18 +194,18 @@ const InputMap& GetInputMap() {
|
||||
|
||||
void SetButton(uint32_t button, bool down) {
|
||||
if (button == Controller::PAD_BUTTON_L2) {
|
||||
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID,
|
||||
Controller::Axis::TriggerLeft, down ? 255 : 0);
|
||||
Controller::SetAxis(Controller::HOST_INPUT_CONTROLLER_ID, Controller::Axis::TriggerLeft,
|
||||
down ? 255 : 0);
|
||||
} else if (button == Controller::PAD_BUTTON_R2) {
|
||||
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID,
|
||||
Controller::Axis::TriggerRight, down ? 255 : 0);
|
||||
Controller::SetAxis(Controller::HOST_INPUT_CONTROLLER_ID, Controller::Axis::TriggerRight,
|
||||
down ? 255 : 0);
|
||||
} else if (button != 0) {
|
||||
Controller::ControllerButton(Controller::HOST_INPUT_CONTROLLER_ID, button, down);
|
||||
Controller::SetButton(Controller::HOST_INPUT_CONTROLLER_ID, button, down);
|
||||
}
|
||||
}
|
||||
|
||||
void SetTouchPad(float x, bool down) {
|
||||
Controller::ControllerTouchPad(Controller::HOST_INPUT_CONTROLLER_ID, 0, down, x, 0.5f);
|
||||
Controller::SetTouchPad(Controller::HOST_INPUT_CONTROLLER_ID, 0, down, x, 0.5f);
|
||||
}
|
||||
|
||||
uint32_t DefaultKeyboardButton(int key_code) {
|
||||
@@ -242,7 +242,7 @@ void SetStickAxis(Controller::Axis axis, bool negative, bool positive) {
|
||||
} else if (positive && !negative) {
|
||||
value = 255;
|
||||
}
|
||||
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value);
|
||||
Controller::SetAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value);
|
||||
}
|
||||
|
||||
void SetControl(std::size_t control, bool down) {
|
||||
@@ -326,15 +326,15 @@ void MouseToJoystick(int delta_x, int delta_y) {
|
||||
const auto map_axis = [scale](int delta) {
|
||||
return std::clamp(128 + static_cast<int>(std::lround(delta * scale)), 0, 255);
|
||||
};
|
||||
Controller::ControllerRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
|
||||
map_axis(delta_y));
|
||||
Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, map_axis(delta_x),
|
||||
map_axis(delta_y));
|
||||
}
|
||||
|
||||
void CenterMouseStick() {
|
||||
if (!g_mouse.output) {
|
||||
return;
|
||||
}
|
||||
Controller::ControllerRightStick(Controller::HOST_INPUT_CONTROLLER_ID, 128, 128);
|
||||
Controller::SetRightStick(Controller::HOST_INPUT_CONTROLLER_ID, 128, 128);
|
||||
g_mouse.output = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,9 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeOverlay.h"
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
|
||||
@@ -192,24 +191,15 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
}
|
||||
if (dst.image != nullptr) {
|
||||
graphics.DeleteImage(dst);
|
||||
dst.memory = {};
|
||||
}
|
||||
|
||||
dst.extent = {extent.width, extent.height, 1};
|
||||
dst.format = format;
|
||||
dst.layers = 1;
|
||||
dst.mip_levels = 1;
|
||||
dst.state = {};
|
||||
dst.subresource_states.clear();
|
||||
dst.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||
|
||||
vk::ImageCreateInfo create {};
|
||||
create.sType = vk::StructureType::eImageCreateInfo;
|
||||
create.imageType = vk::ImageType::e2D;
|
||||
create.extent = {dst.extent.width, dst.extent.height, 1};
|
||||
create.extent = {extent.width, extent.height, 1};
|
||||
create.mipLevels = 1;
|
||||
create.arrayLayers = 1;
|
||||
create.format = dst.format;
|
||||
create.format = format;
|
||||
create.tiling = vk::ImageTiling::eOptimal;
|
||||
create.initialLayout = vk::ImageLayout::eUndefined;
|
||||
create.usage = vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst;
|
||||
@@ -217,7 +207,7 @@ void Presenter::Frame::Configure(GraphicContext& graphics, vk::Extent2D extent,
|
||||
create.samples = vk::SampleCountFlagBits::e1;
|
||||
if (!graphics.CreateImage(create, dst)) {
|
||||
EXIT("failed to allocate prepared presentation image, extent=%ux%u format=%d\n",
|
||||
dst.extent.width, dst.extent.height, static_cast<int>(dst.format));
|
||||
extent.width, extent.height, static_cast<int>(format));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,8 +283,9 @@ public:
|
||||
void Create();
|
||||
void Recreate(bool surface_lost = false);
|
||||
[[nodiscard]] Status AcquireNextImage();
|
||||
[[nodiscard]] bool PrepareImeOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_ime_overlay);
|
||||
[[nodiscard]] bool PrepareSystemOverlay();
|
||||
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
bool draw_system_overlay);
|
||||
uint64_t Submit(CommandScheduler& scheduler);
|
||||
[[nodiscard]] Status Present();
|
||||
|
||||
@@ -314,7 +305,7 @@ private:
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
std::vector<vk::Semaphore> m_image_acquired;
|
||||
std::vector<vk::Semaphore> m_render_complete;
|
||||
std::unique_ptr<ImeOverlay> m_ime_overlay;
|
||||
std::unique_ptr<SystemOverlay> m_system_overlay;
|
||||
uint32_t m_image_index = static_cast<uint32_t>(-1);
|
||||
uint32_t m_frame_index = 0;
|
||||
};
|
||||
@@ -361,7 +352,7 @@ struct Presenter::Impl {
|
||||
Swapchain swapchain;
|
||||
CommandScheduler present_scheduler;
|
||||
FramePool frames;
|
||||
std::atomic<uint64_t> presented_ime_revision {0};
|
||||
std::atomic<uint64_t> presented_overlay_revision {0};
|
||||
};
|
||||
|
||||
void Swapchain::Create() {
|
||||
@@ -504,8 +495,8 @@ void Swapchain::Destroy() {
|
||||
Common::LockGuard queue_lock(graphics.queue_mutex);
|
||||
RequireVulkanSuccess(graphics.queue.waitIdle(), "wait for swapchain queue");
|
||||
}
|
||||
if (m_ime_overlay != nullptr) {
|
||||
m_ime_overlay->ReleaseVulkan();
|
||||
if (m_system_overlay != nullptr) {
|
||||
m_system_overlay->ReleaseVulkan();
|
||||
}
|
||||
|
||||
for (const auto semaphore: m_image_acquired) {
|
||||
@@ -573,21 +564,21 @@ Swapchain::Status Swapchain::AcquireNextImage() {
|
||||
case vk::Result::eErrorSurfaceLostKHR:
|
||||
LOGF("vkAcquireNextImageKHR returned vk::Result::eErrorSurfaceLostKHR\n");
|
||||
return Status::SurfaceLost;
|
||||
default: EXIT("vkAcquireNextImageKHR failed: %s\n", VulkanToString(result).c_str());
|
||||
default: EXIT("vkAcquireNextImageKHR failed: %s\n", vk::to_string(result).c_str());
|
||||
}
|
||||
EXIT_IF(m_image_index >= m_images.size());
|
||||
return Status::Success;
|
||||
}
|
||||
|
||||
bool Swapchain::PrepareImeOverlay() {
|
||||
if (m_ime_overlay == nullptr) {
|
||||
m_ime_overlay = std::make_unique<ImeOverlay>(m_window.graphic_ctx);
|
||||
bool Swapchain::PrepareSystemOverlay() {
|
||||
if (m_system_overlay == nullptr) {
|
||||
m_system_overlay = std::make_unique<SystemOverlay>(m_window.graphic_ctx);
|
||||
}
|
||||
return m_ime_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||
return m_system_overlay->PrepareFrame(m_extent, m_format, ImageCount());
|
||||
}
|
||||
|
||||
void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
|
||||
bool draw_ime_overlay) {
|
||||
bool draw_system_overlay) {
|
||||
if (source.state.layout != vk::ImageLayout::eTransferSrcOptimal) {
|
||||
EXIT("invalid prepared presentation image, vk_image=%p layout=%d\n",
|
||||
static_cast<void*>(source.image), static_cast<int>(source.state.layout));
|
||||
@@ -608,7 +599,8 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
to_transfer.subresourceRange.levelCount = 1;
|
||||
to_transfer.subresourceRange.baseArrayLayer = 0;
|
||||
to_transfer.subresourceRange.layerCount = 1;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
// Match the acquire wait stage so the layout transition cannot precede acquisition.
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags {}, 0,
|
||||
nullptr, 0, nullptr, 1, &to_transfer);
|
||||
|
||||
@@ -634,12 +626,12 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
vk::ImageMemoryBarrier to_present {};
|
||||
to_present.sType = vk::StructureType::eImageMemoryBarrier;
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
to_present.dstAccessMask = draw_ime_overlay ? vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite
|
||||
: vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.dstAccessMask = draw_system_overlay ? vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite
|
||||
: vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
to_present.newLayout = draw_ime_overlay ? vk::ImageLayout::eColorAttachmentOptimal
|
||||
: vk::ImageLayout::ePresentSrcKHR;
|
||||
to_present.newLayout = draw_system_overlay ? vk::ImageLayout::eColorAttachmentOptimal
|
||||
: vk::ImageLayout::ePresentSrcKHR;
|
||||
to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
to_present.image = m_images[m_image_index];
|
||||
@@ -648,13 +640,13 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
|
||||
to_present.subresourceRange.levelCount = 1;
|
||||
to_present.subresourceRange.baseArrayLayer = 0;
|
||||
to_present.subresourceRange.layerCount = 1;
|
||||
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
draw_ime_overlay ? vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
: vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
|
||||
&to_present);
|
||||
if (draw_ime_overlay) {
|
||||
m_ime_overlay->Record(vk_command, m_image_views[m_image_index]);
|
||||
vk_command.pipelineBarrier(
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
draw_system_overlay ? vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
: vk::PipelineStageFlagBits::eAllCommands,
|
||||
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present);
|
||||
if (draw_system_overlay) {
|
||||
m_system_overlay->Record(vk_command, m_image_views[m_image_index]);
|
||||
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
|
||||
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
@@ -701,7 +693,7 @@ Swapchain::Status Swapchain::Present() {
|
||||
case vk::Result::eErrorSurfaceLostKHR:
|
||||
LOGF("vkQueuePresentKHR returned vk::Result::eErrorSurfaceLostKHR\n");
|
||||
return Status::SurfaceLost;
|
||||
default: EXIT("vkQueuePresentKHR failed: %s\n", VulkanToString(result).c_str());
|
||||
default: EXIT("vkQueuePresentKHR failed: %s\n", vk::to_string(result).c_str());
|
||||
}
|
||||
m_frame_index = (m_frame_index + 1u) % static_cast<uint32_t>(m_images.size());
|
||||
return Status::Success;
|
||||
@@ -761,10 +753,10 @@ bool Presenter::IsGuestPaused() const noexcept {
|
||||
return m_impl->window.loop.paused.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
bool Presenter::NeedsImeRefresh() const noexcept {
|
||||
const auto visual = GetImeVisualState();
|
||||
bool Presenter::NeedsSystemOverlayRefresh() const noexcept {
|
||||
const auto visual = GetSystemOverlayVisualState();
|
||||
return visual.active ||
|
||||
visual.revision != m_impl->presented_ime_revision.load(std::memory_order_acquire);
|
||||
visual.revision != m_impl->presented_overlay_revision.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
RenderContext& Presenter::Renderer() const noexcept {
|
||||
@@ -775,7 +767,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
m_impl->frames.ValidateForPresent(&frame, reuse);
|
||||
|
||||
const auto ime_visual = GetImeVisualState();
|
||||
const auto overlay_visual = GetSystemOverlayVisualState();
|
||||
auto& swapchain = m_impl->swapchain;
|
||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||
auto status = swapchain.AcquireNextImage();
|
||||
@@ -786,8 +778,9 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
{
|
||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||
auto& command = m_impl->present_scheduler.BeginCommand();
|
||||
const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
|
||||
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
|
||||
const bool draw_system_overlay =
|
||||
overlay_visual.active && swapchain.PrepareSystemOverlay();
|
||||
swapchain.RecordPresentCommands(command, frame.image, draw_system_overlay);
|
||||
frame.present_tick = swapchain.Submit(m_impl->present_scheduler);
|
||||
}
|
||||
status = swapchain.Present();
|
||||
@@ -796,7 +789,8 @@ void Presenter::Present(Frame& frame, bool reuse) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
|
||||
m_impl->presented_overlay_revision.store(overlay_visual.revision,
|
||||
std::memory_order_release);
|
||||
m_impl->window.UpdateTitle();
|
||||
m_impl->frames.Release(&frame, true);
|
||||
return;
|
||||
|
||||
@@ -27,10 +27,9 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeOverlay.h"
|
||||
#include "graphics/presentation/presenter.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "graphics/presentation/videoOut.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
@@ -63,6 +62,18 @@ struct VulkanExtensions {
|
||||
std::vector<vk::LayerProperties> available_layers;
|
||||
};
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features WindowContext::RequiredVulkan12Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan12Features features {};
|
||||
features.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
|
||||
features.samplerMirrorClampToEdge = VK_TRUE;
|
||||
features.timelineSemaphore = VK_TRUE;
|
||||
features.shaderOutputLayer = VK_TRUE;
|
||||
features.shaderOutputViewportIndex = VK_TRUE;
|
||||
features.bufferDeviceAddress = VK_TRUE;
|
||||
features.shaderBufferInt64Atomics = VK_TRUE;
|
||||
return features;
|
||||
}
|
||||
|
||||
vk::PhysicalDeviceVulkan13Features WindowContext::RequiredVulkan13Features() noexcept {
|
||||
vk::PhysicalDeviceVulkan13Features features {};
|
||||
features.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
|
||||
@@ -132,7 +143,7 @@ static uint32_t VulkanFindQueueFamily(vk::PhysicalDevice device, vk::SurfaceKHR
|
||||
"vkGetPhysicalDeviceSurfaceSupportKHR");
|
||||
|
||||
LOGF("\tqueue family: %s [count = %u], [present = %s]\n",
|
||||
VulkanToString(properties.queueFlags).c_str(), properties.queueCount,
|
||||
vk::to_string(properties.queueFlags).c_str(), properties.queueCount,
|
||||
(presentation_supported == VK_TRUE ? "true" : "false"));
|
||||
if (properties.queueCount != 0 && (properties.queueFlags & required) == required &&
|
||||
presentation_supported == VK_TRUE) {
|
||||
@@ -158,7 +169,12 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
EXIT_NOT_IMPLEMENTED(devices.empty());
|
||||
|
||||
if (Config::GetGpuIndex() >= 0) {
|
||||
devices = {devices[Config::GetGpuIndex()]};
|
||||
if (static_cast<size_t>(Config::GetGpuIndex()) < devices.size()) {
|
||||
devices = {devices[Config::GetGpuIndex()]};
|
||||
} else {
|
||||
LOGF("Vulkan GPU index %d is unavailable; selecting automatically\n",
|
||||
Config::GetGpuIndex());
|
||||
}
|
||||
}
|
||||
|
||||
vk::PhysicalDevice best_device = nullptr;
|
||||
@@ -217,6 +233,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
device_features2.pNext = &features13;
|
||||
|
||||
device.getFeatures2(&device_features2);
|
||||
const auto required_features12 = WindowContext::RequiredVulkan12Features();
|
||||
const auto required_features13 = WindowContext::RequiredVulkan13Features();
|
||||
|
||||
const auto queue_family = VulkanFindQueueFamily(device, surface);
|
||||
@@ -249,14 +266,36 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
}
|
||||
#endif
|
||||
|
||||
if (features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
if (required_features12.samplerMirrorClampToEdge == VK_TRUE &&
|
||||
features12.samplerMirrorClampToEdge != VK_TRUE) {
|
||||
LOGF("samplerMirrorClampToEdge is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features12.timelineSemaphore != VK_TRUE) {
|
||||
if (required_features12.timelineSemaphore == VK_TRUE &&
|
||||
features12.timelineSemaphore != VK_TRUE) {
|
||||
LOGF("timelineSemaphore is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderOutputLayer == VK_TRUE &&
|
||||
features12.shaderOutputLayer != VK_TRUE) {
|
||||
LOGF("shaderOutputLayer is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderOutputViewportIndex == VK_TRUE &&
|
||||
features12.shaderOutputViewportIndex != VK_TRUE) {
|
||||
LOGF("shaderOutputViewportIndex is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.bufferDeviceAddress == VK_TRUE &&
|
||||
features12.bufferDeviceAddress != VK_TRUE) {
|
||||
LOGF("bufferDeviceAddress is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (required_features12.shaderBufferInt64Atomics == VK_TRUE &&
|
||||
features12.shaderBufferInt64Atomics != VK_TRUE) {
|
||||
LOGF("shaderBufferInt64Atomics is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features13.robustImageAccess != VK_TRUE) {
|
||||
LOGF("robustImageAccess is not supported\n");
|
||||
skip_device = true;
|
||||
@@ -291,11 +330,14 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
|
||||
LOGF("largePoints is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (features12.shaderOutputLayer != VK_TRUE) {
|
||||
LOGF("shaderOutputLayer is not supported\n");
|
||||
if (device_features2.features.multiViewport != VK_TRUE) {
|
||||
LOGF("multiViewport is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
if (device_features2.features.fillModeNonSolid != VK_TRUE) {
|
||||
LOGF("fillModeNonSolid is not supported\n");
|
||||
skip_device = true;
|
||||
}
|
||||
|
||||
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
|
||||
LOGF("fragmentStoresAndAtomics is not supported\n");
|
||||
skip_device = true;
|
||||
@@ -501,14 +543,12 @@ static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device,
|
||||
(graphics.required_subgroup_size_stages & vk::ShaderStageFlagBits::eCompute) &&
|
||||
subgroup_size_control.minSubgroupSize <= 64 &&
|
||||
subgroup_size_control.maxSubgroupSize >= 64;
|
||||
graphics.compute_wave64_supported =
|
||||
graphics.subgroup_size == 64u || graphics.compute_subgroup_size_control_enabled;
|
||||
|
||||
LOGF("Vulkan subgroup: default=%u min=%u max=%u stages=0x%08x size_control=%s wave64=%s\n",
|
||||
graphics.subgroup_size, graphics.min_subgroup_size, graphics.max_subgroup_size,
|
||||
static_cast<vk::ShaderStageFlags::MaskType>(graphics.required_subgroup_size_stages),
|
||||
graphics.compute_subgroup_size_control_enabled ? "true" : "false",
|
||||
graphics.compute_wave64_supported ? "true" : "false");
|
||||
graphics.SupportsComputeWave64() ? "true" : "false");
|
||||
}
|
||||
|
||||
static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const VulkanExtensions& r,
|
||||
@@ -547,10 +587,8 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
#endif
|
||||
depth_clip_control.depthClipControl = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceVulkan12Features features12 {};
|
||||
features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
|
||||
features12.pNext = &depth_clip_control;
|
||||
features12.samplerMirrorClampToEdge = VK_TRUE;
|
||||
auto features12 = WindowContext::RequiredVulkan12Features();
|
||||
features12.pNext = &depth_clip_control;
|
||||
|
||||
vk::PhysicalDeviceVulkan13Features supported_features13 {};
|
||||
supported_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
|
||||
@@ -582,12 +620,54 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
#endif
|
||||
}
|
||||
|
||||
const bool mesh_extension = HasExtension(device_extensions, VK_EXT_MESH_SHADER_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT supported_mesh {};
|
||||
supported_mesh.pNext = &supported_features13;
|
||||
vk::PhysicalDeviceFeatures2 supported_features2 {};
|
||||
supported_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
|
||||
supported_features2.pNext = &supported_features13;
|
||||
supported_features2.pNext = mesh_extension ? static_cast<void*>(&supported_mesh)
|
||||
: static_cast<void*>(&supported_features13);
|
||||
const bool feedback_extensions =
|
||||
HasExtension(device_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME) &&
|
||||
HasExtension(device_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT feedback_layout {};
|
||||
vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT feedback_dynamic {};
|
||||
if (feedback_extensions) {
|
||||
feedback_dynamic.pNext = supported_features2.pNext;
|
||||
feedback_layout.pNext = &feedback_dynamic;
|
||||
supported_features2.pNext = &feedback_layout;
|
||||
}
|
||||
const bool provoking_extension =
|
||||
HasExtension(device_extensions, VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME);
|
||||
vk::PhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex {};
|
||||
if (provoking_extension) {
|
||||
provoking_vertex.pNext = supported_features2.pNext;
|
||||
supported_features2.pNext = &provoking_vertex;
|
||||
}
|
||||
physical_device.getFeatures2(&supported_features2);
|
||||
graphics.provoking_vertex_last_enabled = provoking_extension && provoking_vertex.provokingVertexLast;
|
||||
graphics.attachment_feedback_loop_enabled =
|
||||
feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
feedback_dynamic.attachmentFeedbackLoopDynamicState;
|
||||
LOGF("Vulkan depth feedback support: %s\n",
|
||||
graphics.attachment_feedback_loop_enabled ? "true" : "false");
|
||||
graphics.mesh_shader_enabled = mesh_extension && supported_mesh.meshShader;
|
||||
if (graphics.mesh_shader_enabled) {
|
||||
vk::PhysicalDeviceProperties2 properties {};
|
||||
properties.pNext = &graphics.mesh_shader_properties;
|
||||
physical_device.getProperties2(&properties);
|
||||
LOGF("Vulkan MeshEXT: invocations=%u vertices=%u primitives=%u shared=%u\n",
|
||||
graphics.mesh_shader_properties.maxMeshWorkGroupInvocations,
|
||||
graphics.mesh_shader_properties.maxMeshOutputVertices,
|
||||
graphics.mesh_shader_properties.maxMeshOutputPrimitives,
|
||||
graphics.mesh_shader_properties.maxMeshSharedMemorySize);
|
||||
}
|
||||
const auto required_features12 = WindowContext::RequiredVulkan12Features();
|
||||
const auto required_features13 = WindowContext::RequiredVulkan13Features();
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.timelineSemaphore != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.samplerMirrorClampToEdge == VK_TRUE &&
|
||||
supported_features12.samplerMirrorClampToEdge != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.timelineSemaphore == VK_TRUE &&
|
||||
supported_features12.timelineSemaphore != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features13.dynamicRendering == VK_TRUE &&
|
||||
supported_features13.dynamicRendering != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features13.synchronization2 == VK_TRUE &&
|
||||
@@ -597,17 +677,21 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderClipDistance != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderCullDistance != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.largePoints != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.multiViewport != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.fillModeNonSolid != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.shaderOutputLayer != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features12.bufferDeviceAddress != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.shaderOutputLayer == VK_TRUE &&
|
||||
supported_features12.shaderOutputLayer != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.shaderOutputViewportIndex == VK_TRUE &&
|
||||
supported_features12.shaderOutputViewportIndex != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.bufferDeviceAddress == VK_TRUE &&
|
||||
supported_features12.bufferDeviceAddress != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(required_features12.shaderBufferInt64Atomics == VK_TRUE &&
|
||||
supported_features12.shaderBufferInt64Atomics != VK_TRUE);
|
||||
#if !defined(__APPLE__)
|
||||
EXIT_NOT_IMPLEMENTED(supported_fragment_barycentric.fragmentShaderBarycentric != VK_TRUE);
|
||||
#endif
|
||||
features12.timelineSemaphore = VK_TRUE;
|
||||
features12.shaderOutputLayer = VK_TRUE;
|
||||
features12.bufferDeviceAddress = VK_TRUE;
|
||||
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
@@ -624,6 +708,8 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
device_features.shaderClipDistance = VK_TRUE;
|
||||
device_features.shaderCullDistance = VK_TRUE;
|
||||
device_features.largePoints = VK_TRUE;
|
||||
device_features.multiViewport = VK_TRUE;
|
||||
device_features.fillModeNonSolid = VK_TRUE;
|
||||
device_features.vertexPipelineStoresAndAtomics = VK_TRUE;
|
||||
graphics.sample_rate_shading_enabled = true;
|
||||
device_features.shaderInt64 = VK_TRUE;
|
||||
@@ -666,8 +752,20 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
|
||||
|
||||
vk::DeviceCreateInfo create_info {};
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT mesh_features {};
|
||||
mesh_features.pNext = &features13;
|
||||
mesh_features.meshShader = graphics.mesh_shader_enabled;
|
||||
create_info.sType = vk::StructureType::eDeviceCreateInfo;
|
||||
create_info.pNext = &features13;
|
||||
feedback_dynamic.pNext =
|
||||
mesh_extension ? static_cast<void*>(&mesh_features) : static_cast<void*>(&features13);
|
||||
create_info.pNext = graphics.attachment_feedback_loop_enabled
|
||||
? static_cast<void*>(&feedback_layout)
|
||||
: feedback_dynamic.pNext;
|
||||
if (graphics.provoking_vertex_last_enabled) {
|
||||
provoking_vertex.pNext = const_cast<void*>(create_info.pNext);
|
||||
provoking_vertex.transformFeedbackPreservesProvokingVertex = VK_FALSE;
|
||||
create_info.pNext = &provoking_vertex;
|
||||
}
|
||||
create_info.flags = {};
|
||||
create_info.pQueueCreateInfos = &queue_create_info;
|
||||
create_info.queueCreateInfoCount = 1;
|
||||
@@ -679,7 +777,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
|
||||
|
||||
auto result = physical_device.createDevice(&create_info, nullptr, &device);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
LOGF("vkCreateDevice failed: %s\n", VulkanToString(result).c_str());
|
||||
LOGF("vkCreateDevice failed: %s\n", vk::to_string(result).c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -858,7 +956,7 @@ static void VulkanCheckInstanceVersion() {
|
||||
if (VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion != nullptr) {
|
||||
auto result = vk::enumerateInstanceVersion(&version);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
EXIT("Could not query Vulkan loader version: %s\n", VulkanToString(result).c_str());
|
||||
EXIT("Could not query Vulkan loader version: %s\n", vk::to_string(result).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,6 +1133,18 @@ void WindowContext::CreateVulkan() {
|
||||
|
||||
LOGF("Select device: %s\n", device_properties.deviceName.data());
|
||||
|
||||
const vk::PhysicalDeviceImageFormatInfo2 block_texel_view_info {
|
||||
.format = vk::Format::eBc1RgbaUnormBlock,
|
||||
.type = vk::ImageType::e2D,
|
||||
.tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = vk::ImageUsageFlagBits::eSampled,
|
||||
.flags = vk::ImageCreateFlagBits::eBlockTexelViewCompatible,
|
||||
};
|
||||
const auto block_texel_view_props =
|
||||
graphic_ctx.physical_device.getImageFormatProperties2(block_texel_view_info);
|
||||
graphic_ctx.supports_block_texel_view = block_texel_view_props.result == vk::Result::eSuccess;
|
||||
LOGF("Block Texel View support: %s\n", graphic_ctx.supports_block_texel_view ? "Yes" : "No");
|
||||
|
||||
{
|
||||
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
|
||||
"vkEnumerateDeviceExtensionProperties",
|
||||
@@ -1047,8 +1157,18 @@ void WindowContext::CreateVulkan() {
|
||||
device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
|
||||
graphic_ctx.memory_budget_ext_enabled = true;
|
||||
}
|
||||
if (HasExtension(available_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)) {
|
||||
device_extensions.push_back(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
for (const auto* extension: {VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
|
||||
VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME,
|
||||
VK_EXT_MESH_SHADER_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME}) {
|
||||
if (HasExtension(available_extensions, extension)) {
|
||||
device_extensions.push_back(extension);
|
||||
}
|
||||
}
|
||||
if (HasExtension(available_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME) &&
|
||||
HasExtension(available_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME)) {
|
||||
device_extensions.push_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME);
|
||||
device_extensions.push_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1093,7 +1213,7 @@ void WindowContext::RecreateSurface() {
|
||||
}
|
||||
|
||||
WindowContext::~WindowContext() {
|
||||
ShutdownImeInput();
|
||||
ShutdownSystemOverlayInput();
|
||||
presenter.reset();
|
||||
LibKernel::Memory::InstallGpuResources(nullptr);
|
||||
render_context.reset();
|
||||
|
||||
@@ -30,10 +30,9 @@
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/presentation/imeOverlay.h"
|
||||
#include "graphics/presentation/renderDoc.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "graphics/presentation/window/hostInput.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "kytyGitVersion.h"
|
||||
@@ -351,26 +350,26 @@ static void GameEventController([[maybe_unused]] const EventController& f) {
|
||||
auto* pad = SDL_GameControllerOpen(f.id);
|
||||
EXIT_NOT_IMPLEMENTED(pad == nullptr);
|
||||
int id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(pad));
|
||||
Controller::ControllerConnect(id);
|
||||
Controller::Connect(id);
|
||||
}
|
||||
|
||||
if (f.removed) {
|
||||
Controller::ControllerDisconnect(f.id);
|
||||
Controller::Disconnect(f.id);
|
||||
SDL_GameControllerClose(SDL_GameControllerFromInstanceID(f.id));
|
||||
}
|
||||
|
||||
if (f.down || f.up) {
|
||||
const auto button = ControllerButtonToPadButton(f.button);
|
||||
if (button != 0) {
|
||||
Controller::ControllerButton(f.id, button, f.down);
|
||||
Controller::SetButton(f.id, button, f.down);
|
||||
}
|
||||
}
|
||||
|
||||
if (f.axis) {
|
||||
const auto axis = ControllerAxisFromSdl(f.axis_id);
|
||||
if (axis != Controller::Axis::AxisMax) {
|
||||
Controller::ControllerAxis(f.id, axis,
|
||||
ControllerAxisValueFromSdl(f.axis_id, f.axis_value));
|
||||
Controller::SetAxis(f.id, axis,
|
||||
ControllerAxisValueFromSdl(f.axis_id, f.axis_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,7 +512,7 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ProcessImeInput(*event)) {
|
||||
if (ProcessSystemOverlayInput(*event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -708,9 +707,9 @@ void WindowContext::ProcessEvent(double time_s) {
|
||||
case SDL_CONTROLLERTOUCHPADMOTION:
|
||||
case SDL_CONTROLLERTOUCHPADUP:
|
||||
if (event->ctouchpad.touchpad == 0) {
|
||||
Controller::ControllerTouchPad(event->ctouchpad.which, event->ctouchpad.finger,
|
||||
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP,
|
||||
event->ctouchpad.x, event->ctouchpad.y);
|
||||
Controller::SetTouchPad(event->ctouchpad.which, event->ctouchpad.finger,
|
||||
event->ctouchpad.type != SDL_CONTROLLERTOUCHPADUP,
|
||||
event->ctouchpad.x, event->ctouchpad.y);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -814,6 +813,7 @@ static void WindowCreate(WindowContext& context) {
|
||||
int width = static_cast<int>(context.graphic_ctx.screen_width);
|
||||
int height = static_cast<int>(context.graphic_ctx.screen_height);
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
|
||||
@@ -823,7 +823,7 @@ static void WindowCreate(WindowContext& context) {
|
||||
EXIT("%s\n", SDL_GetError());
|
||||
}
|
||||
HostInputInit();
|
||||
InitializeImeInput();
|
||||
InitializeSystemOverlayInput();
|
||||
|
||||
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
|
||||
|
||||
@@ -899,6 +899,7 @@ void WindowRun() {
|
||||
|
||||
void WindowShutdown() {
|
||||
if (g_window != nullptr) {
|
||||
Controller::EmergencyShutdown();
|
||||
g_window.reset();
|
||||
}
|
||||
}
|
||||
@@ -1015,7 +1016,7 @@ void WindowContext::UpdateTitle() {
|
||||
|
||||
const auto* device_name = graphic_ctx.GetPhysicalDeviceProperties().deviceName.data();
|
||||
auto text = fmt::format(
|
||||
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL, build_type,
|
||||
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:.0f}", KYTY_BUILD_LABEL, build_type,
|
||||
(has_title ? title : ""), (has_title ? ", " : ""), (has_title_id ? title_id : ""),
|
||||
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
|
||||
device_name, processor_name, frame_num, current_fps);
|
||||
|
||||
@@ -35,6 +35,7 @@ struct WindowContext {
|
||||
~WindowContext();
|
||||
KYTY_CLASS_NO_COPY(WindowContext);
|
||||
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan12Features RequiredVulkan12Features() noexcept;
|
||||
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features RequiredVulkan13Features() noexcept;
|
||||
[[nodiscard]] static uint32_t InitialWindowFlags(bool fullscreen) noexcept;
|
||||
void CreateVulkan();
|
||||
|
||||
@@ -26,6 +26,28 @@ struct BufferFormatInfo {
|
||||
bool packed_bitfield = false;
|
||||
};
|
||||
|
||||
enum class FormattedSourceKind { Memory, Zero, One, Invalid };
|
||||
|
||||
struct FormattedSource {
|
||||
FormattedSourceKind kind = FormattedSourceKind::Zero;
|
||||
uint32_t component = 0;
|
||||
};
|
||||
|
||||
constexpr FormattedSource ResolveFormattedSource(const BufferFormatInfo& info, uint32_t selector) {
|
||||
if (selector == 0u) return {};
|
||||
if (selector == 1u) return {FormattedSourceKind::One, 0};
|
||||
if (selector < 4u || selector > 7u || info.component_count == 0u) {
|
||||
return {FormattedSourceKind::Invalid, 0};
|
||||
}
|
||||
// Formatted decoding expands source channels before applying the swizzle.
|
||||
return {FormattedSourceKind::Memory, (selector - 4u) % info.component_count};
|
||||
}
|
||||
|
||||
constexpr uint32_t FormattedConstantBits(const BufferFormatInfo& info, FormattedSourceKind kind) {
|
||||
if (kind != FormattedSourceKind::One) return 0;
|
||||
return info.type == ComponentType::Uint || info.type == ComponentType::Sint ? 1u : 0x3f800000u;
|
||||
}
|
||||
|
||||
constexpr Prospero::BufferFormat DecodeTBufferFormat(uint32_t data_format, uint32_t number_format) {
|
||||
return static_cast<Prospero::BufferFormat>(((number_format & 0x7u) << 4u) |
|
||||
(data_format & 0xfu));
|
||||
@@ -216,28 +238,13 @@ constexpr BufferFormatInfo GetFormatInfo(Prospero::BufferFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool IsKnownFormat(Prospero::BufferFormat format) {
|
||||
return GetFormatInfo(format).type != ComponentType::Unknown;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatComponentCount(Prospero::BufferFormat format) {
|
||||
return GetFormatInfo(format).component_count;
|
||||
}
|
||||
|
||||
constexpr uint32_t GetFormatComponentByteOffset(Prospero::BufferFormat format, uint32_t component) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
constexpr uint32_t GetFormatComponentByteOffset(const BufferFormatInfo& info, uint32_t component) {
|
||||
if (component >= info.component_count) {
|
||||
return 0;
|
||||
}
|
||||
return info.packed_bitfield ? 0u : info.component_bit_offset[component] / 8u;
|
||||
}
|
||||
|
||||
constexpr bool CanUseTypedBufferLoad(Prospero::BufferFormat format) {
|
||||
const auto info = GetFormatInfo(format);
|
||||
return info.type != ComponentType::Unknown && !info.packed_bitfield &&
|
||||
info.component_bits[0] == 32u;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Format
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_BUFFERFORMAT_H_ */
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SharedMemoryBarrier.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
|
||||
|
||||
@@ -34,9 +33,9 @@ const char* GetDumpLabel(const CompileOptions& options) {
|
||||
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
|
||||
}
|
||||
|
||||
std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
|
||||
std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
|
||||
std::string dump = "CFG:\n";
|
||||
dump += CFG::GraphToString(cfg);
|
||||
dump += cfg;
|
||||
dump += "\nIR:\n";
|
||||
dump += fmt::format("mode={} scratch_dwords={}\n",
|
||||
ir.dispatcher_fallback ? "dispatcher" : "structured", ir.scratch_dwords);
|
||||
@@ -48,6 +47,7 @@ const char* StageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Compute: return "CS";
|
||||
case ShaderType::Vertex: return "VS";
|
||||
case ShaderType::Mesh: return "MS";
|
||||
case ShaderType::Pixel: return "PS";
|
||||
default: return "unknown";
|
||||
}
|
||||
@@ -77,8 +77,7 @@ enum class EmbeddedFetchValueType {
|
||||
AttribTable,
|
||||
Attrib,
|
||||
BufferTable,
|
||||
Buffer,
|
||||
Index
|
||||
Buffer
|
||||
};
|
||||
|
||||
struct EmbeddedFetchSgprInfo {
|
||||
@@ -88,10 +87,6 @@ struct EmbeddedFetchSgprInfo {
|
||||
std::vector<uint32_t> prolog_loads;
|
||||
};
|
||||
|
||||
struct EmbeddedFetchVgprInfo {
|
||||
EmbeddedFetchValueType type = EmbeddedFetchValueType::Unknown;
|
||||
};
|
||||
|
||||
using EmbeddedFetchVectorLanes = std::map<uint64_t, EmbeddedFetchSgprInfo>;
|
||||
|
||||
uint64_t EmbeddedFetchVectorLaneKey(uint32_t reg, uint32_t lane) {
|
||||
@@ -108,7 +103,6 @@ void ClearEmbeddedFetchVectorLanes(EmbeddedFetchVectorLanes* lanes, uint32_t reg
|
||||
lanes->erase(first, last);
|
||||
}
|
||||
|
||||
using EmbeddedFetchLoad = Frontend::EmbeddedFetchLoad;
|
||||
using EmbeddedFetchData = Frontend::EmbeddedFetchPlan;
|
||||
|
||||
bool IsDecodedSgpr(const Decoder::Operand& op) {
|
||||
@@ -234,15 +228,17 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
uint32_t wave_size) {
|
||||
EmbeddedFetchData data;
|
||||
data.loads.reserve(input_info->resources_num);
|
||||
int32_t offset_candidate = -1;
|
||||
bool offset_conflict = false;
|
||||
int32_t vertex_offset_candidate = -1;
|
||||
int32_t instance_offset_candidate = -1;
|
||||
bool vertex_offset_conflict = false;
|
||||
bool instance_offset_conflict = false;
|
||||
|
||||
const int shift_regs = 8;
|
||||
const int attrib_reg = input_info->fetch_attrib_reg + shift_regs;
|
||||
const int buffer_reg = input_info->fetch_buffer_reg + shift_regs;
|
||||
|
||||
std::array<EmbeddedFetchSgprInfo, 108> sgprs {};
|
||||
std::array<EmbeddedFetchVgprInfo, 256> vgprs {};
|
||||
std::array<bool, 256> vgpr_is_index {};
|
||||
EmbeddedFetchVectorLanes vector_lanes;
|
||||
const bool track_vector_lanes =
|
||||
std::none_of(decoded.instructions.begin(), decoded.instructions.end(),
|
||||
@@ -263,34 +259,42 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
|
||||
for (const auto& inst: decoded.instructions) {
|
||||
// Fetch shaders accumulate the draw's vertex offset in v0. The PS5 NGG ABI
|
||||
// seeds S_NGG_VERTEX_INDEX in v5 and applies the same offset there before fetching.
|
||||
// seeds S_NGG_VERTEX_INDEX in v5 and S_NGG_INSTANCE_INDEX in v8, then applies the
|
||||
// corresponding direct-draw offsets before fetching.
|
||||
const bool vertex_index_accumulator =
|
||||
IsDecodedVgpr(inst.dst) &&
|
||||
(inst.dst.reg == 0 || (user_data_base == 8 && inst.dst.reg == 5));
|
||||
const bool instance_index_accumulator =
|
||||
IsDecodedVgpr(inst.dst) && (inst.dst.reg == (user_data_base == 8 ? 8u : 3u));
|
||||
uint32_t sad_zero = 0;
|
||||
const bool vertex_offset_add =
|
||||
vertex_index_accumulator && IsDecodedSgpr(inst.src0) &&
|
||||
const bool index_offset_add =
|
||||
(vertex_index_accumulator || instance_index_accumulator) &&
|
||||
IsDecodedSgpr(inst.src0) &&
|
||||
((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) &&
|
||||
inst.src1.reg == inst.dst.reg) ||
|
||||
(user_data_base == 8 && inst.dst.reg == 5 &&
|
||||
(user_data_base == 8 && (inst.dst.reg == 5 || inst.dst.reg == 8) &&
|
||||
inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) &&
|
||||
inst.src2.reg == inst.dst.reg &&
|
||||
TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0));
|
||||
if (data.loads.empty() && vertex_offset_add) {
|
||||
if (data.loads.empty() && index_offset_add) {
|
||||
const auto reg = DecodedSgprReg(inst.src0);
|
||||
if (reg >= user_data_base && reg - user_data_base < user_data_count) {
|
||||
if (offset_candidate >= 0 && offset_candidate != static_cast<int32_t>(reg)) {
|
||||
offset_conflict = true;
|
||||
auto& candidate = vertex_index_accumulator ? vertex_offset_candidate
|
||||
: instance_offset_candidate;
|
||||
auto& conflict = vertex_index_accumulator ? vertex_offset_conflict
|
||||
: instance_offset_conflict;
|
||||
if (candidate >= 0 && candidate != static_cast<int32_t>(reg)) {
|
||||
conflict = true;
|
||||
} else {
|
||||
offset_candidate = static_cast<int32_t>(reg);
|
||||
candidate = static_cast<int32_t>(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (inst.opcode) {
|
||||
case Decoder::Opcode::V_WRITELANE_B32: {
|
||||
uint32_t lane = 0;
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
|
||||
vgprs[inst.dst.reg] = {};
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size()) {
|
||||
vgpr_is_index[inst.dst.reg] = false;
|
||||
}
|
||||
if (track_vector_lanes && IsDecodedVgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
|
||||
DecodedSgprReg(inst.src0) < sgprs.size() &&
|
||||
@@ -395,13 +399,13 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
|
||||
}
|
||||
} else if (inst.opcode == Decoder::Opcode::V_CNDMASK_B32) {
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size()) {
|
||||
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
|
||||
}
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size() &&
|
||||
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgpr_is_index.size() &&
|
||||
IsDecodedVgpr(inst.src0) && inst.src0.reg == 8 &&
|
||||
IsDecodedVgpr(inst.src1) && inst.src1.reg == 5) {
|
||||
vgprs[inst.dst.reg].type = EmbeddedFetchValueType::Index;
|
||||
vgpr_is_index[inst.dst.reg] = true;
|
||||
}
|
||||
} else if (IsEmbeddedFetchAttribPropagationAlu(inst)) {
|
||||
if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
|
||||
@@ -431,20 +435,24 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
}
|
||||
}
|
||||
} else if (IsEmbeddedFetchBufferLoad(inst)) {
|
||||
if (IsDecodedVgpr(inst.src0) && inst.src0.reg < vgprs.size() &&
|
||||
vgprs[inst.src0.reg].type == EmbeddedFetchValueType::Index &&
|
||||
if (IsDecodedVgpr(inst.src0) && inst.src0.reg < vgpr_is_index.size() &&
|
||||
vgpr_is_index[inst.src0.reg] &&
|
||||
IsDecodedSgpr(inst.src1) && DecodedSgprReg(inst.src1) < sgprs.size() &&
|
||||
sgprs[DecodedSgprReg(inst.src1)].type == EmbeddedFetchValueType::Buffer) {
|
||||
const auto& buffer = sgprs[DecodedSgprReg(inst.src1)];
|
||||
EmbeddedFetchLoad load;
|
||||
const auto& buffer = sgprs[DecodedSgprReg(inst.src1)];
|
||||
if (data.loads.empty()) {
|
||||
if (!vertex_offset_conflict) {
|
||||
data.vertex_offset_sgpr = vertex_offset_candidate;
|
||||
}
|
||||
if (!instance_offset_conflict) {
|
||||
data.instance_offset_sgpr = instance_offset_candidate;
|
||||
}
|
||||
}
|
||||
auto& load = data.loads.emplace_back();
|
||||
load.pc = inst.pc;
|
||||
load.attrib_id = buffer.attrib_id;
|
||||
load.components = DecodedDstSize(inst);
|
||||
load.prolog_loads = buffer.prolog_loads;
|
||||
if (data.loads.empty() && !offset_conflict) {
|
||||
data.vertex_offset_sgpr = offset_candidate;
|
||||
}
|
||||
data.loads.push_back(load);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -452,7 +460,8 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
if (inst.opcode == Decoder::Opcode::V_MOVRELD_B32) {
|
||||
vector_lanes.clear();
|
||||
} else if (inst.opcode != Decoder::Opcode::V_WRITELANE_B32 && IsDecodedVgpr(inst.dst)) {
|
||||
for (uint32_t i = 0; i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgprs.size();
|
||||
for (uint32_t i = 0;
|
||||
i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgpr_is_index.size();
|
||||
i++) {
|
||||
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg + i);
|
||||
}
|
||||
@@ -462,14 +471,53 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
return data;
|
||||
}
|
||||
|
||||
Decoder::Program DecodeFusedProgram(std::span<const uint32_t> front, std::span<const uint32_t> back,
|
||||
std::vector<uint32_t>& joined_code) {
|
||||
EXIT_IF(back.empty());
|
||||
Decoder::Program result;
|
||||
uint32_t front_words = 0;
|
||||
while (front_words < front.size()) {
|
||||
auto& inst = result.instructions.emplace_back();
|
||||
Decoder::DecodeInstruction(front, front_words, inst);
|
||||
front_words += inst.word_count;
|
||||
if (inst.opcode == Decoder::Opcode::S_SETPC_B64) {
|
||||
EXIT_NOT_IMPLEMENTED(inst.src0.kind != Decoder::OperandKind::Sgpr ||
|
||||
inst.src0.reg != 6u);
|
||||
break;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_ENDPGM);
|
||||
}
|
||||
EXIT_IF(result.instructions.empty() ||
|
||||
result.instructions.back().opcode != Decoder::Opcode::S_SETPC_B64);
|
||||
joined_code.assign(front.begin(), front.begin() + front_words);
|
||||
joined_code.insert(joined_code.end(), back.begin(), back.end());
|
||||
// The merged-stage ABI passes the back shader in s[6:7]. Give that handoff an
|
||||
// ordinary CFG edge, retaining both bodies in one register and LDS lifetime.
|
||||
joined_code[front_words - 1u] = 0xbf820000u; // s_branch to the following instruction
|
||||
result.instructions.back() = {};
|
||||
Decoder::DecodeInstruction(joined_code, front_words - 1u, result.instructions.back());
|
||||
Decoder::Program back_program;
|
||||
Decoder::DecodeProgram(back, back_program);
|
||||
const auto back_pc = front_words * sizeof(uint32_t);
|
||||
for (auto& inst: back_program.instructions) {
|
||||
// A back-stage PC-relative data reference requires its guest code address.
|
||||
EXIT_NOT_IMPLEMENTED(inst.opcode == Decoder::Opcode::S_GETPC_B64);
|
||||
inst.pc += back_pc;
|
||||
inst.branch_target += back_pc;
|
||||
result.instructions.push_back(std::move(inst));
|
||||
}
|
||||
result.code = joined_code;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
if (code.empty()) {
|
||||
EXIT("shader recompiler input is empty\n");
|
||||
}
|
||||
if (options.stage != ShaderType::Compute && options.stage != ShaderType::Vertex &&
|
||||
options.stage != ShaderType::Pixel) {
|
||||
options.stage != ShaderType::Pixel && options.stage != ShaderType::Mesh) {
|
||||
EXIT("shader recompiler received unsupported stage %u\n",
|
||||
static_cast<unsigned>(options.stage));
|
||||
}
|
||||
@@ -486,7 +534,12 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
|
||||
static_cast<uint64_t>(code.size()));
|
||||
|
||||
Decoder::Program decoded;
|
||||
Decoder::DecodeProgram(code, decoded);
|
||||
std::vector<uint32_t> joined_code;
|
||||
if (!options.back_code.empty()) {
|
||||
decoded = DecodeFusedProgram(code, options.back_code, joined_code);
|
||||
} else {
|
||||
Decoder::DecodeProgram(code, decoded);
|
||||
}
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " decode instructions=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
@@ -545,8 +598,7 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
|
||||
const ShaderComputeInputInfo* compute = nullptr;
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex:
|
||||
vertex = options.input_info.vertex;
|
||||
break;
|
||||
case ShaderType::Mesh: vertex = options.input_info.vertex; break;
|
||||
case ShaderType::Pixel:
|
||||
pixel = options.input_info.pixel;
|
||||
break;
|
||||
@@ -602,46 +654,49 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
}
|
||||
if (options.stage == ShaderType::Compute) {
|
||||
const auto lds_barriers =
|
||||
IR::InsertSharedMemoryBarriers(ir, ir.wave_size, *compute);
|
||||
if (lds_barriers.inserted_barriers != 0) {
|
||||
LOGF("%s wave64 LDS synchronization: barriers=%" PRIu32 "\n", GetDumpLabel(options),
|
||||
lds_barriers.inserted_barriers);
|
||||
}
|
||||
}
|
||||
IR::BuildSrtPlan(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
IR::TrackResources(ir);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
if (options.stage == ShaderType::Vertex) {
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
TranslateResult result;
|
||||
result.program = std::move(ir);
|
||||
if (options.dump_ir) {
|
||||
result.decoded_dump = std::move(decoded_dump);
|
||||
result.cfg_dump = CFG::GraphToString(cfg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const IR::SrtRuntime runtime {
|
||||
.user_data = options.user_data,
|
||||
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
||||
.read_memory = options.read_memory,
|
||||
.userdata = options.read_memory_data,
|
||||
.read_specialization_memory = options.read_specialization_memory,
|
||||
};
|
||||
IR::ResourceSnapshot resources;
|
||||
if (!IR::MaterializeResources(ir, runtime, resources)) {
|
||||
EXIT("shader resource materialization failed: stage=%s hash=0x%016" PRIx64 "\n",
|
||||
StageName(options.stage), options.shader_hash);
|
||||
CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options,
|
||||
const IR::ResourceSpecialization& specialization,
|
||||
uint32_t push_data_start_dword) {
|
||||
const auto emit_begin = std::chrono::steady_clock::now();
|
||||
auto& ir = translated.program;
|
||||
IR::ApplyResourceSpecialization(ir, specialization);
|
||||
IR::RemoveIdentities(ir.blocks);
|
||||
IR::EliminateDeadCode(ir.blocks);
|
||||
|
||||
const ShaderVertexInputInfo* vertex = nullptr;
|
||||
const ShaderPixelInputInfo* pixel = nullptr;
|
||||
const ShaderComputeInputInfo* compute = nullptr;
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex:
|
||||
case ShaderType::Mesh: vertex = options.input_info.vertex; break;
|
||||
case ShaderType::Pixel: pixel = options.input_info.pixel; break;
|
||||
case ShaderType::Compute: compute = options.input_info.compute; break;
|
||||
default: EXIT("invalid shader stage\n");
|
||||
}
|
||||
IR::SpecializeResources(ir, resources);
|
||||
|
||||
IR::ShaderInfoOptions info_options;
|
||||
info_options.vertex = vertex;
|
||||
info_options.pixel = pixel;
|
||||
info_options.compute = compute;
|
||||
IR::CollectShaderInfo(ir, info_options);
|
||||
IR::AllocateBindings(ir, options.push_constant_offset);
|
||||
IR::AllocateBindings(ir, push_data_start_dword);
|
||||
Spirv::AnalyzeProgramRequirements(ir);
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(cfg, ir);
|
||||
ir_dump = MakeIrDump(translated.cfg_dump, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
@@ -649,17 +704,19 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
auto spirv = Spirv::EmitProgram(ir, resources, options.input_info);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(spirv.size()), phase_ms());
|
||||
static_cast<uint64_t>(spirv.size()),
|
||||
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - emit_begin)
|
||||
.count()));
|
||||
CompileResult result;
|
||||
result.spirv = std::move(spirv);
|
||||
result.program = std::move(ir);
|
||||
result.resources = std::move(resources);
|
||||
result.spirv = std::move(spirv);
|
||||
result.program = std::move(ir);
|
||||
if (options.dump_ir) {
|
||||
result.decoded_dump = std::move(decoded_dump);
|
||||
result.decoded_dump = std::move(translated.decoded_dump);
|
||||
result.ir_dump = std::move(ir_dump);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -17,27 +17,33 @@ struct CompileOptions {
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t scratch_dwords = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
const char* dump_label = nullptr;
|
||||
std::span<const uint32_t> user_data;
|
||||
IR::SrtMemoryReader read_memory = nullptr;
|
||||
IR::SrtMemoryReader read_specialization_memory = nullptr;
|
||||
void* read_memory_data = nullptr;
|
||||
std::span<const uint32_t> back_code;
|
||||
ShaderStageInputInfo input_info;
|
||||
};
|
||||
|
||||
struct CompileResult {
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string decoded_dump;
|
||||
std::string ir_dump;
|
||||
IR::Program program;
|
||||
IR::ResourceSnapshot resources;
|
||||
struct TranslateResult {
|
||||
IR::Program program;
|
||||
std::string decoded_dump;
|
||||
std::string cfg_dump;
|
||||
};
|
||||
|
||||
[[nodiscard]] CompileResult Recompile(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
struct CompileResult {
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string decoded_dump;
|
||||
std::string ir_dump;
|
||||
IR::Program program;
|
||||
};
|
||||
|
||||
[[nodiscard]] TranslateResult TranslateProgram(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
[[nodiscard]] CompileResult CompileProgram(TranslateResult translated,
|
||||
const CompileOptions& options,
|
||||
const IR::ResourceSpecialization& specialization,
|
||||
uint32_t push_data_start_dword = 0);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
@@ -31,6 +32,10 @@ uint32_t Builder::AllocateId() {
|
||||
return m_next_id++;
|
||||
}
|
||||
|
||||
void Builder::RequireVersion(uint32_t version) {
|
||||
m_version = std::max(m_version, version);
|
||||
}
|
||||
|
||||
void Builder::RequireCapability(uint32_t capability) {
|
||||
if (m_required_capabilities.insert(capability).second) {
|
||||
AddCapability({capability});
|
||||
@@ -190,6 +195,18 @@ void Builder::AddEntryPoint(uint32_t execution_model, uint32_t entry_point, cons
|
||||
std::vector<uint32_t> operands = {execution_model, entry_point};
|
||||
AppendString(operands, name);
|
||||
operands.insert(operands.end(), interfaces.begin(), interfaces.end());
|
||||
if (m_version >= 0x00010400u) {
|
||||
for (size_t offset = 0; offset < m_declarations.size();) {
|
||||
const auto count = m_declarations[offset] >> 16u;
|
||||
if ((m_declarations[offset] & 0xffffu) == 59u) {
|
||||
const auto id = m_declarations[offset + 2u];
|
||||
if (std::find(interfaces.begin(), interfaces.end(), id) == interfaces.end()) {
|
||||
operands.push_back(id);
|
||||
}
|
||||
}
|
||||
offset += count;
|
||||
}
|
||||
}
|
||||
AppendInstruction(m_entry_points, 15u, operands);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
KYTY_CLASS_DEFAULT_COPY(Builder);
|
||||
|
||||
uint32_t AllocateId();
|
||||
void RequireVersion(uint32_t version);
|
||||
void RequireCapability(uint32_t capability);
|
||||
void RequireExtension(const char* name);
|
||||
uint32_t Import(const char* name);
|
||||
|
||||
@@ -81,13 +81,13 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
|
||||
return image.indirect_mapping_capacity != 0u;
|
||||
return image.indirect_search_iterations != 0u;
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
|
||||
Expect(Kind::UserData);
|
||||
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
|
||||
Expect(Kind::ShaderData);
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
@@ -104,21 +104,19 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
Fail(program, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
|
||||
if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 ||
|
||||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
|
||||
IR::NativePushConstantSize ||
|
||||
const auto has_shader_data_storage = present[static_cast<size_t>(Kind::ShaderData)];
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.memory_offset_count != program.info.buffers.size() ||
|
||||
(!has_user_storage &&
|
||||
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
|
||||
(has_user_storage && program.bindings.push_constant_size != 0) ||
|
||||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
Fail(program, "native user-data layout is inconsistent");
|
||||
Fail(program, "native shader-data layout is inconsistent");
|
||||
}
|
||||
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
@@ -191,6 +189,10 @@ void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
const auto MarkBallot = [&] { requirements.subgroup_ballot = true; };
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
if (IR::BufferAccessOf(inst.GetOpcode()) == IR::BufferAccess::Atomic &&
|
||||
inst.GetType() == IR::Type::U64) {
|
||||
requirements.buffer_int64_atomics = true;
|
||||
}
|
||||
const auto address_access = IR::AddressOpcodeInfoOf(inst.GetOpcode()).access;
|
||||
if (address_access != IR::AddressAccess::None) {
|
||||
const auto memory_index = inst.Flags<IR::MemoryFlags>().index;
|
||||
@@ -234,7 +236,8 @@ void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
if (kind != IR::ResourceKind::Lds && kind != IR::ResourceKind::Gds) {
|
||||
Fail(program, "shared operation has invalid resource kind");
|
||||
}
|
||||
if (program.stage != ShaderType::Compute && kind == IR::ResourceKind::Lds) {
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Mesh &&
|
||||
kind == IR::ResourceKind::Lds) {
|
||||
requirements.function_lds = true;
|
||||
}
|
||||
if (shared_access == IR::SharedAccess::Append ||
|
||||
@@ -257,7 +260,6 @@ void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
break;
|
||||
}
|
||||
case IR::ValueOpcode::DppUpdateU32:
|
||||
case IR::ValueOpcode::WqmMask:
|
||||
case IR::ValueOpcode::WriteLane: {
|
||||
MarkBallot();
|
||||
requirements.subgroup_local_invocation_id = true;
|
||||
@@ -302,12 +304,11 @@ void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
}
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info) {
|
||||
using namespace Emitter;
|
||||
|
||||
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
|
||||
program.stage != ShaderType::Pixel) {
|
||||
program.stage != ShaderType::Pixel && program.stage != ShaderType::Mesh) {
|
||||
Fail(program, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
|
||||
}
|
||||
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
|
||||
@@ -315,17 +316,15 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
!program.spirv_requirements.has_value()) {
|
||||
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
|
||||
}
|
||||
if (!IR::ValidateResourceSnapshot(program, resources)) {
|
||||
Fail(program, "resource snapshot is inconsistent with the native shader plan");
|
||||
}
|
||||
if (!IR::ValidateResourceSpecialization(program, resources)) {
|
||||
Fail(program, "resource specialization is inconsistent with the native shader plan");
|
||||
}
|
||||
ValidateNativeProgram(program);
|
||||
IR::ValidateProgram(program, true);
|
||||
EmitterState state(program, resources, input_info);
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
EmitterState state(program, input_info);
|
||||
state.stage = program.stage;
|
||||
const auto* workgroup = ShaderWorkgroupInput(program.stage, input_info);
|
||||
state.lane_count =
|
||||
workgroup != nullptr && program.wave_size == 64u && workgroup->host_subgroup_size == 32u
|
||||
? 2u
|
||||
: 1u;
|
||||
state.inputs.reserve(program.info.inputs.size());
|
||||
state.outputs.reserve(program.info.outputs.size());
|
||||
state.interface_variables.reserve(program.info.inputs.size() + program.info.outputs.size());
|
||||
@@ -333,7 +332,9 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
AllocateInputVariables(state);
|
||||
AllocateOutputVariables(state);
|
||||
DefineModule(state);
|
||||
EmitProgram(state, program);
|
||||
EmitProgram(state);
|
||||
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
|
||||
state.interface_variables);
|
||||
|
||||
auto binary = state.builder.Build();
|
||||
if (binary.empty()) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
void AnalyzeProgramRequirements(IR::Program& program);
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -10,26 +10,6 @@ struct Pair {
|
||||
uint32_t high = 0;
|
||||
};
|
||||
|
||||
uint32_t NewUnary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, value});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t NewBinary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
|
||||
uint32_t rhs) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, lhs, rhs});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t NewSelect(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
|
||||
uint32_t false_value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
|
||||
return result;
|
||||
}
|
||||
|
||||
Pair ExtractPair(EmitterState& state, uint32_t value) {
|
||||
Pair result {state.builder.AllocateId(), state.builder.AllocateId()};
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), result.low, value, 0});
|
||||
@@ -45,20 +25,20 @@ uint32_t MakePair(EmitterState& state, uint32_t low, uint32_t high) {
|
||||
|
||||
uint32_t CompareEqual64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
|
||||
bool not_equal) {
|
||||
const auto compare = NewBinary(state, not_equal ? OpINotEqual : OpIEqual,
|
||||
const auto compare = Binary(state, not_equal ? OpINotEqual : OpIEqual,
|
||||
TypeBoolVector(state, 2), lhs_value, rhs_value);
|
||||
return NewUnary(state, not_equal ? OpAny : OpAll, TypeBool(state), compare);
|
||||
return Unary(state, not_equal ? OpAny : OpAll, TypeBool(state), compare);
|
||||
}
|
||||
|
||||
uint32_t CompareOrdered64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
|
||||
uint32_t high_compare, uint32_t low_compare) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto high_equal = NewBinary(state, OpIEqual, TypeBool(state), lhs.high, rhs.high);
|
||||
const auto high_result = NewBinary(state, high_compare, TypeBool(state), lhs.high, rhs.high);
|
||||
const auto low_result = NewBinary(state, low_compare, TypeBool(state), lhs.low, rhs.low);
|
||||
const auto low_path = NewBinary(state, OpLogicalAnd, TypeBool(state), high_equal, low_result);
|
||||
return NewBinary(state, OpLogicalOr, TypeBool(state), high_result, low_path);
|
||||
const auto high_equal = Binary(state, OpIEqual, TypeBool(state), lhs.high, rhs.high);
|
||||
const auto high_result = Binary(state, high_compare, TypeBool(state), lhs.high, rhs.high);
|
||||
const auto low_result = Binary(state, low_compare, TypeBool(state), lhs.low, rhs.low);
|
||||
const auto low_path = Binary(state, OpLogicalAnd, TypeBool(state), high_equal, low_result);
|
||||
return Binary(state, OpLogicalOr, TypeBool(state), high_result, low_path);
|
||||
}
|
||||
|
||||
uint32_t EmitAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
|
||||
@@ -70,20 +50,20 @@ uint32_t EmitAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value)
|
||||
state.builder.AddFunction({OpIAddCarry, TypeU32Pair(state), low_pair, lhs.low, rhs.low});
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, low_pair, 0});
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), carry, low_pair, 1});
|
||||
const auto high0 = NewBinary(state, OpIAdd, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = NewBinary(state, OpIAdd, TypeU32(state), high0, carry);
|
||||
const auto high0 = Binary(state, OpIAdd, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = Binary(state, OpIAdd, TypeU32(state), high0, carry);
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
|
||||
uint32_t EmitSub64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto low = NewBinary(state, OpISub, TypeU32(state), lhs.low, rhs.low);
|
||||
const auto borrow = NewBinary(state, OpULessThan, TypeBool(state), lhs.low, rhs.low);
|
||||
const auto low = Binary(state, OpISub, TypeU32(state), lhs.low, rhs.low);
|
||||
const auto borrow = Binary(state, OpULessThan, TypeBool(state), lhs.low, rhs.low);
|
||||
const auto borrow_u32 =
|
||||
NewSelect(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0));
|
||||
const auto high0 = NewBinary(state, OpISub, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = NewBinary(state, OpISub, TypeU32(state), high0, borrow_u32);
|
||||
Select(state, TypeU32(state), borrow, ConstantU32(state, 1), ConstantU32(state, 0));
|
||||
const auto high0 = Binary(state, OpISub, TypeU32(state), lhs.high, rhs.high);
|
||||
const auto high = Binary(state, OpISub, TypeU32(state), high0, borrow_u32);
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
|
||||
@@ -93,27 +73,27 @@ uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signe
|
||||
uint32_t lhs_operand = lhs;
|
||||
uint32_t rhs_operand = rhs;
|
||||
if (signed_value) {
|
||||
lhs_operand = NewUnary(state, OpBitcast, TypeI32(state), lhs);
|
||||
rhs_operand = NewUnary(state, OpBitcast, TypeI32(state), rhs);
|
||||
lhs_operand = Unary(state, OpBitcast, TypeI32(state), lhs);
|
||||
rhs_operand = Unary(state, OpBitcast, TypeI32(state), rhs);
|
||||
}
|
||||
const auto extended = state.builder.AllocateId();
|
||||
state.builder.AddFunction({signed_value ? OpSMulExtended : OpUMulExtended, pair_type, extended,
|
||||
lhs_operand, rhs_operand});
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, operand_type, high, extended, 1});
|
||||
return signed_value ? NewUnary(state, OpBitcast, TypeU32(state), high) : high;
|
||||
return signed_value ? Unary(state, OpBitcast, TypeU32(state), high) : high;
|
||||
}
|
||||
|
||||
uint32_t EmitMul64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
|
||||
const auto lhs = ExtractPair(state, lhs_value);
|
||||
const auto rhs = ExtractPair(state, rhs_value);
|
||||
const auto low = NewBinary(state, OpIMul, TypeU32(state), lhs.low, rhs.low);
|
||||
const auto low = Binary(state, OpIMul, TypeU32(state), lhs.low, rhs.low);
|
||||
const auto high0 = EmitMulHigh(state, lhs.low, rhs.low, false);
|
||||
const auto high1 = NewBinary(state, OpIMul, TypeU32(state), lhs.low, rhs.high);
|
||||
const auto high2 = NewBinary(state, OpIMul, TypeU32(state), lhs.high, rhs.low);
|
||||
const auto high1 = Binary(state, OpIMul, TypeU32(state), lhs.low, rhs.high);
|
||||
const auto high2 = Binary(state, OpIMul, TypeU32(state), lhs.high, rhs.low);
|
||||
return MakePair(state, low,
|
||||
NewBinary(state, OpIAdd, TypeU32(state),
|
||||
NewBinary(state, OpIAdd, TypeU32(state), high0, high1), high2));
|
||||
Binary(state, OpIAdd, TypeU32(state),
|
||||
Binary(state, OpIAdd, TypeU32(state), high0, high1), high2));
|
||||
}
|
||||
|
||||
uint32_t EmitShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint32_t shift) {
|
||||
@@ -126,33 +106,33 @@ uint32_t EmitShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint3
|
||||
EmitShiftRightLogicalU64Values(state, pair.low, pair.high, shift, low, high);
|
||||
} else {
|
||||
const auto amount =
|
||||
NewBinary(state, OpBitwiseAnd, TypeU32(state), shift, ConstantU32(state, 63));
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), shift, ConstantU32(state, 63));
|
||||
const auto word_shift =
|
||||
NewBinary(state, OpBitwiseAnd, TypeU32(state), amount, ConstantU32(state, 31));
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), amount, ConstantU32(state, 31));
|
||||
const auto at_least_32 =
|
||||
NewBinary(state, OpUGreaterThanEqual, TypeBool(state), amount, ConstantU32(state, 32));
|
||||
Binary(state, OpUGreaterThanEqual, TypeBool(state), amount, ConstantU32(state, 32));
|
||||
const auto nonzero =
|
||||
NewBinary(state, OpINotEqual, TypeBool(state), amount, ConstantU32(state, 0));
|
||||
Binary(state, OpINotEqual, TypeBool(state), amount, ConstantU32(state, 0));
|
||||
const auto high_shifted =
|
||||
NewBinary(state, OpShiftRightArithmetic, TypeU32(state), pair.high, word_shift);
|
||||
Binary(state, OpShiftRightArithmetic, TypeU32(state), pair.high, word_shift);
|
||||
const auto low_shifted =
|
||||
NewBinary(state, OpShiftRightLogical, TypeU32(state), pair.low, word_shift);
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), pair.low, word_shift);
|
||||
const auto carry_count =
|
||||
NewBinary(state, OpBitwiseAnd, TypeU32(state),
|
||||
NewBinary(state, OpISub, TypeU32(state), ConstantU32(state, 32), word_shift),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state),
|
||||
Binary(state, OpISub, TypeU32(state), ConstantU32(state, 32), word_shift),
|
||||
ConstantU32(state, 31));
|
||||
const auto carry =
|
||||
NewSelect(state, TypeU32(state), nonzero,
|
||||
NewBinary(state, OpShiftLeftLogical, TypeU32(state), pair.high, carry_count),
|
||||
Select(state, TypeU32(state), nonzero,
|
||||
Binary(state, OpShiftLeftLogical, TypeU32(state), pair.high, carry_count),
|
||||
ConstantU32(state, 0));
|
||||
const auto low_below_32 = NewBinary(state, OpBitwiseOr, TypeU32(state), low_shifted, carry);
|
||||
const auto low_below_32 = Binary(state, OpBitwiseOr, TypeU32(state), low_shifted, carry);
|
||||
const auto negative =
|
||||
NewBinary(state, OpSLessThan, TypeBool(state), pair.high, ConstantU32(state, 0));
|
||||
const auto sign_fill = NewSelect(state, TypeU32(state), negative,
|
||||
Binary(state, OpSLessThan, TypeBool(state), pair.high, ConstantU32(state, 0));
|
||||
const auto sign_fill = Select(state, TypeU32(state), negative,
|
||||
ConstantU32(state, 0xffffffffu), ConstantU32(state, 0));
|
||||
return MakePair(state,
|
||||
NewSelect(state, TypeU32(state), at_least_32, high_shifted, low_below_32),
|
||||
NewSelect(state, TypeU32(state), at_least_32, sign_fill, high_shifted));
|
||||
Select(state, TypeU32(state), at_least_32, high_shifted, low_below_32),
|
||||
Select(state, TypeU32(state), at_least_32, sign_fill, high_shifted));
|
||||
}
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
@@ -165,36 +145,36 @@ uint32_t EmitConstantShift64(EmitterState& state, uint32_t opcode, uint32_t valu
|
||||
const auto pair = ExtractPair(state, value);
|
||||
if (opcode == OpShiftLeftLogical) {
|
||||
if (shift < 32u) {
|
||||
const auto low = NewBinary(state, OpShiftLeftLogical, TypeU32(state), pair.low,
|
||||
const auto low = Binary(state, OpShiftLeftLogical, TypeU32(state), pair.low,
|
||||
ConstantU32(state, shift));
|
||||
const auto high = NewBinary(state, OpBitwiseOr, TypeU32(state),
|
||||
NewBinary(state, OpShiftLeftLogical, TypeU32(state),
|
||||
const auto high = Binary(state, OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, OpShiftLeftLogical, TypeU32(state),
|
||||
pair.high, ConstantU32(state, shift)),
|
||||
NewBinary(state, OpShiftRightLogical, TypeU32(state),
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state),
|
||||
pair.low, ConstantU32(state, 32u - shift)));
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
return MakePair(state, ConstantU32(state, 0),
|
||||
shift == 32u ? pair.low
|
||||
: NewBinary(state, OpShiftLeftLogical, TypeU32(state),
|
||||
: Binary(state, OpShiftLeftLogical, TypeU32(state),
|
||||
pair.low, ConstantU32(state, shift - 32u)));
|
||||
}
|
||||
if (shift < 32u) {
|
||||
const auto low = NewBinary(state, OpBitwiseOr, TypeU32(state),
|
||||
NewBinary(state, OpShiftRightLogical, TypeU32(state), pair.low,
|
||||
const auto low = Binary(state, OpBitwiseOr, TypeU32(state),
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), pair.low,
|
||||
ConstantU32(state, shift)),
|
||||
NewBinary(state, OpShiftLeftLogical, TypeU32(state), pair.high,
|
||||
Binary(state, OpShiftLeftLogical, TypeU32(state), pair.high,
|
||||
ConstantU32(state, 32u - shift)));
|
||||
const auto high =
|
||||
NewBinary(state, opcode, TypeU32(state), pair.high, ConstantU32(state, shift));
|
||||
Binary(state, opcode, TypeU32(state), pair.high, ConstantU32(state, shift));
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
const auto high = opcode == OpShiftRightArithmetic
|
||||
? NewBinary(state, OpShiftRightArithmetic, TypeU32(state), pair.high,
|
||||
? Binary(state, OpShiftRightArithmetic, TypeU32(state), pair.high,
|
||||
ConstantU32(state, 31u))
|
||||
: ConstantU32(state, 0);
|
||||
const auto low = shift == 32u ? pair.high
|
||||
: NewBinary(state, opcode, TypeU32(state), pair.high,
|
||||
: Binary(state, opcode, TypeU32(state), pair.high,
|
||||
ConstantU32(state, shift - 32u));
|
||||
return MakePair(state, low, high);
|
||||
}
|
||||
@@ -207,12 +187,12 @@ uint32_t EmitFindMsb64(EmitterState& state, uint32_t value) {
|
||||
{OpExtInst, TypeI32(state), high_i, GlslStd450(state), GlslFindUMsb, pair.high});
|
||||
state.builder.AddFunction(
|
||||
{OpExtInst, TypeI32(state), low_i, GlslStd450(state), GlslFindUMsb, pair.low});
|
||||
const auto high = NewUnary(state, OpBitcast, TypeU32(state), high_i);
|
||||
const auto low = NewUnary(state, OpBitcast, TypeU32(state), low_i);
|
||||
const auto high = Unary(state, OpBitcast, TypeU32(state), high_i);
|
||||
const auto low = Unary(state, OpBitcast, TypeU32(state), low_i);
|
||||
const auto high_nonzero =
|
||||
NewBinary(state, OpINotEqual, TypeBool(state), pair.high, ConstantU32(state, 0));
|
||||
return NewSelect(state, TypeU32(state), high_nonzero,
|
||||
NewBinary(state, OpIAdd, TypeU32(state), high, ConstantU32(state, 32)), low);
|
||||
Binary(state, OpINotEqual, TypeBool(state), pair.high, ConstantU32(state, 0));
|
||||
return Select(state, TypeU32(state), high_nonzero,
|
||||
Binary(state, OpIAdd, TypeU32(state), high, ConstantU32(state, 32)), low);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value,
|
||||
@@ -226,10 +206,10 @@ uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bo
|
||||
uint32_t EmitMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value) {
|
||||
const auto minimum = EmitMinMax3(state, a, b, c, signed_value, false);
|
||||
const auto maximum = EmitMinMax3(state, a, b, c, signed_value, true);
|
||||
const auto ab = NewBinary(state, OpIAdd, TypeU32(state), a, b);
|
||||
const auto abc = NewBinary(state, OpIAdd, TypeU32(state), ab, c);
|
||||
return NewBinary(state, OpISub, TypeU32(state),
|
||||
NewBinary(state, OpISub, TypeU32(state), abc, minimum), maximum);
|
||||
const auto ab = Binary(state, OpIAdd, TypeU32(state), a, b);
|
||||
const auto abc = Binary(state, OpIAdd, TypeU32(state), ab, c);
|
||||
return Binary(state, OpISub, TypeU32(state),
|
||||
Binary(state, OpISub, TypeU32(state), abc, minimum), maximum);
|
||||
}
|
||||
|
||||
uint32_t EmitFMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool max_value) {
|
||||
@@ -242,11 +222,11 @@ uint32_t EmitFMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
const auto max_ab = EmitMinMaxF32Value(state, a, b, true);
|
||||
const auto high_min = EmitMinMaxF32Value(state, max_ab, c, false);
|
||||
const auto median = EmitMinMaxF32Value(state, min_ab, high_min, true);
|
||||
const auto nan_ab = NewBinary(state, OpLogicalOr, TypeBool(state),
|
||||
const auto nan_ab = Binary(state, OpLogicalOr, TypeBool(state),
|
||||
EmitClassifyF32(state, a).nan, EmitClassifyF32(state, b).nan);
|
||||
const auto any_nan =
|
||||
NewBinary(state, OpLogicalOr, TypeBool(state), nan_ab, EmitClassifyF32(state, c).nan);
|
||||
return NewSelect(state, TypeF32(state), any_nan, min3, median);
|
||||
Binary(state, OpLogicalOr, TypeBool(state), nan_ab, EmitClassifyF32(state, c).nan);
|
||||
return Select(state, TypeF32(state), any_nan, min3, median);
|
||||
}
|
||||
|
||||
uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
|
||||
@@ -270,37 +250,30 @@ uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
}
|
||||
const auto nan = EmitClassifyF32(state, src).nan;
|
||||
if (signed_value) {
|
||||
const auto below = NewBinary(state, OpFOrdLessThanEqual, TypeBool(state), src,
|
||||
const auto below = Binary(state, OpFOrdLessThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, 0xcf000000u));
|
||||
const auto above = NewBinary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
const auto above = Binary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, 0x4f000000u));
|
||||
const auto high =
|
||||
NewSelect(state, TypeU32(state), above, ConstantU32(state, 0x7fffffffu), converted_raw);
|
||||
Select(state, TypeU32(state), above, ConstantU32(state, 0x7fffffffu), converted_raw);
|
||||
const auto low =
|
||||
NewSelect(state, TypeU32(state), below, ConstantU32(state, 0x80000000u), high);
|
||||
return NewSelect(state, TypeU32(state), nan, ConstantU32(state, 0), low);
|
||||
Select(state, TypeU32(state), below, ConstantU32(state, 0x80000000u), high);
|
||||
return Select(state, TypeU32(state), nan, ConstantU32(state, 0), low);
|
||||
}
|
||||
const auto below =
|
||||
NewBinary(state, OpFOrdLessThanEqual, TypeBool(state), src, ConstantF32(state, 0));
|
||||
const auto above = NewBinary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
Binary(state, OpFOrdLessThanEqual, TypeBool(state), src, ConstantF32(state, 0));
|
||||
const auto above = Binary(state, OpFOrdGreaterThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, 0x4f800000u));
|
||||
const auto zero = NewBinary(state, OpLogicalOr, TypeBool(state), nan, below);
|
||||
const auto zero = Binary(state, OpLogicalOr, TypeBool(state), nan, below);
|
||||
const auto high =
|
||||
NewSelect(state, TypeU32(state), above, ConstantU32(state, 0xffffffffu), converted_raw);
|
||||
return NewSelect(state, TypeU32(state), zero, ConstantU32(state, 0), high);
|
||||
Select(state, TypeU32(state), above, ConstantU32(state, 0xffffffffu), converted_raw);
|
||||
return Select(state, TypeU32(state), zero, ConstantU32(state, 0), high);
|
||||
}
|
||||
|
||||
uint32_t EmitPackHalf(EmitterState& state, uint32_t src) {
|
||||
return EmitExt(state, TypeU32(state), GlslPackHalf2x16, {src});
|
||||
}
|
||||
|
||||
uint32_t EmitUnpackHalf(EmitterState& state, uint32_t bits) {
|
||||
const auto pair = EmitExt(state, TypeF32Vector(state, 2), GlslUnpackHalf2x16, {bits});
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeF32(state), result, pair, 0});
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
@@ -341,7 +314,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::ConvertF32F16:
|
||||
ctx.Define(inst, EmitUnpackHalf(state, ctx.Arg(inst, 0)));
|
||||
ctx.Define(inst, EmitF16BitsToF32(state, ctx.Arg(inst, 0)));
|
||||
return true;
|
||||
case IR::ValueOpcode::ConvertS32F32:
|
||||
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), true));
|
||||
@@ -350,7 +323,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), false));
|
||||
return true;
|
||||
case IR::ValueOpcode::ConvertF32S32: {
|
||||
const auto signed_value = NewUnary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 0));
|
||||
const auto signed_value = Unary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 0));
|
||||
ctx.Emit(inst, OpConvertSToF, IR::Type::F32, {signed_value});
|
||||
return true;
|
||||
}
|
||||
@@ -394,9 +367,9 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::PackFloat2x16Rtz: {
|
||||
const auto low = EmitF32ToF16RtzBits(state, ctx.Arg(inst, 0));
|
||||
const auto high =
|
||||
NewBinary(state, OpShiftLeftLogical, TypeU32(state),
|
||||
Binary(state, OpShiftLeftLogical, TypeU32(state),
|
||||
EmitF32ToF16RtzBits(state, ctx.Arg(inst, 1)), ConstantU32(state, 16));
|
||||
ctx.Define(inst, NewBinary(state, OpBitwiseOr, TypeU32(state), low, high));
|
||||
ctx.Define(inst, Binary(state, OpBitwiseOr, TypeU32(state), low, high));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::FPAbs32:
|
||||
@@ -458,10 +431,10 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return true;
|
||||
case IR::ValueOpcode::IAbs32: {
|
||||
const auto value = ctx.Arg(inst, 0);
|
||||
const auto neg = NewUnary(state, OpSNegate, TypeU32(state), value);
|
||||
const auto neg = Unary(state, OpSNegate, TypeU32(state), value);
|
||||
const auto negative =
|
||||
NewBinary(state, OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
|
||||
ctx.Define(inst, NewSelect(state, TypeU32(state), negative, neg, value));
|
||||
Binary(state, OpSLessThan, TypeBool(state), value, ConstantU32(state, 0));
|
||||
ctx.Define(inst, Select(state, TypeU32(state), negative, neg, value));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::ShiftLeftLogical32: return binary(OpShiftLeftLogical, IR::Type::U32);
|
||||
@@ -496,25 +469,25 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::BitwiseXor32: return binary(OpBitwiseXor, IR::Type::U32);
|
||||
case IR::ValueOpcode::BitwiseNot32: return unary(OpNot, IR::Type::U32);
|
||||
case IR::ValueOpcode::BitwiseAnd64:
|
||||
ctx.Define(inst, NewBinary(state, OpBitwiseAnd, TypeU64(state), ctx.Arg(inst, 0),
|
||||
ctx.Define(inst, Binary(state, OpBitwiseAnd, TypeU64(state), ctx.Arg(inst, 0),
|
||||
ctx.Arg(inst, 1)));
|
||||
return true;
|
||||
case IR::ValueOpcode::BitReverse32: return unary(OpBitReverse, IR::Type::U32);
|
||||
case IR::ValueOpcode::BitCount32: return unary(OpBitCount, IR::Type::U32);
|
||||
case IR::ValueOpcode::BitCount64: {
|
||||
const auto pair =
|
||||
ExtractPair(state, NewUnary(state, OpBitCount, TypeU64(state), ctx.Arg(inst, 0)));
|
||||
ctx.Define(inst, NewBinary(state, OpIAdd, TypeU32(state), pair.low, pair.high));
|
||||
ExtractPair(state, Unary(state, OpBitCount, TypeU64(state), ctx.Arg(inst, 0)));
|
||||
ctx.Define(inst, Binary(state, OpIAdd, TypeU32(state), pair.low, pair.high));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::FindILsb32: {
|
||||
const auto value = EmitExt(state, TypeI32(state), GlslFindILsb, {ctx.Arg(inst, 0)});
|
||||
ctx.Define(inst, NewUnary(state, OpBitcast, TypeU32(state), value));
|
||||
ctx.Define(inst, Unary(state, OpBitcast, TypeU32(state), value));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::FindUMsb32: {
|
||||
const auto value = EmitExt(state, TypeI32(state), GlslFindUMsb, {ctx.Arg(inst, 0)});
|
||||
ctx.Define(inst, NewUnary(state, OpBitcast, TypeU32(state), value));
|
||||
ctx.Define(inst, Unary(state, OpBitcast, TypeU32(state), value));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::FindUMsb64:
|
||||
@@ -638,13 +611,13 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return true;
|
||||
case IR::ValueOpcode::FPRecip32: {
|
||||
const auto source = EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0));
|
||||
ctx.Define(inst, NewBinary(state, OpFDiv, TypeF32(state),
|
||||
ctx.Define(inst, Binary(state, OpFDiv, TypeF32(state),
|
||||
ConstantF32(state, 0x3f800000u), source));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::FPRecipIFlag32:
|
||||
// Integer-to-float inputs used by IFLAG cannot be denormal.
|
||||
ctx.Define(inst, NewBinary(state, OpFDiv, TypeF32(state),
|
||||
ctx.Define(inst, Binary(state, OpFDiv, TypeF32(state),
|
||||
ConstantF32(state, 0x3f800000u), ctx.Arg(inst, 0)));
|
||||
return true;
|
||||
case IR::ValueOpcode::FPRecipSqrt32:
|
||||
@@ -659,7 +632,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::FPCos: {
|
||||
auto source = EmitTrigCycleF32(state, ctx.Arg(inst, 0), op == IR::ValueOpcode::FPSin);
|
||||
source =
|
||||
NewBinary(state, OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
|
||||
Binary(state, OpFMul, TypeF32(state), source, ConstantF32(state, 0x40c90fdbu));
|
||||
ctx.Define(inst, EmitExt(state, TypeF32(state),
|
||||
op == IR::ValueOpcode::FPSin ? GlslSin : GlslCos, {source}));
|
||||
return true;
|
||||
@@ -673,7 +646,7 @@ bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
|
||||
return true;
|
||||
case IR::ValueOpcode::FPLdexp: {
|
||||
const auto exponent = NewUnary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 1));
|
||||
const auto exponent = Unary(state, OpBitcast, TypeI32(state), ctx.Arg(inst, 1));
|
||||
ctx.Define(inst,
|
||||
EmitExt(state, TypeF32(state), GlslLdexp, {ctx.Arg(inst, 0), exponent}));
|
||||
return true;
|
||||
|
||||
@@ -215,9 +215,9 @@ uint32_t EmitLogicalNotBool(EmitterState& state, uint32_t value) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
|
||||
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits) {
|
||||
F32Class cls;
|
||||
cls.bits = EmitBitcastF32ToU32(state, value);
|
||||
cls.bits = bits;
|
||||
const auto abs_bits = EmitAndConstant(state, cls.bits, 0x7fffffffu);
|
||||
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
|
||||
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
|
||||
@@ -228,6 +228,10 @@ F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
|
||||
return cls;
|
||||
}
|
||||
|
||||
F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
|
||||
return EmitClassifyF32Bits(state, EmitBitcastF32ToU32(state, value));
|
||||
}
|
||||
|
||||
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
|
||||
uint32_t class_match) {
|
||||
const auto selected =
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr) {
|
||||
if (state.stage != ShaderType::Pixel) {
|
||||
return attr;
|
||||
}
|
||||
return ShaderPixelParameterMappedLocation(*state.input_info.pixel, attr);
|
||||
}
|
||||
|
||||
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
|
||||
std::array<uint32_t, 32> active_inputs {};
|
||||
uint32_t active_count = 0;
|
||||
@@ -86,8 +79,8 @@ uint32_t ConstantU32(EmitterState& state, uint32_t value);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
DescriptorResourceBinding ResourceForDescriptor(const EmitterState& state,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource) {
|
||||
uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind,
|
||||
uint32_t resource) {
|
||||
const auto* descriptor = IR::FindBinding(state.program.bindings, kind);
|
||||
if (descriptor == nullptr) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource, "descriptor group was not allocated");
|
||||
@@ -98,7 +91,7 @@ DescriptorResourceBinding ResourceForDescriptor(const EmitterState& state,
|
||||
ExitDescriptorBindingFailure(state, kind, resource,
|
||||
"resource is absent from descriptor group");
|
||||
}
|
||||
return {descriptor, static_cast<uint32_t>(found - descriptor->resources.begin())};
|
||||
return static_cast<uint32_t>(found - descriptor->resources.begin());
|
||||
}
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
@@ -114,68 +107,57 @@ uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
return pointer;
|
||||
}
|
||||
|
||||
ImageViewKind ImageViewKindFromDimension(Decoder::ImageDimension dimension) {
|
||||
switch (dimension) {
|
||||
case Decoder::ImageDimension::Dim1D: return ImageViewKind::Dim1D;
|
||||
case Decoder::ImageDimension::Dim1DArray: return ImageViewKind::Dim1DArray;
|
||||
case Decoder::ImageDimension::Dim2DArray: return ImageViewKind::Dim2DArray;
|
||||
case Decoder::ImageDimension::Dim3D: return ImageViewKind::Dim3D;
|
||||
case Decoder::ImageDimension::Dim2DMsaa: return ImageViewKind::Dim2DMsaa;
|
||||
case Decoder::ImageDimension::Dim2DMsaaArray: return ImageViewKind::Dim2DMsaaArray;
|
||||
default: return ImageViewKind::Dim2D;
|
||||
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension) {
|
||||
for (const auto& info: ImageDimensions) {
|
||||
if (info.dimension == dimension) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
EXIT("SPIR-V image dimension %u is invalid\n", static_cast<uint32_t>(dimension));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t use_pc) {
|
||||
(void)state;
|
||||
(void)use_pc;
|
||||
return ImageViewKindFromDimension(mem.image_dimension);
|
||||
}
|
||||
|
||||
ImageViewKind StorageImageViewKind(const IR::MemoryInfo& mem) {
|
||||
return ImageViewKindFromDimension(mem.image_dimension);
|
||||
}
|
||||
|
||||
uint32_t ImageViewCoordinateComponents(ImageViewKind view) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D: return 1u;
|
||||
case ImageViewKind::Dim1DArray:
|
||||
case ImageViewKind::Dim2D: return 2u;
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim2DMsaaArray:
|
||||
case ImageViewKind::Dim3D: return 3u;
|
||||
case ImageViewKind::Dim2DMsaa: return 2u;
|
||||
default: return 0u;
|
||||
uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass numeric_class) {
|
||||
switch (numeric_class) {
|
||||
case Prospero::TextureNumericClass::Float: return TypeF32(state);
|
||||
case Prospero::TextureNumericClass::Uint: return TypeU32(state);
|
||||
case Prospero::TextureNumericClass::Sint: return TypeI32(state);
|
||||
case Prospero::TextureNumericClass::Unsupported: break;
|
||||
}
|
||||
EXIT("invalid image numeric class");
|
||||
}
|
||||
|
||||
uint32_t ImageViewSpatialComponents(ImageViewKind view) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D:
|
||||
case ImageViewKind::Dim1DArray: return 1u;
|
||||
case ImageViewKind::Dim2D:
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim2DMsaa:
|
||||
case ImageViewKind::Dim2DMsaaArray: return 2u;
|
||||
case ImageViewKind::Dim3D: return 3u;
|
||||
default: return 0u;
|
||||
uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class,
|
||||
uint32_t components) {
|
||||
return state.builder.Type(OpTypeVector, {ImageScalarType(state, numeric_class), components});
|
||||
}
|
||||
|
||||
uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
|
||||
uint32_t sampled = 0;
|
||||
uint32_t format = ImageFormatUnknown;
|
||||
if (image.resource_class == IR::ImageResourceClass::Sampled) {
|
||||
EXIT_IF(image.atomic);
|
||||
sampled = 1;
|
||||
} else if (image.resource_class == IR::ImageResourceClass::Storage) {
|
||||
EXIT_IF(image.numeric_class == Prospero::TextureNumericClass::Sint ||
|
||||
image.numeric_class == Prospero::TextureNumericClass::Unsupported);
|
||||
sampled = 2;
|
||||
if (image.atomic) {
|
||||
EXIT_IF(image.numeric_class != Prospero::TextureNumericClass::Uint);
|
||||
format = ImageFormatR32ui;
|
||||
}
|
||||
} else {
|
||||
EXIT("invalid image resource class");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ImageViewImageType(EmitterState& state, ImageViewKind view, bool integer) {
|
||||
const auto component = integer ? TypeU32(state) : TypeF32(state);
|
||||
const auto& info = ImageDimensionInfoFor(image.dimension);
|
||||
return state.builder.Type(OpTypeImage,
|
||||
{component, ImageSpirvDimension(view), 0, ImageSpirvArrayed(view),
|
||||
ImageSpirvMultisampled(view), 1, ImageFormatUnknown});
|
||||
{ImageScalarType(state, image.numeric_class), info.spirv_dimension,
|
||||
image.depth_compare ? 1u : 0u,
|
||||
info.arrayed, info.multisampled, sampled, format});
|
||||
}
|
||||
|
||||
uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, bool integer) {
|
||||
return state.builder.Type(OpTypeSampledImage, {ImageViewImageType(state, view, integer)});
|
||||
}
|
||||
|
||||
uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view) {
|
||||
switch (ImageViewCoordinateComponents(view)) {
|
||||
uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
|
||||
switch (ImageDimensionInfoFor(dimension).coordinate_components) {
|
||||
case 1u: return TypeU32(state);
|
||||
case 2u: return TypeU32Vector(state, 2);
|
||||
case 3u: return TypeU32Vector(state, 3);
|
||||
@@ -183,128 +165,89 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, ImageViewKind view) {
|
||||
uint32_t component = TypeF32(state);
|
||||
uint32_t format = ImageFormatUnknown;
|
||||
if (image_class == StorageImageClass::FormatlessUint) {
|
||||
component = TypeU32(state);
|
||||
} else if (image_class == StorageImageClass::AtomicUint) {
|
||||
component = TypeU32(state);
|
||||
format = ImageFormatR32ui;
|
||||
}
|
||||
return state.builder.Type(OpTypeImage, {component, ImageSpirvDimension(view), 0,
|
||||
ImageSpirvArrayed(view), 0, 2, format});
|
||||
}
|
||||
|
||||
uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class,
|
||||
ImageViewKind view) {
|
||||
return state.builder.Type(
|
||||
OpTypePointer, {StorageClassUniformConstant, StorageImageType(state, image_class, view)});
|
||||
}
|
||||
|
||||
StorageImageClass StorageImageClassForResource(const EmitterState& state, uint32_t resource) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint;
|
||||
EXIT_IF(image.kind != IR::ResourceKind::StorageImage && !uint_image);
|
||||
EXIT_IF(image.atomic && !uint_image);
|
||||
return StorageImageClassFor(uint_image, image.atomic);
|
||||
}
|
||||
|
||||
uint32_t StorageImageVariable(const EmitterState& state, StorageImageClass image_class,
|
||||
ImageViewKind view) {
|
||||
return state.storage_image_variables[StorageImageIndex(image_class, view)];
|
||||
}
|
||||
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view) {
|
||||
(void)use_pc;
|
||||
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
|
||||
const auto kind = SampledBindingKind(integer, view);
|
||||
const auto binding = ResourceForDescriptor(state, kind, mem.resource);
|
||||
const auto variable = state.sampled_image_variables[SampledImageIndex(integer, view)];
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto kind = IR::DescriptorBindingForImage(image_resource);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
const auto pointer_type = state.builder.Type(
|
||||
OpTypePointer, {StorageClassUniformConstant, ImageViewImageType(state, view, integer)});
|
||||
OpTypePointer, {StorageClassUniformConstant, ImageType(state, image_resource)});
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, binding.array_index, kind,
|
||||
mem.resource, "sampled image descriptor array was not emitted");
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"sampled image descriptor array was not emitted");
|
||||
const auto image = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, ImageViewImageType(state, view, integer), image, pointer});
|
||||
state.builder.AddFunction({OpLoad, ImageType(state, image_resource), image, pointer});
|
||||
return image;
|
||||
}
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t use_pc) {
|
||||
(void)use_pc;
|
||||
const auto binding = ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Samplers, sampler);
|
||||
const auto sampler_type = state.builder.Type(OpTypeSampler);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(OpTypePointer, {StorageClassUniformConstant, sampler_type});
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, pointer_type, state.sampler_variable, binding.array_index,
|
||||
state, pointer_type, state.sampler_variable, array_index,
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
const auto sampler_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, sampler_type, sampler_id, pointer});
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view) {
|
||||
const auto image = LoadSampledImageDescriptor(state, mem, use_pc, view);
|
||||
const auto sampler = LoadSamplerDescriptor(state, mem.sampler, use_pc);
|
||||
if (image == 0 || sampler == 0) {
|
||||
ExitDescriptorBindingFailure(
|
||||
state, SampledBindingKind(mem.kind == IR::ResourceKind::ImageUint, view), mem.resource,
|
||||
"sampled image or sampler descriptor load failed");
|
||||
}
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpSampledImage,
|
||||
ImageViewSampledImageType(state, view, mem.kind == IR::ResourceKind::ImageUint),
|
||||
sampled_image, image, sampler});
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
const auto image = LoadSampledImageDescriptor(state, resource);
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, sampler);
|
||||
const auto sampled_image = state.builder.AllocateId();
|
||||
const auto sampled_type =
|
||||
state.builder.Type(OpTypeSampledImage, {ImageType(state, image_resource)});
|
||||
state.builder.AddFunction({OpSampledImage, sampled_type, sampled_image, image, sampler_id});
|
||||
return sampled_image;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view, uint32_t image_resource) {
|
||||
auto selected = mem;
|
||||
selected.resource = image_resource;
|
||||
return MakeSampledImage(state, selected, use_pc, view);
|
||||
}
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, ImageViewKind view) {
|
||||
const auto image_class = StorageImageClassForResource(state, resource);
|
||||
const auto kind = StorageBindingKind(image_class, view);
|
||||
const auto binding = ResourceForDescriptor(state, kind, resource);
|
||||
const auto ptr_type = StorageImagePointerType(state, image_class, view);
|
||||
const auto variable = StorageImageVariable(state, image_class, view);
|
||||
return DescriptorElementPointer(state, ptr_type, variable, binding.array_index, kind, resource,
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(OpTypePointer, {StorageClassUniformConstant, ImageType(state, image)});
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
return DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
}
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind view,
|
||||
uint32_t mip_lod, uint32_t coord, uint32_t texel) {
|
||||
const auto image_class = StorageImageClassForResource(state, resource);
|
||||
if (image_class != StorageImageClass::AtomicUint) {
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel) {
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
EXIT_IF(image.resource_class != IR::ImageResourceClass::Storage);
|
||||
if (!image.atomic) {
|
||||
state.builder.RequireCapability(CapabilityStorageImageWriteWithoutFormat);
|
||||
}
|
||||
const auto kind = StorageBindingKind(image_class, view);
|
||||
const auto binding = ResourceForDescriptor(state, kind, resource);
|
||||
const auto& image = state.program.info.images.at(resource);
|
||||
const auto LoadAt = [&](uint32_t array_index) {
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, StorageImagePointerType(state, image_class, view),
|
||||
StorageImageVariable(state, image_class, view), array_index, kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
const auto kind = IR::DescriptorBindingForImage(image);
|
||||
EXIT_IF(!kind.has_value());
|
||||
const auto array_index = ResourceForDescriptor(state, *kind, resource);
|
||||
const auto image_type = ImageType(state, image);
|
||||
const auto pointer_type =
|
||||
state.builder.Type(OpTypePointer, {StorageClassUniformConstant, image_type});
|
||||
const auto variable = state.image_variables[IR::ImageBindingIndex(*kind)];
|
||||
const auto LoadAt = [&](uint32_t array_index) {
|
||||
const auto pointer =
|
||||
DescriptorElementPointer(state, pointer_type, variable, array_index, *kind, resource,
|
||||
"storage image descriptor array was not emitted");
|
||||
const auto descriptor = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpLoad, StorageImageType(state, image_class, view), descriptor, pointer});
|
||||
state.builder.AddFunction({OpLoad, image_type, descriptor, pointer});
|
||||
return descriptor;
|
||||
};
|
||||
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
|
||||
state.builder.AddFunction({OpImageWrite, LoadAt(binding.array_index), coord, texel});
|
||||
state.builder.AddFunction({OpImageWrite, LoadAt(array_index), coord, texel});
|
||||
return;
|
||||
}
|
||||
if (image.mip_count == 0u) {
|
||||
ExitDescriptorBindingFailure(state, kind, resource,
|
||||
ExitDescriptorBindingFailure(state, *kind, resource,
|
||||
"dynamic storage image has no mip descriptors");
|
||||
}
|
||||
|
||||
@@ -320,7 +263,7 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind
|
||||
state.builder.AddFunction(words);
|
||||
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
|
||||
EmitLabel(state, labels[mip]);
|
||||
state.builder.AddFunction({OpImageWrite, LoadAt(binding.array_index + mip), coord, texel});
|
||||
state.builder.AddFunction({OpImageWrite, LoadAt(array_index + mip), coord, texel});
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
}
|
||||
EmitLabel(state, merge_label);
|
||||
@@ -329,6 +272,7 @@ void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind
|
||||
uint32_t ExecutionModelForStage(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return ExecutionModelVertex;
|
||||
case ShaderType::Mesh: return 5365u; // MeshEXT
|
||||
case ShaderType::Pixel: return ExecutionModelFragment;
|
||||
default: return ExecutionModelGLCompute;
|
||||
}
|
||||
|
||||
@@ -16,26 +16,6 @@ bool UserDataDwordIndex(const EmitterState& state, IR::ScalarReg reg, uint32_t&
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t EmitWqmWordU32(EmitterState& state, uint32_t value) {
|
||||
const auto shifted_one = state.builder.AllocateId();
|
||||
const auto merged_one = state.builder.AllocateId();
|
||||
const auto shifted_two = state.builder.AllocateId();
|
||||
const auto merged_two = state.builder.AllocateId();
|
||||
const auto quad_bits = state.builder.AllocateId();
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpShiftRightLogical, TypeU32(state), shifted_one, value, ConstantU32(state, 1)});
|
||||
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), merged_one, value, shifted_one});
|
||||
state.builder.AddFunction(
|
||||
{OpShiftRightLogical, TypeU32(state), shifted_two, merged_one, ConstantU32(state, 2)});
|
||||
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), merged_two, merged_one, shifted_two});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, TypeU32(state), quad_bits, merged_two, ConstantU32(state, 0x11111111u)});
|
||||
state.builder.AddFunction(
|
||||
{OpIMul, TypeU32(state), result, quad_bits, ConstantU32(state, 0x0fu)});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitWqmU64(EmitterState& state, uint32_t value) {
|
||||
const auto shifted_one = state.builder.AllocateId();
|
||||
const auto merged_one = state.builder.AllocateId();
|
||||
@@ -61,6 +41,24 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t
|
||||
if (kind == IR::StageInputKind::LocalInvocationIndex) {
|
||||
return EmitLocalInvocationIndex(state);
|
||||
}
|
||||
if (state.lane_count == 2 && (kind == IR::StageInputKind::LocalInvocationId ||
|
||||
kind == IR::StageInputKind::GlobalInvocationId)) {
|
||||
const auto* cs = ShaderWorkgroupInput(state.stage, state.input_info);
|
||||
uint32_t divisor = 1;
|
||||
for (uint32_t axis = 0; axis < component; axis++) {
|
||||
divisor *= std::max(cs->threads_num[axis], 1u);
|
||||
}
|
||||
const auto size = std::max(cs->threads_num[component], 1u);
|
||||
const auto divided = EmitBinaryU32(state, OpUDiv, EmitLocalInvocationIndex(state),
|
||||
ConstantU32(state, divisor));
|
||||
const auto local = EmitBinaryU32(state, OpUMod, divided, ConstantU32(state, size));
|
||||
if (kind == IR::StageInputKind::LocalInvocationId) {
|
||||
return local;
|
||||
}
|
||||
const auto group = EmitInputComponentU32(state, IR::StageInputKind::WorkgroupId, component);
|
||||
return EmitAddU32(state, local,
|
||||
EmitBinaryU32(state, OpIMul, group, ConstantU32(state, size)));
|
||||
}
|
||||
const auto variable = InputVariableForKind(state, kind);
|
||||
if (variable == 0) {
|
||||
return ConstantU32(state, 0);
|
||||
@@ -73,7 +71,8 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t
|
||||
{OpSelect, TypeU32(state), bits, value, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
return bits;
|
||||
}
|
||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex) {
|
||||
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex ||
|
||||
kind == IR::StageInputKind::Layer || kind == IR::StageInputKind::SampleId) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
const auto bits = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, TypeI32(state), value, variable});
|
||||
@@ -106,40 +105,6 @@ uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t
|
||||
return EmitInputComponentU32(state, kind, component);
|
||||
}
|
||||
|
||||
uint32_t EmitWqm(ValueEmitContext& ctx, uint32_t active) {
|
||||
auto& state = ctx.state;
|
||||
const auto ballot = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallot, TypeU32Vector(state, 4), ballot,
|
||||
ConstantU32(state, ScopeSubgroup), active});
|
||||
const auto low = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, ballot, 0});
|
||||
const auto wqm_low = EmitWqmWordU32(state, low);
|
||||
const auto lane = EmitSubgroupLocalInvocationId(state);
|
||||
uint32_t mask = wqm_low;
|
||||
uint32_t bit_lane = lane;
|
||||
if (state.wave_size == 64u) {
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), high, ballot, 1});
|
||||
const auto wqm_high = EmitWqmWordU32(state, high);
|
||||
const auto upper = state.builder.AllocateId();
|
||||
mask = state.builder.AllocateId();
|
||||
bit_lane = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpUGreaterThanEqual, TypeBool(state), upper, lane, ConstantU32(state, 32)});
|
||||
state.builder.AddFunction({OpSelect, TypeU32(state), mask, upper, wqm_high, wqm_low});
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, TypeU32(state), bit_lane, lane, ConstantU32(state, 31)});
|
||||
}
|
||||
const auto bit = state.builder.AllocateId();
|
||||
const auto hit = state.builder.AllocateId();
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpShiftLeftLogical, TypeU32(state), bit, ConstantU32(state, 1), bit_lane});
|
||||
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), hit, mask, bit});
|
||||
state.builder.AddFunction({OpINotEqual, TypeBool(state), result, hit, ConstantU32(state, 0)});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& flags,
|
||||
uint32_t exec) {
|
||||
auto& state = ctx.state;
|
||||
@@ -373,12 +338,24 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
|
||||
}
|
||||
const auto output = IR::DecodePositionExportComponent(
|
||||
state.input_info.vertex->pa_cl_vs_out_cntl, exp.index, component);
|
||||
if (output.layer) {
|
||||
const auto raw = ExportRawComponent(ctx, data, component);
|
||||
const auto layer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), layer, raw,
|
||||
ConstantU32(state, 0x7ffu)});
|
||||
state.builder.AddFunction({OpStore, state.layer_variable, layer});
|
||||
if (output.layer || output.viewport) {
|
||||
const auto raw = ExportRawComponent(ctx, data, component);
|
||||
if (output.layer) {
|
||||
const auto layer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), layer, raw,
|
||||
ConstantU32(state, 0x7ffu)});
|
||||
const auto pointer = state.stage == ShaderType::Mesh
|
||||
? MeshOutputPointer(state, IR::StageOutputKind::Layer)
|
||||
: state.layer_variable;
|
||||
state.builder.AddFunction({OpStore, pointer, layer});
|
||||
}
|
||||
if (output.viewport) {
|
||||
// GFX10 MISC.z packs the viewport index in bits 16..19 alongside the layer.
|
||||
const auto viewport = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitFieldUExtract, TypeU32(state), viewport, raw,
|
||||
ConstantU32(state, 16), ConstantU32(state, 4)});
|
||||
state.builder.AddFunction({OpStore, state.viewport_index_variable, viewport});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!output.point_size && output.clip_distance == UINT32_MAX &&
|
||||
@@ -454,12 +431,18 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
{OpSelect, TypeU32(state), value, exec, ConstantU32(state, 1), ConstantU32(state, 0)});
|
||||
state.builder.AddFunction({OpStore, state.pixel_valid_mask_variable, value});
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::Null || exp.kind == IR::ExportTargetKind::Primitive ||
|
||||
exp.en == 0u) {
|
||||
if (exp.kind == IR::ExportTargetKind::Null || exp.en == 0u) {
|
||||
return;
|
||||
}
|
||||
EmitIfCondition(state, exec, [&]() {
|
||||
const auto data = ctx.Arg(inst, 0);
|
||||
if (exp.kind == IR::ExportTargetKind::Primitive) {
|
||||
if (state.stage == ShaderType::Mesh) {
|
||||
state.builder.AddFunction(
|
||||
{OpStore, MeshPrimitivePointer(state), ExportRawComponent(ctx, data, 0)});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::Position && exp.index != 0) {
|
||||
EmitAuxPositionExport(ctx, data, exp);
|
||||
return;
|
||||
@@ -483,8 +466,9 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto variable = OutputVariableForExport(state, exp);
|
||||
if (variable == 0) {
|
||||
const auto variable =
|
||||
state.stage == ShaderType::Mesh ? 0u : OutputVariableForExport(state, exp);
|
||||
if (state.stage != ShaderType::Mesh && variable == 0) {
|
||||
return;
|
||||
}
|
||||
const bool uint_output = MrtOutputMode(state, exp) == 7u;
|
||||
@@ -501,10 +485,16 @@ void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
value = mapped;
|
||||
}
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::Position) {
|
||||
if (state.stage == ShaderType::Vertex && state.input_info.vertex->clip_space.enabled) {
|
||||
value = ConvertPositionToClipSpace(state, value);
|
||||
}
|
||||
if (exp.kind == IR::ExportTargetKind::Position &&
|
||||
state.input_info.vertex->clip_space.enabled) {
|
||||
value = ConvertPositionToClipSpace(state, value);
|
||||
}
|
||||
if (state.stage == ShaderType::Mesh) {
|
||||
const auto kind = exp.kind == IR::ExportTargetKind::Position
|
||||
? IR::StageOutputKind::Position
|
||||
: IR::StageOutputKind::Parameter;
|
||||
state.builder.AddFunction({OpStore, MeshOutputPointer(state, kind, exp.index), value});
|
||||
} else if (exp.kind == IR::ExportTargetKind::Position) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpAccessChain, TypePointer(state, StorageClassOutput, TypeF32Vector(state, 4)),
|
||||
@@ -537,6 +527,19 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
ConstantU32(state, semantics)});
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::MeshAllocate: EmitMeshAllocate(ctx, inst); return true;
|
||||
case IR::ValueOpcode::MeshDrawParameter: {
|
||||
const auto index = inst.Arg(0).U32();
|
||||
if (state.stage != ShaderType::Mesh || index >= IR::PushData::MeshDrawDwordCount) {
|
||||
ctx.Fail(inst, "invalid mesh draw parameter");
|
||||
}
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypePushConstantElementPointer(state),
|
||||
pointer, state.push_constant_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, index)});
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), ctx.Result(inst), pointer});
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::GetUserData: {
|
||||
const auto reg = inst.Arg(0).ScalarRegister();
|
||||
uint32_t dword = 0;
|
||||
@@ -561,17 +564,12 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::DppMoveU32: {
|
||||
const auto flags = inst.Flags<IR::DppMoveFlags>();
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto shuffled = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0),
|
||||
target.lane});
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target.lane);
|
||||
if (flags.fetch_inactive) {
|
||||
ctx.Define(inst, shuffled);
|
||||
return true;
|
||||
}
|
||||
const auto ballot = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallot, TypeU32Vector(state, 4), ballot,
|
||||
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 1)});
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto source_active = EmitBallotLaneActiveBool(state, ballot, target.lane);
|
||||
const auto can_fetch = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
@@ -588,30 +586,18 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
case IR::ValueOpcode::WqmU64:
|
||||
ctx.Define(inst, EmitWqmU64(ctx.state, ctx.Arg(inst, 0)));
|
||||
return true;
|
||||
case IR::ValueOpcode::WqmMask:
|
||||
ctx.Define(inst, EmitWqm(ctx, ctx.Arg(inst, 0)));
|
||||
return true;
|
||||
case IR::ValueOpcode::LaneId:
|
||||
ctx.Define(inst, EmitSubgroupLocalInvocationId(state));
|
||||
return true;
|
||||
case IR::ValueOpcode::Ballot:
|
||||
ctx.Emit(inst, OpGroupNonUniformBallot, IR::Type::U32x4,
|
||||
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0)});
|
||||
return true;
|
||||
case IR::ValueOpcode::Ballot: ctx.Define(inst, ctx.Ballot(inst.Arg(0))); return true;
|
||||
case IR::ValueOpcode::ReadFirstLane: {
|
||||
const auto ballot = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallot, TypeU32Vector(state, 4), ballot,
|
||||
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 1)});
|
||||
state.builder.AddFunction({OpGroupNonUniformBallotFindLSB, TypeU32(state), lane,
|
||||
ConstantU32(state, ScopeSubgroup), ballot});
|
||||
ctx.Emit(inst, OpGroupNonUniformShuffle, IR::Type::U32,
|
||||
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0), lane});
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto lane = ctx.FirstLane(ballot);
|
||||
ctx.Define(inst, ctx.Shuffle(inst, 0, lane));
|
||||
return true;
|
||||
}
|
||||
case IR::ValueOpcode::ReadLane:
|
||||
ctx.Emit(inst, OpGroupNonUniformShuffle, IR::Type::U32,
|
||||
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
|
||||
ctx.Define(inst, ctx.Shuffle(inst, 0, ctx.Arg(inst, 1)));
|
||||
return true;
|
||||
case IR::ValueOpcode::WriteLane: {
|
||||
const auto hit = state.builder.AllocateId();
|
||||
@@ -656,16 +642,10 @@ bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, TypeU32(state), index, shifted, ConstantU32(state, 15)});
|
||||
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, row_value, index});
|
||||
const auto shuffled = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0),
|
||||
target});
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target);
|
||||
uint32_t result = shuffled;
|
||||
if (!flags.fetch_inactive) {
|
||||
const auto source_exec = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeBool(state), source_exec,
|
||||
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 3),
|
||||
target});
|
||||
const auto source_exec = ctx.Shuffle(inst, 3, target);
|
||||
result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelect, TypeU32(state), result, source_exec, shuffled,
|
||||
ConstantU32(state, 0)});
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t EmitTrueBool(EmitterState& state) {
|
||||
return ConstantBool(state, true);
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
|
||||
const auto quad_base = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
@@ -23,7 +19,7 @@ DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uin
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseAnd, TypeU32(state), selected, selected0, ConstantU32(state, 3)});
|
||||
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, quad_base, selected});
|
||||
return {target, EmitTrueBool(state)};
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
|
||||
@@ -70,7 +66,7 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
|
||||
{OpIAdd, TypeU32(state), plus, lane, ConstantU32(state, 16u - amount)});
|
||||
state.builder.AddFunction({OpSelect, TypeU32(state), selected, in_high, minus, plus});
|
||||
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, row, selected});
|
||||
return {target, EmitTrueBool(state)};
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row) {
|
||||
@@ -87,7 +83,7 @@ DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool
|
||||
state.builder.AddFunction(
|
||||
{OpISub, TypeU32(state), mirrored, ConstantU32(state, lane_mask), lane});
|
||||
state.builder.AddFunction({OpBitwiseOr, TypeU32(state), target, base, mirrored});
|
||||
return {target, EmitTrueBool(state)};
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
|
||||
@@ -114,9 +110,9 @@ DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpBitwiseXor, TypeU32(state), target, subid, ConstantU32(state, control & 0xfu)});
|
||||
return {target, EmitTrueBool(state)};
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
return {subid, EmitTrueBool(state)};
|
||||
return {subid, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state) {
|
||||
@@ -126,7 +122,7 @@ uint32_t EmitSubgroupLocalInvocationId(EmitterState& state) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpLoad, TypeU32(state), value, state.subgroup_local_invocation_id_variable});
|
||||
return value;
|
||||
return state.lane_half == 0 ? value : EmitAddU32(state, value, ConstantU32(state, 32));
|
||||
}
|
||||
|
||||
uint32_t InputVariableForKind(const EmitterState& state, IR::StageInputKind kind) {
|
||||
@@ -167,31 +163,24 @@ uint32_t EmitLocalInvocationIndex(EmitterState& state) {
|
||||
}
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, variable});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t VertexInputDefaultComponentU32(EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t component) {
|
||||
if ((component & 3u) != 3u) {
|
||||
return ConstantU32(state, 0);
|
||||
if (state.lane_count == 2) {
|
||||
const auto wave_base = EmitBinaryU32(state, OpBitwiseAnd, value, ConstantU32(state, ~31u));
|
||||
return EmitAddU32(state, EmitAddU32(state, value, wave_base),
|
||||
ConstantU32(state, state.lane_half * 32));
|
||||
}
|
||||
return ConstantU32(state, kind == VertexInputScalarKind::Float ? 0x3f800000u : 1u);
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding& input,
|
||||
uint32_t component) {
|
||||
const auto count = VertexParameterComponentCount(state, input);
|
||||
const auto count = VertexParameterComponentCount(input);
|
||||
const auto kind = VertexParameterScalarKind(state, input.location);
|
||||
if (component >= count) {
|
||||
return VertexInputDefaultComponentU32(state, kind, component);
|
||||
}
|
||||
|
||||
const auto scalar_type = VertexParameterScalarType(state, kind);
|
||||
uint32_t raw = state.builder.AllocateId();
|
||||
if (count == 1u) {
|
||||
state.builder.AddFunction({OpLoad, scalar_type, raw, input.variable_id});
|
||||
} else {
|
||||
const auto pointer_type = VertexParameterScalarPointerType(state, kind);
|
||||
const auto pointer_type = TypePointer(state, StorageClassInput, scalar_type);
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, pointer_type, pointer, input.variable_id,
|
||||
ConstantU32(state, component)});
|
||||
@@ -210,14 +199,14 @@ uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding
|
||||
uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane) {
|
||||
const auto active_ballot = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallot, TypeU32Vector(state, 4), active_ballot,
|
||||
ConstantU32(state, ScopeSubgroup), EmitTrueBool(state)});
|
||||
ConstantU32(state, ScopeSubgroup), ConstantBool(state, true)});
|
||||
return EmitBallotLaneActiveBool(state, active_ballot, lane);
|
||||
}
|
||||
uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, uint32_t lane) {
|
||||
const auto low = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, active_ballot, 0});
|
||||
uint32_t mask = low;
|
||||
if (state.wave_size == 64u) {
|
||||
if (state.program.wave_size == 64u) {
|
||||
const auto high = state.builder.AllocateId();
|
||||
const auto in_high = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
@@ -242,7 +231,7 @@ uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, u
|
||||
state.builder.AddFunction({OpBitwiseAnd, TypeU32(state), hit, mask, bit});
|
||||
state.builder.AddFunction({OpINotEqual, TypeBool(state), active, hit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{OpULessThan, TypeBool(state), in_range, lane, ConstantU32(state, state.wave_size)});
|
||||
{OpULessThan, TypeBool(state), in_range, lane, ConstantU32(state, state.program.wave_size)});
|
||||
state.builder.AddFunction({OpLogicalAnd, TypeBool(state), ret, active, in_range});
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ImageOps.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
@@ -31,61 +32,18 @@ uint32_t ImageGatherComponent(uint32_t dmask) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, value});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs, uint32_t rhs) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, lhs, rhs});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HasFlag(const IR::MemoryInfo& mem, uint32_t flag) {
|
||||
return (mem.image_sample_flags & flag) != 0u;
|
||||
}
|
||||
|
||||
bool UsesA16(const IR::MemoryInfo& mem) {
|
||||
return HasFlag(mem, Decoder::ImageSampleFlagA16);
|
||||
}
|
||||
|
||||
bool ComponentUsesA16(const IR::MemoryInfo& mem, uint32_t component) {
|
||||
if (!UsesA16(mem)) return false;
|
||||
uint32_t cursor = 0;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) {
|
||||
if (component == cursor) return false;
|
||||
cursor++;
|
||||
}
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) {
|
||||
if (component == cursor) return true;
|
||||
cursor++;
|
||||
}
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagCompare) && component == cursor) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t HalfComponent(const IR::MemoryInfo& mem, uint32_t component) {
|
||||
if (!UsesA16(mem)) return component * 2u;
|
||||
uint32_t half = 0;
|
||||
for (uint32_t index = 0; index < component; index++) {
|
||||
const auto width = ComponentUsesA16(mem, index) ? 1u : 2u;
|
||||
if (width == 2u && (half & 1u) != 0u) half++;
|
||||
half += width;
|
||||
}
|
||||
if (!ComponentUsesA16(mem, component) && (half & 1u) != 0u) half++;
|
||||
return half;
|
||||
}
|
||||
|
||||
uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
uint32_t component) {
|
||||
const auto half = HalfComponent(mem, component);
|
||||
const auto packed = half / 2u;
|
||||
const auto layout = Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component);
|
||||
const auto packed = layout.bit_offset / 32u;
|
||||
if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0);
|
||||
auto value = ctx.Def(address.Arg(packed));
|
||||
if (ComponentUsesA16(mem, component)) {
|
||||
if ((half & 1u) != 0u) {
|
||||
if (layout.bit_width == 16u) {
|
||||
if ((layout.bit_offset & 31u) != 0u) {
|
||||
value = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state), value,
|
||||
ConstantU32(ctx.state, 16));
|
||||
}
|
||||
@@ -98,25 +56,26 @@ uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::
|
||||
uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
uint32_t component) {
|
||||
const auto value = AddressU32(ctx, mem, address, component);
|
||||
return ComponentUsesA16(mem, component)
|
||||
return Decoder::ImageAddressComponentLayout(mem.image_sample_flags, component).bit_width == 16u
|
||||
? EmitF16BitsToF32(ctx.state, value)
|
||||
: Unary(ctx.state, OpBitcast, TypeF32(ctx.state), value);
|
||||
}
|
||||
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageViewKind view) {
|
||||
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageDimension dimension) {
|
||||
ImageSampleLayout layout;
|
||||
uint32_t cursor = 0;
|
||||
const auto& info = ImageDimensionInfoFor(dimension);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
layout.grad_x = cursor;
|
||||
cursor += ImageViewSpatialComponents(view);
|
||||
cursor += info.spatial_components;
|
||||
layout.grad_y = cursor;
|
||||
cursor += ImageViewSpatialComponents(view);
|
||||
cursor += info.spatial_components;
|
||||
}
|
||||
layout.coord = cursor;
|
||||
cursor += ImageViewCoordinateComponents(view);
|
||||
cursor += info.coordinate_components;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
|
||||
return layout;
|
||||
}
|
||||
@@ -144,11 +103,12 @@ uint32_t CubeLayer(EmitterState& state, uint32_t value) {
|
||||
|
||||
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
uint32_t first, uint32_t components) {
|
||||
const bool cube = ctx.state.program.info.images.at(mem.resource).cube;
|
||||
auto x = AddressF32(ctx, mem, address, first);
|
||||
if (components == 1u) return x;
|
||||
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
|
||||
: ZeroF32(ctx.state);
|
||||
if (mem.image_cube) {
|
||||
if (cube) {
|
||||
x = CubeAxis(ctx.state, x);
|
||||
y = CubeAxis(ctx.state, y);
|
||||
}
|
||||
@@ -157,7 +117,7 @@ uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
|
||||
auto z = mem.image_address_components > first + 2u
|
||||
? AddressF32(ctx, mem, address, first + 2u)
|
||||
: ZeroF32(ctx.state);
|
||||
if (mem.image_cube) z = CubeLayer(ctx.state, z);
|
||||
if (cube) z = CubeLayer(ctx.state, z);
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpCompositeConstruct, TypeF32Vector(ctx.state, 3), result, x, y, z});
|
||||
} else {
|
||||
@@ -168,8 +128,8 @@ uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
|
||||
}
|
||||
|
||||
uint32_t CoordU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
ImageViewKind view) {
|
||||
const auto components = ImageViewCoordinateComponents(view);
|
||||
ImageDimension dimension) {
|
||||
const auto components = ImageDimensionInfoFor(dimension).coordinate_components;
|
||||
const auto x = AddressU32(ctx, mem, address, 0);
|
||||
if (components == 1u) return x;
|
||||
const auto y = mem.image_address_components > 1u ? AddressU32(ctx, mem, address, 1)
|
||||
@@ -188,8 +148,8 @@ uint32_t CoordU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::In
|
||||
}
|
||||
|
||||
uint32_t LodU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
ImageViewKind view) {
|
||||
const auto component = ImageViewCoordinateComponents(view);
|
||||
ImageDimension dimension) {
|
||||
const auto component = ImageDimensionInfoFor(dimension).coordinate_components;
|
||||
return mem.image_has_mip && mem.image_address_components > component
|
||||
? AddressU32(ctx, mem, address, component)
|
||||
: ConstantU32(ctx.state, 0);
|
||||
@@ -199,8 +159,33 @@ uint32_t FloatBits(ValueEmitContext& ctx, uint32_t value) {
|
||||
return Unary(ctx.state, OpBitcast, TypeU32(ctx.state), value);
|
||||
}
|
||||
|
||||
uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool dref,
|
||||
uint32_t SampledComponentBits(ValueEmitContext& ctx, uint32_t value,
|
||||
Prospero::TextureNumericClass numeric_class) {
|
||||
if (numeric_class == Prospero::TextureNumericClass::Uint) {
|
||||
return value;
|
||||
}
|
||||
return Unary(ctx.state, OpBitcast, TypeU32(ctx.state), value);
|
||||
}
|
||||
|
||||
uint32_t SampledComponentZero(EmitterState& state, Prospero::TextureNumericClass numeric_class) {
|
||||
switch (numeric_class) {
|
||||
case Prospero::TextureNumericClass::Float: return ZeroF32(state);
|
||||
case Prospero::TextureNumericClass::Uint: return ConstantU32(state, 0);
|
||||
case Prospero::TextureNumericClass::Sint: return ConstantI32(state, 0);
|
||||
case Prospero::TextureNumericClass::Unsupported: break;
|
||||
}
|
||||
EXIT("invalid sampled image numeric class");
|
||||
}
|
||||
|
||||
uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value,
|
||||
Prospero::TextureNumericClass numeric_class, bool dref,
|
||||
const IR::MemoryInfo& mem, bool gather = false) {
|
||||
auto value_class = numeric_class;
|
||||
if (dref) {
|
||||
value_class = Prospero::TextureNumericClass::Float;
|
||||
}
|
||||
const bool integer = value_class == Prospero::TextureNumericClass::Uint ||
|
||||
value_class == Prospero::TextureNumericClass::Sint;
|
||||
if (mem.data_bits == 16u) {
|
||||
uint32_t packed[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
|
||||
ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)};
|
||||
@@ -209,25 +194,27 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool
|
||||
const auto component = gather ? index : DmaskComponent(mem.dmask, index);
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({OpCompositeExtract,
|
||||
integer ? TypeU32(ctx.state) : TypeF32(ctx.state),
|
||||
result, value, component});
|
||||
ImageScalarType(ctx.state, value_class), result, value,
|
||||
component});
|
||||
return result;
|
||||
};
|
||||
for (uint32_t word = 0; word < mem.data_dwords; word++) {
|
||||
const auto low_index = word * 2u;
|
||||
const auto high_index = low_index + 1u;
|
||||
const auto low = scalar(low_index);
|
||||
const auto high = high_index < mem.component_count
|
||||
? scalar(high_index)
|
||||
: (integer ? ConstantU32(ctx.state, 0) : ZeroF32(ctx.state));
|
||||
const auto high = high_index < mem.component_count
|
||||
? scalar(high_index)
|
||||
: SampledComponentZero(ctx.state, value_class);
|
||||
if (integer) {
|
||||
const auto mask = ConstantU32(ctx.state, 0xffffu);
|
||||
packed[word] =
|
||||
Binary(ctx.state, OpBitwiseOr, TypeU32(ctx.state),
|
||||
Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), low, mask),
|
||||
Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state),
|
||||
Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), high, mask),
|
||||
ConstantU32(ctx.state, 16u)));
|
||||
const auto low_bits = SampledComponentBits(ctx, low, value_class);
|
||||
const auto high_bits = SampledComponentBits(ctx, high, value_class);
|
||||
const auto mask = ConstantU32(ctx.state, 0xffffu);
|
||||
packed[word] = Binary(
|
||||
ctx.state, OpBitwiseOr, TypeU32(ctx.state),
|
||||
Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), low_bits, mask),
|
||||
Binary(ctx.state, OpShiftLeftLogical, TypeU32(ctx.state),
|
||||
Binary(ctx.state, OpBitwiseAnd, TypeU32(ctx.state), high_bits, mask),
|
||||
ConstantU32(ctx.state, 16u)));
|
||||
} else {
|
||||
const auto pair = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(
|
||||
@@ -249,10 +236,9 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool
|
||||
continue;
|
||||
}
|
||||
const auto scalar = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({OpCompositeExtract,
|
||||
integer ? TypeU32(ctx.state) : TypeF32(ctx.state), scalar,
|
||||
value, index});
|
||||
component[index] = integer ? scalar : FloatBits(ctx, scalar);
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpCompositeExtract, ImageScalarType(ctx.state, value_class), scalar, value, index});
|
||||
component[index] = SampledComponentBits(ctx, scalar, value_class);
|
||||
}
|
||||
const auto result = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({OpCompositeConstruct, TypeU32Vector(ctx.state, 4), result,
|
||||
@@ -260,20 +246,20 @@ uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const IR::Inst& address) {
|
||||
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
|
||||
const auto view = SampledImageViewKind(ctx.state, mem, pc);
|
||||
const auto image = LoadSampledImageDescriptor(ctx.state, mem, pc, view);
|
||||
const auto size = ctx.state.builder.AllocateId();
|
||||
if (ImageSpirvMultisampled(view) != 0u) {
|
||||
const auto dimension = ctx.state.program.info.images.at(mem.resource).dimension;
|
||||
const auto& info = ImageDimensionInfoFor(dimension);
|
||||
const auto image = LoadSampledImageDescriptor(ctx.state, mem.resource);
|
||||
const auto size = ctx.state.builder.AllocateId();
|
||||
if (info.multisampled != 0u) {
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpImageQuerySize, ImageViewSizeType(ctx.state, view), size, image});
|
||||
{OpImageQuerySize, ImageViewSizeType(ctx.state, dimension), size, image});
|
||||
} else {
|
||||
ctx.state.builder.AddFunction({OpImageQuerySizeLod, ImageViewSizeType(ctx.state, view),
|
||||
ctx.state.builder.AddFunction({OpImageQuerySizeLod, ImageViewSizeType(ctx.state, dimension),
|
||||
size, image, AddressU32(ctx, mem, address, 0)});
|
||||
}
|
||||
const auto components = ImageViewCoordinateComponents(view);
|
||||
const auto components = info.coordinate_components;
|
||||
uint32_t result[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
|
||||
ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)};
|
||||
if (components == 1u) {
|
||||
@@ -285,7 +271,7 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::Inst& inst, const IR::
|
||||
{OpCompositeExtract, TypeU32(ctx.state), result[index], size, index});
|
||||
}
|
||||
}
|
||||
if (ImageSpirvMultisampled(view) == 0u) {
|
||||
if (info.multisampled == 0u) {
|
||||
result[3] = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction({OpImageQueryLevels, TypeU32(ctx.state), result[3], image});
|
||||
}
|
||||
@@ -296,8 +282,8 @@ uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::Inst& inst, const IR::
|
||||
}
|
||||
|
||||
uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
|
||||
const ImageSampleLayout& layout, ImageViewKind view) {
|
||||
const auto components = ImageViewSpatialComponents(view);
|
||||
const ImageSampleLayout& layout, ImageDimension dimension) {
|
||||
const auto components = ImageDimensionInfoFor(dimension).spatial_components;
|
||||
const auto zero = ConstantI32(ctx.state, 0);
|
||||
if (layout.offset == NoImageComponent || mem.image_address_components <= layout.offset) {
|
||||
if (components == 1u) return zero;
|
||||
@@ -332,8 +318,8 @@ uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t HorizontalOffsets(EmitterState& state, ImageViewKind view) {
|
||||
const auto components = ImageViewSpatialComponents(view);
|
||||
uint32_t HorizontalOffsets(EmitterState& state, ImageDimension dimension) {
|
||||
const auto components = ImageDimensionInfoFor(dimension).spatial_components;
|
||||
const auto count = ConstantU32(state, 4);
|
||||
const auto element_type = components == 1u ? TypeI32(state) : TypeI32Vector(state, 2);
|
||||
const auto array_type = state.builder.Type(OpTypeArray, {element_type, count});
|
||||
@@ -363,8 +349,7 @@ Format::BufferFormatInfo ImageConversionFormat(const EmitterState& state,
|
||||
const auto format = state.program.info.images[mem.resource].conversion_format;
|
||||
if (format == Prospero::BufferFormat::kInvalid) return {};
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
EXIT_IF(!Prospero::IsSampledTextureFormat(format) ||
|
||||
!Prospero::IsUintTextureFormat(format) ||
|
||||
EXIT_IF(Prospero::SampledTextureNumericClass(format) != Prospero::TextureNumericClass::Uint ||
|
||||
Prospero::RemapTextureFormat(format) == format ||
|
||||
info.type != Format::ComponentType::Uint || !info.packed_bitfield ||
|
||||
info.byte_size != sizeof(uint32_t) || info.component_count == 0u ||
|
||||
@@ -439,46 +424,46 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t pc,
|
||||
uint32_t coord, bool integer) {
|
||||
uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
uint32_t coord, Prospero::TextureNumericClass numeric_class) {
|
||||
auto& state = ctx.state;
|
||||
state.builder.RequireCapability(CapabilityImageQuery);
|
||||
const auto image = LoadSampledImageDescriptor(state, mem, pc, ImageViewKind::Dim1D);
|
||||
const auto image = LoadSampledImageDescriptor(state, mem.resource);
|
||||
const auto width = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)});
|
||||
const auto width_f32 = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpConvertUToF, TypeF32(state), width_f32, width});
|
||||
const auto left = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpExtInst, TypeF32(state), left, GlslStd450(state), GlslFloor,
|
||||
Binary(state, OpFSub, TypeF32(state),
|
||||
Binary(state, OpFMul, TypeF32(state), coord, width_f32),
|
||||
ConstantF32(state, 0x3f000000u))});
|
||||
state.builder.AddFunction({OpExtInst, TypeF32(state), left, GlslStd450(state), GlslFloor,
|
||||
Binary(state, OpFSub, TypeF32(state),
|
||||
Binary(state, OpFMul, TypeF32(state), coord, width_f32),
|
||||
ConstantF32(state, 0x3f000000u))});
|
||||
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, ImageViewKind::Dim1D);
|
||||
const auto vector_type = integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
const auto scalar_type = integer ? TypeU32(state) : TypeF32(state);
|
||||
const auto component = ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask)
|
||||
: 0u;
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto vector_type = ImageVectorType(state, numeric_class, 4);
|
||||
const auto scalar_type = ImageScalarType(state, numeric_class);
|
||||
const auto component =
|
||||
ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask)
|
||||
: 0u;
|
||||
uint32_t values[2] {};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
const auto sample_coord = Binary(
|
||||
state, OpFDiv, TypeF32(state),
|
||||
Binary(state, OpFAdd, TypeF32(state), left,
|
||||
ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)),
|
||||
width_f32);
|
||||
const auto sample_coord =
|
||||
Binary(state, OpFDiv, TypeF32(state),
|
||||
Binary(state, OpFAdd, TypeF32(state), left,
|
||||
ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)),
|
||||
width_f32);
|
||||
const auto texel = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpImageSampleExplicitLod, vector_type, texel, sampled, sample_coord,
|
||||
ImageOperandsLodMask, ZeroF32(state)});
|
||||
state.builder.AddFunction({OpImageSampleExplicitLod, vector_type, texel, sampled,
|
||||
sample_coord, ImageOperandsLodMask, ZeroF32(state)});
|
||||
values[index] = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpCompositeExtract, scalar_type, values[index], texel, component});
|
||||
}
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeConstruct, vector_type, result, values[0], values[1],
|
||||
values[1], values[0]});
|
||||
state.builder.AddFunction(
|
||||
{OpCompositeConstruct, vector_type, result, values[0], values[1], values[1], values[0]});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -549,6 +534,7 @@ uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t d
|
||||
|
||||
uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::ImageAtomicSwap32: return OpAtomicExchange;
|
||||
case IR::ValueOpcode::ImageAtomicIAdd32: return OpAtomicIAdd;
|
||||
case IR::ValueOpcode::ImageAtomicUMin32: return OpAtomicUMin;
|
||||
case IR::ValueOpcode::ImageAtomicUMax32: return OpAtomicUMax;
|
||||
@@ -569,24 +555,24 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
auto& state = ctx.state;
|
||||
const auto& mem = ctx.Memory(inst);
|
||||
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
|
||||
const auto image_arg = inst.Arg(0);
|
||||
ctx.ResourceIndex(image_arg, IR::ValueOpcode::GetImageResource);
|
||||
const auto& image = state.program.info.images.at(mem.resource);
|
||||
const auto* address = ctx.ImageAddress(inst.Arg(image_info.needs_sampler ? 2 : 1));
|
||||
if (address == nullptr) return true;
|
||||
if (op == IR::ValueOpcode::ImageQueryDimensions) {
|
||||
state.builder.RequireCapability(CapabilityImageQuery);
|
||||
ctx.Define(inst, QueryDimensions(ctx, inst, mem, *address));
|
||||
ctx.Define(inst, QueryDimensions(ctx, mem, *address));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageQueryLod) {
|
||||
state.builder.RequireCapability(CapabilityImageQuery);
|
||||
const auto view = SampledImageViewKind(state, mem, pc);
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view);
|
||||
const auto lod = state.builder.AllocateId();
|
||||
const auto dimension = image.dimension;
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto lod = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
|
||||
CoordF32(ctx, mem, *address, 0, ImageViewSpatialComponents(view))});
|
||||
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components)});
|
||||
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
|
||||
ConstantU32(state, 0)};
|
||||
for (uint32_t index = 0; index < 2u; index++) {
|
||||
@@ -601,143 +587,153 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageRead) {
|
||||
const auto view = SampledImageViewKind(state, mem, pc);
|
||||
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
|
||||
const auto condition = ctx.Arg(inst, 2);
|
||||
const auto dimension = image.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const auto condition = ctx.Arg(inst, 2);
|
||||
ctx.Define(
|
||||
inst,
|
||||
EmitValueOrDefaultIfCondition(
|
||||
state, condition, TypeU32Vector(state, 4), ConstantU32CompositeZero(state, 4),
|
||||
[&]() {
|
||||
const auto image = LoadSampledImageDescriptor(state, mem, pc, view);
|
||||
const auto color = state.builder.AllocateId();
|
||||
const auto coord = CoordU32(ctx, mem, *address, view);
|
||||
if (ImageSpirvMultisampled(view) != 0u) {
|
||||
const auto descriptor = LoadSampledImageDescriptor(state, mem.resource);
|
||||
const auto color = state.builder.AllocateId();
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
if (dimension_info.multisampled != 0u) {
|
||||
state.builder.AddFunction(
|
||||
{OpImageFetch,
|
||||
integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4), color,
|
||||
image, coord, ImageOperandsSampleMask,
|
||||
AddressU32(ctx, mem, *address, ImageViewCoordinateComponents(view))});
|
||||
{OpImageFetch, ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, ImageOperandsSampleMask,
|
||||
AddressU32(ctx, mem, *address, dimension_info.coordinate_components)});
|
||||
} else {
|
||||
state.builder.AddFunction(
|
||||
{OpImageFetch,
|
||||
integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4), color,
|
||||
image, coord, ImageOperandsLodMask, LodU32(ctx, mem, *address, view)});
|
||||
state.builder.AddFunction({OpImageFetch,
|
||||
ImageVectorType(state, numeric_class, 4), color,
|
||||
descriptor, coord, ImageOperandsLodMask,
|
||||
LodU32(ctx, mem, *address, dimension)});
|
||||
}
|
||||
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), integer, false,
|
||||
mem);
|
||||
return ResultVector(ctx, UnpackImageTexel(ctx, mem, color), numeric_class,
|
||||
false, mem);
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageWrite) {
|
||||
const auto uint_image = mem.kind == IR::ResourceKind::StorageImageUint;
|
||||
const auto view = StorageImageViewKind(mem);
|
||||
const bool uint_image = image.numeric_class == Prospero::TextureNumericClass::Uint;
|
||||
const auto dimension = image.dimension;
|
||||
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto mip_lod =
|
||||
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage
|
||||
? LodU32(ctx, mem, *address, view)
|
||||
? LodU32(ctx, mem, *address, dimension)
|
||||
: 0u;
|
||||
const auto coord = CoordU32(ctx, mem, *address, view);
|
||||
const auto coord = CoordU32(ctx, mem, *address, dimension);
|
||||
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
|
||||
EmitStorageImageWrite(state, mem.resource, view, mip_lod, coord, texel);
|
||||
EmitStorageImageWrite(state, mem.resource, mip_lod, coord, texel);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto view = SampledImageViewKind(state, mem, pc);
|
||||
const auto layout = Layout(mem, view);
|
||||
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
const auto dimension = image.dimension;
|
||||
const auto& dimension_info = ImageDimensionInfoFor(dimension);
|
||||
const auto layout = Layout(mem, dimension);
|
||||
const auto numeric_class = image.numeric_class;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
if (dref && state.program.info.images[mem.resource].conversion_format !=
|
||||
Prospero::BufferFormat::kInvalid) {
|
||||
ctx.Fail(inst, "uses depth comparison with a packed integer image");
|
||||
return true;
|
||||
}
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view));
|
||||
CoordF32(ctx, mem, *address, layout.coord, dimension_info.coordinate_components);
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
if (view == ImageViewKind::Dim1D) {
|
||||
if (dimension == ImageDimension::Dim1D) {
|
||||
if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagOffset) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
ctx.Fail(inst, "has an unsupported 1D gather variant");
|
||||
return true;
|
||||
}
|
||||
const auto sample = EmitOneDimensionalGatherLz(ctx, mem, pc, coord, integer);
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), integer,
|
||||
false, mem, true));
|
||||
const auto sample = EmitOneDimensionalGatherLz(ctx, mem, coord, numeric_class);
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
|
||||
numeric_class, false, mem, true));
|
||||
return true;
|
||||
}
|
||||
if (view == ImageViewKind::Dim1DArray) {
|
||||
if (dimension == ImageDimension::Dim1DArray) {
|
||||
ctx.Fail(inst, "has an unsupported 1D-array gather");
|
||||
return true;
|
||||
}
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view);
|
||||
const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words =
|
||||
dref ? std::vector<uint32_t> {OpImageDrefGather,
|
||||
TypeF32Vector(state, 4),
|
||||
sample,
|
||||
sampled,
|
||||
coord,
|
||||
layout.dref != NoImageComponent
|
||||
? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state)}
|
||||
: std::vector<uint32_t> {
|
||||
OpImageGather,
|
||||
integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4),
|
||||
sample,
|
||||
sampled,
|
||||
coord,
|
||||
ConstantU32(state, ImageConversionFormat(state, mem).format ==
|
||||
Prospero::BufferFormat::kInvalid
|
||||
? ImageGatherComponent(mem.dmask)
|
||||
: 0u)};
|
||||
std::vector<uint32_t> words;
|
||||
if (dref) {
|
||||
auto dref_value = ZeroF32(state);
|
||||
if (layout.dref != NoImageComponent) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
words = {OpImageDrefGather, TypeF32Vector(state, 4), sample, sampled, coord,
|
||||
dref_value};
|
||||
} else {
|
||||
uint32_t component = 0;
|
||||
if (ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid) {
|
||||
component = ImageGatherComponent(mem.dmask);
|
||||
}
|
||||
words = {OpImageGather, ImageVectorType(state, numeric_class, 4),
|
||||
sample, sampled,
|
||||
coord, ConstantU32(state, component)};
|
||||
}
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
|
||||
words.push_back(ImageOperandsConstOffsetsMask);
|
||||
words.push_back(HorizontalOffsets(state, view));
|
||||
words.push_back(HorizontalOffsets(state, dimension));
|
||||
} else if (layout.offset != NoImageComponent) {
|
||||
words.push_back(ImageOperandsOffsetMask);
|
||||
words.push_back(PackedOffset(ctx, mem, *address, layout, view));
|
||||
words.push_back(PackedOffset(ctx, mem, *address, layout, dimension));
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
auto result_numeric_class = numeric_class;
|
||||
if (dref) {
|
||||
result_numeric_class = Prospero::TextureNumericClass::Float;
|
||||
}
|
||||
ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample),
|
||||
integer && !dref, false, mem, true));
|
||||
result_numeric_class, false, mem, true));
|
||||
return true;
|
||||
}
|
||||
const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagLod) ||
|
||||
HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
|
||||
state.stage != ShaderType::Pixel;
|
||||
const auto opcode = explicit_lod
|
||||
? (dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod)
|
||||
: (dref ? OpImageSampleDrefImplicitLod : OpImageSampleImplicitLod);
|
||||
const auto result_type =
|
||||
dref ? TypeF32(state) : (integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4));
|
||||
const auto dref_value =
|
||||
dref ? (layout.dref != NoImageComponent ? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state))
|
||||
: 0u;
|
||||
uint32_t opcode = OpImageSampleImplicitLod;
|
||||
if (explicit_lod) {
|
||||
opcode = dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod;
|
||||
} else if (dref) {
|
||||
opcode = OpImageSampleDrefImplicitLod;
|
||||
}
|
||||
uint32_t result_type = ImageVectorType(state, numeric_class, 4);
|
||||
uint32_t dref_value = 0;
|
||||
if (dref) {
|
||||
result_type = TypeF32(state);
|
||||
dref_value = ZeroF32(state);
|
||||
if (layout.dref != NoImageComponent) {
|
||||
dref_value = AddressF32(ctx, mem, *address, layout.dref);
|
||||
}
|
||||
}
|
||||
uint32_t operand_mask = 0;
|
||||
std::vector<uint32_t> operands;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
operand_mask |= ImageOperandsGradMask;
|
||||
operands.push_back(
|
||||
CoordF32(ctx, mem, *address, layout.grad_x, ImageViewSpatialComponents(view)));
|
||||
CoordF32(ctx, mem, *address, layout.grad_x, dimension_info.spatial_components));
|
||||
operands.push_back(
|
||||
CoordF32(ctx, mem, *address, layout.grad_y, ImageViewSpatialComponents(view)));
|
||||
CoordF32(ctx, mem, *address, layout.grad_y, dimension_info.spatial_components));
|
||||
} else if (explicit_lod) {
|
||||
operand_mask |= ImageOperandsLodMask;
|
||||
operands.push_back(HasFlag(mem, Decoder::ImageSampleFlagLod) &&
|
||||
layout.lod != NoImageComponent
|
||||
? AddressF32(ctx, mem, *address, layout.lod)
|
||||
: ZeroF32(state));
|
||||
auto lod = ZeroF32(state);
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagLod) && layout.lod != NoImageComponent) {
|
||||
lod = AddressF32(ctx, mem, *address, layout.lod);
|
||||
}
|
||||
operands.push_back(lod);
|
||||
} else if (layout.bias != NoImageComponent) {
|
||||
operand_mask |= ImageOperandsBiasMask;
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
}
|
||||
const auto EmitSample = [&](uint32_t resource) {
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view, resource);
|
||||
const auto sampled = MakeSampledImage(state, resource, mem.sampler);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
@@ -750,16 +746,18 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
state.builder.AddFunction(words);
|
||||
return sample;
|
||||
};
|
||||
const auto& image = state.program.info.images[mem.resource];
|
||||
if (image.indirect_root != mem.resource) {
|
||||
const auto sample = EmitSample(mem.resource);
|
||||
ctx.Define(inst, ResultVector(ctx, dref ? sample : UnpackImageTexel(ctx, mem, sample),
|
||||
integer, dref, mem));
|
||||
auto result = sample;
|
||||
if (!dref) {
|
||||
result = UnpackImageTexel(ctx, mem, sample);
|
||||
}
|
||||
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
|
||||
return true;
|
||||
}
|
||||
const auto* handle = image_arg.ResolveInstruction();
|
||||
const auto* source = image.source < ctx.program.descriptor_sources.size()
|
||||
? &ctx.program.descriptor_sources[image.source]
|
||||
const auto* source = image.source < state.program.descriptor_sources.size()
|
||||
? &state.program.descriptor_sources[image.source]
|
||||
: nullptr;
|
||||
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
|
||||
source->indirect_image->key_arg >= handle->NumArgs()) {
|
||||
@@ -767,7 +765,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return true;
|
||||
}
|
||||
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
|
||||
if (state.flattened_srt_variable == 0 || image.indirect_mapping_capacity == 0u ||
|
||||
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
return true;
|
||||
@@ -785,7 +783,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto low = ConstantU32(state, 0u);
|
||||
auto high = LoadMapping(mapping);
|
||||
auto selected = ConstantU32(state, 0u);
|
||||
for (uint32_t iteration = 0; iteration < std::bit_width(image.indirect_mapping_capacity);
|
||||
for (uint32_t iteration = 0; iteration < image.indirect_search_iterations;
|
||||
iteration++) {
|
||||
const auto active = Binary(state, OpULessThan, TypeBool(state), low, high);
|
||||
const auto mid =
|
||||
@@ -845,22 +843,24 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
}
|
||||
EmitLabel(state, merge_label);
|
||||
state.builder.AddFunction(phi_words);
|
||||
ctx.Define(inst,
|
||||
ResultVector(ctx, dref ? phi_words[2] : UnpackImageTexel(ctx, mem, phi_words[2]),
|
||||
integer, dref, mem));
|
||||
auto result = phi_words[2];
|
||||
if (!dref) {
|
||||
result = UnpackImageTexel(ctx, mem, result);
|
||||
}
|
||||
ctx.Define(inst, ResultVector(ctx, result, numeric_class, dref, mem));
|
||||
return true;
|
||||
}
|
||||
const auto atomic_opcode = ImageAtomicOpcode(op);
|
||||
if (atomic_opcode != 0u) {
|
||||
const auto view = StorageImageViewKind(mem);
|
||||
const auto dimension = image.dimension;
|
||||
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto pointer_type =
|
||||
state.builder.Type(OpTypePointer, {StorageClassImage, TypeU32(state)});
|
||||
state.builder.AddFunction(
|
||||
{OpImageTexelPointer, pointer_type, pointer,
|
||||
StorageImageDescriptorPointer(state, mem.resource, view),
|
||||
CoordU32(ctx, mem, *address, view), ConstantU32(state, 0)});
|
||||
StorageImageDescriptorPointer(state, mem.resource),
|
||||
CoordU32(ctx, mem, *address, dimension), ConstantU32(state, 0)});
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction({atomic_opcode, TypeU32(state), old, pointer,
|
||||
ConstantU32(state, ScopeDevice),
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "graphics/shader/recompiler/BufferFormat.h"
|
||||
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
|
||||
@@ -40,9 +39,11 @@ enum : uint32_t {
|
||||
MemoryModelGLSL450 = 1,
|
||||
CapabilityShader = 1,
|
||||
CapabilityInt64 = 11,
|
||||
CapabilityInt64Atomics = 12,
|
||||
CapabilityImageGatherExtended = 25,
|
||||
CapabilityClipDistance = 32,
|
||||
CapabilityCullDistance = 33,
|
||||
CapabilitySampleRateShading = 35,
|
||||
CapabilitySampled1D = 43,
|
||||
CapabilityImage1D = 44,
|
||||
CapabilityImageQuery = 50,
|
||||
@@ -50,8 +51,9 @@ enum : uint32_t {
|
||||
CapabilityGroupNonUniform = 61,
|
||||
CapabilityGroupNonUniformBallot = 64,
|
||||
CapabilityGroupNonUniformShuffle = 65,
|
||||
CapabilityShaderLayer = 69,
|
||||
CapabilityShaderViewportIndex = 70,
|
||||
CapabilitySignedZeroInfNanPreserve = 4466,
|
||||
CapabilityShaderViewportIndexLayerEXT = 5254,
|
||||
CapabilityFragmentBarycentricKHR = 5284,
|
||||
CapabilityComputeDerivativeGroupQuadsKHR = 5288,
|
||||
CapabilityPhysicalStorageBufferAddresses = 5347,
|
||||
@@ -59,6 +61,7 @@ enum : uint32_t {
|
||||
StorageClassInput = 1,
|
||||
StorageClassOutput = 3,
|
||||
StorageClassWorkgroup = 4,
|
||||
StorageClassPrivate = 6,
|
||||
StorageClassFunction = 7,
|
||||
StorageClassPushConstant = 9,
|
||||
StorageClassImage = 11,
|
||||
@@ -74,6 +77,7 @@ enum : uint32_t {
|
||||
DecorationBuiltIn = 11,
|
||||
DecorationNoPerspective = 13,
|
||||
DecorationFlat = 14,
|
||||
DecorationAliased = 20,
|
||||
DecorationLocation = 30,
|
||||
DecorationArrayStride = 6,
|
||||
DecorationBinding = 33,
|
||||
@@ -88,8 +92,10 @@ enum : uint32_t {
|
||||
BuiltInClipDistance = 3,
|
||||
BuiltInCullDistance = 4,
|
||||
BuiltInLayer = 9,
|
||||
BuiltInViewportIndex = 10,
|
||||
BuiltInFragCoord = 15,
|
||||
BuiltInFrontFacing = 17,
|
||||
BuiltInSampleId = 18,
|
||||
BuiltInSampleMask = 20,
|
||||
BuiltInFragDepth = 22,
|
||||
BuiltInWorkgroupId = 26,
|
||||
@@ -129,6 +135,7 @@ enum : uint32_t {
|
||||
MemorySemanticsAcquireRelease = 0x00000008u,
|
||||
MemorySemanticsUniformMemory = 0x00000040u,
|
||||
MemorySemanticsWorkgroupMemory = 0x00000100u,
|
||||
MemorySemanticsImageMemory = 0x00000800u,
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
@@ -196,6 +203,7 @@ enum : uint32_t {
|
||||
OpIMul = 132,
|
||||
OpFMul = 133,
|
||||
OpUDiv = 134,
|
||||
OpUMod = 137,
|
||||
OpFDiv = 136,
|
||||
OpIAddCarry = 149,
|
||||
OpUMulExtended = 151,
|
||||
@@ -316,67 +324,68 @@ struct OutputBinding {
|
||||
uint32_t location = 0;
|
||||
uint32_t variable_id = 0;
|
||||
std::string debug_name;
|
||||
uint32_t mesh_data_variable = 0;
|
||||
};
|
||||
|
||||
struct DescriptorResourceBinding {
|
||||
const IR::DescriptorBinding* descriptor = nullptr;
|
||||
uint32_t array_index = 0;
|
||||
using ImageDimension = Decoder::ImageDimension;
|
||||
|
||||
struct ImageDimensionInfo {
|
||||
ImageDimension dimension;
|
||||
uint32_t spirv_dimension;
|
||||
uint32_t coordinate_components;
|
||||
uint32_t spatial_components;
|
||||
uint32_t arrayed;
|
||||
uint32_t multisampled;
|
||||
};
|
||||
|
||||
enum class ImageViewKind {
|
||||
Dim1D,
|
||||
Dim1DArray,
|
||||
Dim2D,
|
||||
Dim2DArray,
|
||||
Dim3D,
|
||||
Dim2DMsaa,
|
||||
Dim2DMsaaArray,
|
||||
Count,
|
||||
};
|
||||
constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
|
||||
{ImageDimension::Dim1D, Dim1D, 1, 1, 0, 0},
|
||||
{ImageDimension::Dim1DArray, Dim1D, 2, 1, 1, 0},
|
||||
{ImageDimension::Dim2D, Dim2D, 2, 2, 0, 0},
|
||||
{ImageDimension::Dim2DArray, Dim2D, 3, 2, 1, 0},
|
||||
{ImageDimension::Dim3D, Dim3D, 3, 3, 0, 0},
|
||||
{ImageDimension::Dim2DMsaa, Dim2D, 2, 2, 0, 1},
|
||||
{ImageDimension::Dim2DMsaaArray, Dim2D, 3, 2, 1, 1},
|
||||
}};
|
||||
|
||||
constexpr uint32_t SampledImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Count);
|
||||
constexpr uint32_t StorageImageViewKindCount = static_cast<uint32_t>(ImageViewKind::Dim2DMsaa);
|
||||
|
||||
enum class StorageImageClass : uint32_t {
|
||||
FormatlessFloat,
|
||||
FormatlessUint,
|
||||
AtomicUint,
|
||||
Count,
|
||||
};
|
||||
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
|
||||
|
||||
struct EmitterState {
|
||||
EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_,
|
||||
ShaderStageInputInfo input_info_)
|
||||
: program(program_), resources(resources_), input_info(input_info_),
|
||||
requirements(*program_.spirv_requirements) {}
|
||||
EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_)
|
||||
: builder(program_.stage == ShaderType::Mesh ? 0x00010400u : 0x00010300u),
|
||||
program(program_), input_info(input_info_), requirements(*program_.spirv_requirements) {}
|
||||
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
const IR::ResourceSnapshot& resources;
|
||||
ShaderStageInputInfo input_info;
|
||||
const IR::SpirvRequirements& requirements;
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
uint32_t vsharp_storage_variable = 0;
|
||||
uint32_t flattened_srt_variable = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t scratch_variable = 0;
|
||||
std::array<uint32_t, 2u * SampledImageViewKindCount> sampled_image_variables {};
|
||||
std::array<uint32_t,
|
||||
static_cast<uint32_t>(StorageImageClass::Count) * StorageImageViewKindCount>
|
||||
storage_image_variables {};
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
ShaderStageInputInfo input_info;
|
||||
const IR::SpirvRequirements& requirements;
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint32_t lane_count = 1;
|
||||
uint32_t lane_half = 0;
|
||||
uint32_t storage_buffer_variable = 0;
|
||||
uint32_t storage_buffer_u64_variable = 0;
|
||||
std::array<uint32_t, IR::ShaderInfo::MaxBuffers> memory_byte_offsets {};
|
||||
uint32_t bda_pagetable_variable = 0;
|
||||
uint32_t fault_buffer_variable = 0;
|
||||
uint32_t bda_pointer_function = 0;
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
uint32_t shader_data_storage_variable = 0;
|
||||
uint32_t flattened_srt_variable = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
std::array<uint32_t, 2> scratch_variable {};
|
||||
std::array<uint32_t, IR::ImageBindingCount> image_variables {};
|
||||
uint32_t sampler_variable = 0;
|
||||
uint32_t main_func = 0;
|
||||
uint32_t mesh_guest_func = 0;
|
||||
uint32_t mesh_allocation = 0;
|
||||
uint32_t mesh_primitive_data = 0;
|
||||
uint32_t mesh_primitives = 0;
|
||||
uint32_t mesh_cull = 0;
|
||||
uint32_t entry_label = 0;
|
||||
uint32_t current_label = 0;
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t pixel_valid_mask_variable = 0;
|
||||
uint32_t subgroup_local_invocation_id_variable = 0;
|
||||
uint32_t per_vertex_variable = 0;
|
||||
@@ -384,6 +393,7 @@ struct EmitterState {
|
||||
uint32_t clip_distance_variable = 0;
|
||||
uint32_t cull_distance_variable = 0;
|
||||
uint32_t layer_variable = 0;
|
||||
uint32_t viewport_index_variable = 0;
|
||||
uint32_t clip_distance_count = 0;
|
||||
uint32_t cull_distance_count = 0;
|
||||
uint32_t depth_variable = 0;
|
||||
@@ -391,6 +401,7 @@ struct EmitterState {
|
||||
std::vector<InputBinding> inputs;
|
||||
std::vector<OutputBinding> outputs;
|
||||
std::vector<uint32_t> interface_variables;
|
||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||
};
|
||||
|
||||
uint32_t TypeVoid(EmitterState& state);
|
||||
@@ -399,6 +410,7 @@ uint32_t TypeBoolVector(EmitterState& state, uint32_t components);
|
||||
uint32_t TypeU32(EmitterState& state);
|
||||
uint32_t TypeU64(EmitterState& state);
|
||||
uint32_t TypeDeviceAddress(EmitterState& state);
|
||||
uint32_t TypeScalarU64(EmitterState& state);
|
||||
uint32_t TypeU32Pair(EmitterState& state);
|
||||
uint32_t TypeI32(EmitterState& state);
|
||||
uint32_t TypeI32Pair(EmitterState& state);
|
||||
@@ -412,6 +424,8 @@ uint32_t TypePointer(EmitterState& state, uint32_t storage_class, uint32_t point
|
||||
uint32_t TypeFunction(EmitterState& state);
|
||||
uint32_t TypeStorageBufferPointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferElementPointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferU64Pointer(EmitterState& state);
|
||||
uint32_t TypeStorageBufferU64ElementPointer(EmitterState& state);
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state);
|
||||
uint32_t TypePhysicalU32Pointer(EmitterState& state);
|
||||
uint32_t TypePushConstantElementPointer(EmitterState& state);
|
||||
@@ -423,12 +437,35 @@ inline void EmitLabel(EmitterState& state, uint32_t label) {
|
||||
state.builder.AddFunction({OpLabel, label});
|
||||
}
|
||||
|
||||
inline uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, value});
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
|
||||
uint32_t rhs) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, lhs, rhs});
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition,
|
||||
uint32_t true_value, uint32_t false_value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ValueEmitContext {
|
||||
ValueEmitContext(EmitterState& state_, const IR::Program& program_)
|
||||
: state(state_), program(program_) {}
|
||||
explicit ValueEmitContext(EmitterState& state_): state(state_) {}
|
||||
|
||||
uint32_t Def(IR::Value value);
|
||||
uint32_t Arg(const IR::Inst& inst, size_t index);
|
||||
uint32_t HalfArg(const IR::Inst& inst, size_t index, uint32_t half);
|
||||
uint32_t Ballot(IR::Value predicate);
|
||||
uint32_t FirstLane(uint32_t ballot);
|
||||
uint32_t Shuffle(const IR::Inst& inst, size_t index, uint32_t lane);
|
||||
uint32_t Result(const IR::Inst& inst);
|
||||
uint32_t TypeId(IR::Type type) const;
|
||||
uint32_t Emit(const IR::Inst& inst, uint32_t opcode, IR::Type type,
|
||||
@@ -443,13 +480,12 @@ struct ValueEmitContext {
|
||||
[[noreturn]] void Fail(const IR::Inst& inst, const char* reason) const;
|
||||
|
||||
EmitterState& state;
|
||||
const IR::Program& program;
|
||||
std::unordered_map<const IR::Inst*, uint32_t> definitions;
|
||||
std::unordered_map<const IR::Block*, uint32_t> labels;
|
||||
const std::unordered_map<const IR::Inst*, uint32_t>* dispatcher_spills = nullptr;
|
||||
std::unordered_map<const IR::Inst*, std::pair<uint32_t, uint32_t>> dispatcher_block_loads;
|
||||
const IR::Block* current_block = nullptr;
|
||||
uint32_t scratch_u32_variable = 0;
|
||||
ValueEmitContext* other_half = nullptr;
|
||||
uint32_t half = 0;
|
||||
};
|
||||
|
||||
enum class VertexInputScalarKind { Float, Sint, Uint };
|
||||
@@ -471,127 +507,12 @@ struct ImageSampleLayout {
|
||||
uint32_t grad_y = NoImageComponent;
|
||||
};
|
||||
|
||||
constexpr uint32_t SampledImageIndex(bool integer, ImageViewKind view) {
|
||||
return static_cast<uint32_t>(view) + (integer ? SampledImageViewKindCount : 0u);
|
||||
}
|
||||
|
||||
constexpr StorageImageClass StorageImageClassFor(bool uint_image, bool atomic) {
|
||||
if (atomic) {
|
||||
return StorageImageClass::AtomicUint;
|
||||
}
|
||||
if (uint_image) {
|
||||
return StorageImageClass::FormatlessUint;
|
||||
}
|
||||
return StorageImageClass::FormatlessFloat;
|
||||
}
|
||||
|
||||
constexpr uint32_t StorageImageIndex(StorageImageClass image_class, ImageViewKind view) {
|
||||
return static_cast<uint32_t>(view) +
|
||||
static_cast<uint32_t>(image_class) * StorageImageViewKindCount;
|
||||
}
|
||||
|
||||
constexpr IR::DescriptorBindingKind SampledBindingKind(bool integer, ImageViewKind view) {
|
||||
if (integer) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::SampledUint1D;
|
||||
case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::SampledUint1DArray;
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::SampledUint2D;
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::SampledUint2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::SampledUint3D;
|
||||
case ImageViewKind::Dim2DMsaa: return IR::DescriptorBindingKind::SampledUint2DMsaa;
|
||||
case ImageViewKind::Dim2DMsaaArray:
|
||||
return IR::DescriptorBindingKind::SampledUint2DMsaaArray;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Sampled1D;
|
||||
case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Sampled1DArray;
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Sampled2D;
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Sampled2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Sampled3D;
|
||||
case ImageViewKind::Dim2DMsaa: return IR::DescriptorBindingKind::Sampled2DMsaa;
|
||||
case ImageViewKind::Dim2DMsaaArray: return IR::DescriptorBindingKind::Sampled2DMsaaArray;
|
||||
default: break;
|
||||
}
|
||||
return IR::DescriptorBindingKind::Count;
|
||||
}
|
||||
|
||||
constexpr IR::DescriptorBindingKind StorageBindingKind(StorageImageClass image_class,
|
||||
ImageViewKind view) {
|
||||
switch (image_class) {
|
||||
case StorageImageClass::FormatlessFloat:
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::Storage1D;
|
||||
case ImageViewKind::Dim1DArray: return IR::DescriptorBindingKind::Storage1DArray;
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::Storage2D;
|
||||
case ImageViewKind::Dim2DArray: return IR::DescriptorBindingKind::Storage2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::Storage3D;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case StorageImageClass::FormatlessUint:
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageUint1D;
|
||||
case ImageViewKind::Dim1DArray:
|
||||
return IR::DescriptorBindingKind::StorageUint1DArray;
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageUint2D;
|
||||
case ImageViewKind::Dim2DArray:
|
||||
return IR::DescriptorBindingKind::StorageUint2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageUint3D;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case StorageImageClass::AtomicUint:
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D: return IR::DescriptorBindingKind::StorageAtomic1D;
|
||||
case ImageViewKind::Dim1DArray:
|
||||
return IR::DescriptorBindingKind::StorageAtomic1DArray;
|
||||
case ImageViewKind::Dim2D: return IR::DescriptorBindingKind::StorageAtomic2D;
|
||||
case ImageViewKind::Dim2DArray:
|
||||
return IR::DescriptorBindingKind::StorageAtomic2DArray;
|
||||
case ImageViewKind::Dim3D: return IR::DescriptorBindingKind::StorageAtomic3D;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case StorageImageClass::Count: break;
|
||||
}
|
||||
return IR::DescriptorBindingKind::Count;
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSpirvDimension(ImageViewKind view) {
|
||||
switch (view) {
|
||||
case ImageViewKind::Dim1D:
|
||||
case ImageViewKind::Dim1DArray: return Dim1D;
|
||||
case ImageViewKind::Dim2D:
|
||||
case ImageViewKind::Dim2DArray:
|
||||
case ImageViewKind::Dim2DMsaa:
|
||||
case ImageViewKind::Dim2DMsaaArray:
|
||||
case ImageViewKind::Count: return Dim2D;
|
||||
case ImageViewKind::Dim3D: return Dim3D;
|
||||
}
|
||||
return Dim2D;
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSpirvArrayed(ImageViewKind view) {
|
||||
return view == ImageViewKind::Dim1DArray || view == ImageViewKind::Dim2DArray ||
|
||||
view == ImageViewKind::Dim2DMsaaArray
|
||||
? 1u
|
||||
: 0u;
|
||||
}
|
||||
|
||||
constexpr uint32_t ImageSpirvMultisampled(ImageViewKind view) {
|
||||
return view == ImageViewKind::Dim2DMsaa || view == ImageViewKind::Dim2DMsaaArray ? 1u : 0u;
|
||||
}
|
||||
|
||||
struct F32Class {
|
||||
uint32_t bits = 0;
|
||||
uint32_t nan = 0;
|
||||
uint32_t zero = 0;
|
||||
};
|
||||
|
||||
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr);
|
||||
|
||||
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr);
|
||||
|
||||
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr);
|
||||
@@ -600,15 +521,10 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr);
|
||||
|
||||
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location);
|
||||
|
||||
uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input);
|
||||
uint32_t VertexParameterComponentCount(const InputBinding& input);
|
||||
|
||||
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind);
|
||||
|
||||
uint32_t VertexParameterScalarPointerType(EmitterState& state, VertexInputScalarKind kind);
|
||||
|
||||
uint32_t VertexParameterVectorOrScalarType(EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components);
|
||||
|
||||
uint32_t VertexParameterInputPointerType(EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components);
|
||||
|
||||
@@ -626,48 +542,33 @@ uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
const char* reason);
|
||||
|
||||
DescriptorResourceBinding ResourceForDescriptor(const EmitterState& state,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource);
|
||||
uint32_t ResourceForDescriptor(const EmitterState& state, IR::DescriptorBindingKind kind,
|
||||
uint32_t resource);
|
||||
|
||||
uint32_t DescriptorElementPointer(EmitterState& state, uint32_t result_ptr_type,
|
||||
uint32_t variable_id, uint32_t array_index,
|
||||
IR::DescriptorBindingKind kind, uint32_t resource,
|
||||
const char* variable_name);
|
||||
|
||||
ImageViewKind SampledImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
|
||||
uint32_t use_pc);
|
||||
uint32_t ImageScalarType(EmitterState& state, Prospero::TextureNumericClass numeric_class);
|
||||
|
||||
uint32_t ImageViewCoordinateComponents(ImageViewKind view);
|
||||
uint32_t ImageVectorType(EmitterState& state, Prospero::TextureNumericClass numeric_class,
|
||||
uint32_t components);
|
||||
|
||||
uint32_t ImageViewSpatialComponents(ImageViewKind view);
|
||||
uint32_t ImageType(EmitterState& state, const IR::ImageResource& image);
|
||||
|
||||
uint32_t ImageViewImageType(EmitterState& state, ImageViewKind view, bool integer);
|
||||
uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension);
|
||||
|
||||
uint32_t ImageViewSampledImageType(EmitterState& state, ImageViewKind view, bool integer);
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState& state, uint32_t resource);
|
||||
|
||||
uint32_t ImageViewSizeType(EmitterState& state, ImageViewKind view);
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler);
|
||||
|
||||
uint32_t StorageImageType(EmitterState& state, StorageImageClass image_class, ImageViewKind view);
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler);
|
||||
|
||||
uint32_t StorageImagePointerType(EmitterState& state, StorageImageClass image_class,
|
||||
ImageViewKind view);
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource);
|
||||
|
||||
uint32_t LoadSampledImageDescriptor(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view);
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t use_pc);
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view);
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view, uint32_t image_resource);
|
||||
|
||||
ImageViewKind StorageImageViewKind(const IR::MemoryInfo& mem);
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, ImageViewKind view);
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, ImageViewKind view,
|
||||
uint32_t mip_lod, uint32_t coord, uint32_t texel);
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel);
|
||||
|
||||
uint32_t ExecutionModelForStage(ShaderType stage);
|
||||
|
||||
@@ -705,8 +606,11 @@ void DecorateDescriptor(EmitterState& state, uint32_t variable, const char* name
|
||||
void AddDescriptorAnnotationsAndNames(EmitterState& state);
|
||||
|
||||
void DefineModule(EmitterState& state);
|
||||
|
||||
uint32_t EmitTrueBool(EmitterState& state);
|
||||
void DefineMeshOutputs(EmitterState& state);
|
||||
void EmitMeshEntryPoint(EmitterState& state);
|
||||
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0);
|
||||
uint32_t MeshPrimitivePointer(EmitterState& state);
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
|
||||
|
||||
@@ -755,11 +659,17 @@ struct MemoryResourceAccess {
|
||||
uint32_t object_pointer = 0;
|
||||
uint32_t length = 0;
|
||||
uint32_t index_offset = 0;
|
||||
uint32_t byte_offset = 0;
|
||||
bool add_index_offset = false;
|
||||
};
|
||||
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem);
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable,
|
||||
uint32_t pointer_type);
|
||||
|
||||
uint32_t EmitMemoryElementIndex(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t raw_index);
|
||||
|
||||
@@ -769,22 +679,17 @@ uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAcce
|
||||
uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index);
|
||||
|
||||
uint32_t EmitTBufferBitcastF32ToU32(EmitterState& state, uint32_t value);
|
||||
|
||||
uint32_t EmitTBufferBitcastU32ToF32(EmitterState& state, uint32_t value);
|
||||
uint32_t EmitStorageBufferElementPointer(EmitterState& state,
|
||||
const MemoryResourceAccess& access, uint32_t index,
|
||||
uint32_t pointer_type);
|
||||
|
||||
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value);
|
||||
|
||||
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
|
||||
uint32_t constant);
|
||||
|
||||
uint32_t EmitTBufferSelectF32(EmitterState& state, uint32_t condition, uint32_t true_value,
|
||||
uint32_t false_value);
|
||||
|
||||
bool IsSignedFormatComponent(Format::ComponentType type);
|
||||
|
||||
uint32_t EmitHalfToF32Bits(EmitterState& state, uint32_t raw);
|
||||
|
||||
uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits);
|
||||
|
||||
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
@@ -792,6 +697,9 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
|
||||
void EmitDeviceAtomicMemoryBarrier(EmitterState& state);
|
||||
|
||||
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
bool max_value);
|
||||
|
||||
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
|
||||
|
||||
uint32_t EmitSelectValueU32(EmitterState& state, uint32_t cond, uint32_t true_value,
|
||||
@@ -832,6 +740,8 @@ uint32_t EmitLogicalOrBool(EmitterState& state, uint32_t lhs, uint32_t rhs);
|
||||
|
||||
uint32_t EmitLogicalNotBool(EmitterState& state, uint32_t value);
|
||||
|
||||
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits);
|
||||
|
||||
F32Class EmitClassifyF32(EmitterState& state, uint32_t value);
|
||||
|
||||
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
|
||||
@@ -861,7 +771,7 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
|
||||
void EmitProgram(EmitterState& state, const IR::Program& program);
|
||||
void EmitProgram(EmitterState& state);
|
||||
|
||||
void DefineGetBdaPointer(EmitterState& state);
|
||||
|
||||
@@ -907,6 +817,47 @@ uint32_t EmitValueOrZeroIfCondition(EmitterState& state, uint32_t condition, Fn&
|
||||
std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, Fn&& desired) {
|
||||
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
const auto memory = [&] {
|
||||
switch (kind) {
|
||||
case IR::ResourceKind::Lds: return MemorySemanticsWorkgroupMemory;
|
||||
case IR::ResourceKind::Image: return MemorySemanticsImageMemory;
|
||||
default: return MemorySemanticsUniformMemory;
|
||||
}
|
||||
}();
|
||||
const auto preheader = state.builder.AllocateId();
|
||||
const auto header = state.builder.AllocateId();
|
||||
const auto cont = state.builder.AllocateId();
|
||||
const auto merge = state.builder.AllocateId();
|
||||
const auto initial = state.builder.AllocateId();
|
||||
const auto observed = state.builder.AllocateId();
|
||||
const auto exchanged = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBranch, preheader});
|
||||
EmitLabel(state, preheader);
|
||||
state.builder.AddFunction({OpAtomicLoad, TypeU32(state), initial, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, header);
|
||||
state.builder.AddFunction(
|
||||
{OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont});
|
||||
const auto next = desired(observed);
|
||||
state.builder.AddFunction({OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone),
|
||||
ConstantU32(state, MemorySemanticsNone), next, observed});
|
||||
const auto success = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpIEqual, TypeBool(state), success, exchanged, observed});
|
||||
state.builder.AddFunction({OpLoopMerge, merge, cont, LoopControlNone});
|
||||
state.builder.AddFunction({OpBranchConditional, success, merge, cont});
|
||||
EmitLabel(state, cont);
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, merge);
|
||||
state.builder.AddFunction({OpMemoryBarrier, ConstantU32(state, scope),
|
||||
ConstantU32(state, MemorySemanticsAcquireRelease | memory)});
|
||||
return observed;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SPIRVEMITTER_INTERNAL_H_ */
|
||||
@@ -7,31 +7,15 @@
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
|
||||
uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, value});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs, uint32_t rhs) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, type, result, lhs, rhs});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
|
||||
uint32_t false_value) {
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t AndCondition(EmitterState& state, uint32_t lhs, uint32_t rhs) {
|
||||
return Binary(state, OpLogicalAnd, TypeBool(state), lhs, rhs);
|
||||
}
|
||||
|
||||
uint32_t EmitDsMaskedLaneRead(EmitterState& state, uint32_t source, uint32_t target,
|
||||
uint32_t exec) {
|
||||
if (state.lane_count == 2) {
|
||||
target = Binary(state, OpBitwiseAnd, TypeU32(state), target, ConstantU32(state, 31));
|
||||
}
|
||||
const auto shuffled = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeU32(state), shuffled,
|
||||
ConstantU32(state, ScopeSubgroup), source, target});
|
||||
@@ -287,12 +271,6 @@ PreparedMemoryElement PrepareMemoryElement(ValueEmitContext& ctx, const IR::Memo
|
||||
return {.resource = resource, .index = index};
|
||||
}
|
||||
|
||||
PreparedMemoryElement PrepareMemoryElement(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource,
|
||||
uint32_t raw_index) {
|
||||
return {.resource = resource, .index = EmitMemoryElementIndex(ctx.state, resource, raw_index)};
|
||||
}
|
||||
|
||||
uint32_t LoadWordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess& resource,
|
||||
uint32_t index);
|
||||
|
||||
@@ -301,10 +279,10 @@ uint32_t LoadSubwordInBounds(ValueEmitContext& ctx, const MemoryResourceAccess&
|
||||
|
||||
uint32_t LoadWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource) {
|
||||
const auto access = PrepareMemoryElement(ctx, mem, resource, DwordIndex(ctx, inst, mem));
|
||||
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index),
|
||||
[&]() { return LoadWordInBounds(ctx, access.resource, access.index); });
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index),
|
||||
[&]() { return LoadWordInBounds(ctx, resource, index); });
|
||||
}
|
||||
|
||||
uint32_t LoadWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) {
|
||||
@@ -320,11 +298,10 @@ uint32_t LoadSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const
|
||||
const auto address = ByteAddress(ctx, inst, mem);
|
||||
const auto raw_index = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state), address,
|
||||
ConstantU32(ctx.state, 2));
|
||||
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
|
||||
const auto index = EmitMemoryElementIndex(ctx.state, resource, raw_index);
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
|
||||
return LoadSubwordInBounds(ctx, access.resource, address, access.index, bits,
|
||||
sign_extend);
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
return LoadSubwordInBounds(ctx, resource, address, index, bits, sign_extend);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -362,15 +339,14 @@ uint32_t LoadSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo
|
||||
});
|
||||
}
|
||||
|
||||
Prospero::BufferFormat BufferFormat(const ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem) {
|
||||
Prospero::BufferFormat BufferFormat(const ValueEmitContext& ctx, const IR::MemoryInfo& mem) {
|
||||
return mem.typed ? Format::DecodeTBufferFormat(mem.data_format, mem.number_format)
|
||||
: StorageBufferFormat(ctx.state, mem);
|
||||
}
|
||||
|
||||
IR::MemoryInfo RebaseFormattedComponent(IR::MemoryInfo mem, Prospero::BufferFormat format,
|
||||
IR::MemoryInfo RebaseFormattedComponent(IR::MemoryInfo mem, const Format::BufferFormatInfo& info,
|
||||
uint32_t component) {
|
||||
mem.offset += Format::GetFormatComponentByteOffset(format, component);
|
||||
mem.offset += Format::GetFormatComponentByteOffset(info, component);
|
||||
mem.data_dwords = 1u;
|
||||
mem.component_index = component;
|
||||
return mem;
|
||||
@@ -383,12 +359,8 @@ IR::MemoryInfo RebaseRawComponent(IR::MemoryInfo mem, uint32_t component) {
|
||||
return mem;
|
||||
}
|
||||
|
||||
enum class FormattedSourceKind { Memory, Zero, One };
|
||||
|
||||
struct FormattedSource {
|
||||
FormattedSourceKind kind = FormattedSourceKind::Zero;
|
||||
uint32_t component = 0;
|
||||
};
|
||||
using Format::FormattedSource;
|
||||
using Format::FormattedSourceKind;
|
||||
|
||||
FormattedSource ResolveFormattedSource(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const Format::BufferFormatInfo& info,
|
||||
@@ -400,29 +372,22 @@ FormattedSource ResolveFormattedSource(ValueEmitContext& ctx, const IR::MemoryIn
|
||||
}
|
||||
const auto selector = GetDstSel(ctx.state.program.info.buffers[mem.resource].descriptor_swizzle,
|
||||
output_component);
|
||||
if (selector == 0u) return {};
|
||||
if (selector == 1u) return {FormattedSourceKind::One, 0};
|
||||
if (selector < 4u) {
|
||||
const auto source = Format::ResolveFormattedSource(info, selector);
|
||||
if (source.kind == FormattedSourceKind::Invalid) {
|
||||
ExitDescriptorBindingFailure(ctx.state, IR::DescriptorBindingKind::Buffers, mem.resource,
|
||||
"buffer descriptor has reserved dst_sel");
|
||||
}
|
||||
const auto component = selector - 4u;
|
||||
return component < info.component_count
|
||||
? FormattedSource {FormattedSourceKind::Memory, component}
|
||||
: FormattedSource {};
|
||||
return source;
|
||||
}
|
||||
|
||||
uint32_t FormattedConstant(ValueEmitContext& ctx, const Format::BufferFormatInfo& info,
|
||||
FormattedSourceKind kind) {
|
||||
if (kind != FormattedSourceKind::One) return ConstantU32(ctx.state, 0);
|
||||
const auto integer =
|
||||
info.type == Format::ComponentType::Uint || info.type == Format::ComponentType::Sint;
|
||||
return ConstantU32(ctx.state, integer ? 1u : 0x3f800000u);
|
||||
return ConstantU32(ctx.state, Format::FormattedConstantBits(info, kind));
|
||||
}
|
||||
|
||||
template <typename LoadWordFn, typename LoadSubwordFn>
|
||||
uint32_t LoadFormattedComponent(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
Prospero::BufferFormat format, const Format::BufferFormatInfo& info,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t output_component, LoadWordFn&& load_word,
|
||||
LoadSubwordFn&& load_subword) {
|
||||
const auto source = ResolveFormattedSource(ctx, mem, info, output_component);
|
||||
@@ -457,19 +422,18 @@ uint32_t LoadFormattedComponent(ValueEmitContext& ctx, const IR::MemoryInfo& mem
|
||||
uint32_t FormattedLoadPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, uint32_t output_component,
|
||||
const MemoryResourceAccess& resource) {
|
||||
const auto format = BufferFormat(ctx, inst, mem);
|
||||
if (!Format::IsKnownFormat(format)) {
|
||||
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
|
||||
if (info.type == Format::ComponentType::Unknown) {
|
||||
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, output_component), resource);
|
||||
}
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
return LoadFormattedComponent(
|
||||
ctx, mem, format, info, output_component,
|
||||
ctx, mem, info, output_component,
|
||||
[&](uint32_t component) {
|
||||
return LoadWordPrepared(ctx, inst, RebaseFormattedComponent(mem, format, component),
|
||||
return LoadWordPrepared(ctx, inst, RebaseFormattedComponent(mem, info, component),
|
||||
resource);
|
||||
},
|
||||
[&](uint32_t component, uint32_t bits, bool sign_extend) {
|
||||
return LoadSubwordPrepared(ctx, inst, RebaseFormattedComponent(mem, format, component),
|
||||
return LoadSubwordPrepared(ctx, inst, RebaseFormattedComponent(mem, info, component),
|
||||
resource, bits, sign_extend);
|
||||
});
|
||||
}
|
||||
@@ -481,41 +445,6 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, Fn&& desired) {
|
||||
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory
|
||||
: MemorySemanticsUniformMemory;
|
||||
const auto preheader = state.builder.AllocateId();
|
||||
const auto header = state.builder.AllocateId();
|
||||
const auto cont = state.builder.AllocateId();
|
||||
const auto merge = state.builder.AllocateId();
|
||||
const auto initial = state.builder.AllocateId();
|
||||
const auto observed = state.builder.AllocateId();
|
||||
const auto exchanged = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBranch, preheader});
|
||||
EmitLabel(state, preheader);
|
||||
state.builder.AddFunction({OpAtomicLoad, TypeU32(state), initial, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, header);
|
||||
state.builder.AddFunction(
|
||||
{OpPhi, TypeU32(state), observed, initial, preheader, exchanged, cont});
|
||||
const auto next = desired(observed);
|
||||
state.builder.AddFunction({OpAtomicCompareExchange, TypeU32(state), exchanged, pointer,
|
||||
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone),
|
||||
ConstantU32(state, MemorySemanticsNone), next, observed});
|
||||
const auto success = Binary(state, OpIEqual, TypeBool(state), exchanged, observed);
|
||||
state.builder.AddFunction({OpLoopMerge, merge, cont, LoopControlNone});
|
||||
state.builder.AddFunction({OpBranchConditional, success, merge, cont});
|
||||
EmitLabel(state, cont);
|
||||
state.builder.AddFunction({OpBranch, header});
|
||||
EmitLabel(state, merge);
|
||||
state.builder.AddFunction({OpMemoryBarrier, ConstantU32(state, scope),
|
||||
ConstantU32(state, MemorySemanticsAcquireRelease | memory)});
|
||||
return observed;
|
||||
}
|
||||
|
||||
void StoreSubwordInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t address, uint32_t index,
|
||||
uint32_t bits, uint32_t data) {
|
||||
@@ -550,10 +479,10 @@ void StoreSubwordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR:
|
||||
const auto address = ByteAddress(ctx, inst, mem);
|
||||
const auto raw_index = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state), address,
|
||||
ConstantU32(ctx.state, 2));
|
||||
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
|
||||
const auto index = EmitMemoryElementIndex(ctx.state, resource, raw_index);
|
||||
EmitIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
|
||||
StoreSubwordInBounds(ctx, mem, access.resource, address, access.index, bits, data);
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
StoreSubwordInBounds(ctx, mem, resource, address, index, bits, data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -566,12 +495,11 @@ void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo me
|
||||
|
||||
void StoreWordPrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource, uint32_t data) {
|
||||
const auto access = PrepareMemoryElement(ctx, mem, resource, DwordIndex(ctx, inst, mem));
|
||||
const auto index = EmitMemoryElementIndex(ctx.state, resource, DwordIndex(ctx, inst, mem));
|
||||
EmitIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, resource, index), [&]() {
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpStore, EmitMemoryElementPointer(ctx.state, access.resource, access.index),
|
||||
data});
|
||||
{OpStore, EmitMemoryElementPointer(ctx.state, resource, index), data});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -591,15 +519,14 @@ void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem)
|
||||
void FormattedStorePrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
uint32_t component, const MemoryResourceAccess& resource,
|
||||
uint32_t data) {
|
||||
const auto format = BufferFormat(ctx, inst, mem);
|
||||
if (!Format::IsKnownFormat(format)) {
|
||||
const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem));
|
||||
if (info.type == Format::ComponentType::Unknown) {
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data);
|
||||
return;
|
||||
}
|
||||
const auto info = Format::GetFormatInfo(format);
|
||||
if (component >= info.component_count) return;
|
||||
const auto bits = info.component_bits[component];
|
||||
const auto component_mem = RebaseFormattedComponent(mem, format, component);
|
||||
const auto component_mem = RebaseFormattedComponent(mem, info, component);
|
||||
if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordPrepared(ctx, inst, component_mem, resource, bits, data);
|
||||
} else {
|
||||
@@ -614,9 +541,11 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
|
||||
uint32_t SpirvAtomicOpcode(IR::ValueOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case IR::ValueOpcode::BufferAtomicCmpSwap32: return OpAtomicCompareExchange;
|
||||
case IR::ValueOpcode::BufferAtomicSwap32:
|
||||
case IR::ValueOpcode::BufferAtomicSwap64:
|
||||
case IR::ValueOpcode::SharedAtomicSwap32: return OpAtomicExchange;
|
||||
case IR::ValueOpcode::BufferAtomicIAdd32:
|
||||
case IR::ValueOpcode::SharedAtomicIAdd32: return OpAtomicIAdd;
|
||||
@@ -633,6 +562,7 @@ uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
|
||||
case IR::ValueOpcode::BufferAtomicAnd32:
|
||||
case IR::ValueOpcode::SharedAtomicAnd32: return OpAtomicAnd;
|
||||
case IR::ValueOpcode::BufferAtomicOr32:
|
||||
case IR::ValueOpcode::BufferAtomicOr64:
|
||||
case IR::ValueOpcode::SharedAtomicOr32: return OpAtomicOr;
|
||||
case IR::ValueOpcode::BufferAtomicXor32:
|
||||
case IR::ValueOpcode::SharedAtomicXor32: return OpAtomicXor;
|
||||
@@ -640,87 +570,116 @@ uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
bool return_value) {
|
||||
const auto result =
|
||||
EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index),
|
||||
[&]() {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
const auto scope =
|
||||
mem.kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
ctx.state.builder.AddFunction(
|
||||
{AtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old,
|
||||
EmitMemoryElementPointer(ctx.state, access.resource, access.index),
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, MemorySemanticsNone),
|
||||
value});
|
||||
if (mem.kind == IR::ResourceKind::Lds) {
|
||||
const auto semantics =
|
||||
MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
|
||||
ctx.state.builder.AddFunction({OpMemoryBarrier,
|
||||
ConstantU32(ctx.state, ScopeWorkgroup),
|
||||
ConstantU32(ctx.state, semantics)});
|
||||
} else {
|
||||
EmitDeviceAtomicMemoryBarrier(ctx.state);
|
||||
}
|
||||
return old;
|
||||
});
|
||||
});
|
||||
return return_value ? result : ConstantU32(ctx.state, 0);
|
||||
uint32_t EmitAtomicOperation(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t pointer,
|
||||
uint32_t scope) {
|
||||
const auto old = ctx.state.builder.AllocateId();
|
||||
if (inst.GetOpcode() == IR::ValueOpcode::BufferAtomicCmpSwap32) {
|
||||
const auto desired = ctx.Arg(inst, inst.NumArgs() - 3);
|
||||
const auto comparator = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(
|
||||
{OpAtomicCompareExchange, TypeU32(ctx.state), old, pointer, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, MemorySemanticsNone),
|
||||
ConstantU32(ctx.state, MemorySemanticsNone), desired, comparator});
|
||||
} else {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
ctx.state.builder.AddFunction(
|
||||
{SpirvAtomicOpcode(inst.GetOpcode()), TypeU32(ctx.state), old, pointer,
|
||||
ConstantU32(ctx.state, scope), ConstantU32(ctx.state, MemorySemanticsNone), value});
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
uint32_t FloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
bool max_value) {
|
||||
struct OrderedBits {
|
||||
uint32_t nan;
|
||||
uint32_t zero;
|
||||
uint32_t key;
|
||||
};
|
||||
const auto classify = [&](uint32_t bits) {
|
||||
const auto value = Unary(state, OpBitcast, TypeF32(state), bits);
|
||||
const auto cls = EmitClassifyF32(state, value);
|
||||
const auto negative = Binary(
|
||||
state, OpINotEqual, TypeBool(state),
|
||||
Binary(state, OpBitwiseAnd, TypeU32(state), bits, ConstantU32(state, 0x80000000u)),
|
||||
ConstantU32(state, 0));
|
||||
const auto negative_key = Unary(state, OpNot, TypeU32(state), bits);
|
||||
const auto positive_key =
|
||||
Binary(state, OpBitwiseXor, TypeU32(state), bits, ConstantU32(state, 0x80000000u));
|
||||
return OrderedBits {cls.nan, cls.zero,
|
||||
Select(state, TypeU32(state), negative, negative_key, positive_key)};
|
||||
};
|
||||
const auto source_class = classify(source);
|
||||
const auto old_class = classify(old);
|
||||
const auto unordered =
|
||||
Binary(state, OpLogicalOr, TypeBool(state),
|
||||
Binary(state, OpLogicalOr, TypeBool(state), source_class.nan, old_class.nan),
|
||||
Binary(state, OpLogicalAnd, TypeBool(state), source_class.zero, old_class.zero));
|
||||
const auto ordered = Unary(state, OpLogicalNot, TypeBool(state), unordered);
|
||||
const auto compare = Binary(state, max_value ? OpUGreaterThan : OpULessThan, TypeBool(state),
|
||||
source_class.key, old_class.key);
|
||||
return Select(state, TypeU32(state),
|
||||
Binary(state, OpLogicalAnd, TypeBool(state), ordered, compare), source, old);
|
||||
}
|
||||
|
||||
uint32_t FloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
bool max_value) {
|
||||
template <typename Fn>
|
||||
uint32_t EmitAtomicAccess(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, Fn&& operation) {
|
||||
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
const auto access = PrepareMemoryElement(ctx, mem, DwordIndex(ctx, inst, mem));
|
||||
return EmitValueOrZeroIfCondition(
|
||||
ctx.state, EmitMemoryElementInBounds(ctx.state, access.resource, access.index), [&]() {
|
||||
const auto pointer =
|
||||
EmitMemoryElementPointer(ctx.state, access.resource, access.index);
|
||||
const auto source = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
return AtomicUpdate(ctx.state, pointer, mem.kind, [&](uint32_t old) {
|
||||
return FloatAtomicReplacement(ctx.state, old, source, max_value);
|
||||
});
|
||||
return operation(EmitMemoryElementPointer(ctx.state, access.resource, access.index));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
return EmitAtomicAccess(ctx, inst, mem, [&](uint32_t pointer) {
|
||||
const auto scope = mem.kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
|
||||
const auto old = EmitAtomicOperation(ctx, inst, pointer, scope);
|
||||
if (mem.kind == IR::ResourceKind::Lds) {
|
||||
const auto semantics = MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
|
||||
ctx.state.builder.AddFunction({OpMemoryBarrier, ConstantU32(ctx.state, scope),
|
||||
ConstantU32(ctx.state, semantics)});
|
||||
} else {
|
||||
EmitDeviceAtomicMemoryBarrier(ctx.state);
|
||||
}
|
||||
return old;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
uint32_t EmitAtomicUpdate(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, Fn&& replacement) {
|
||||
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
return EmitAtomicAccess(ctx, inst, mem, [&](uint32_t pointer) {
|
||||
return AtomicUpdate(ctx.state, pointer, mem.kind, [&](uint32_t old) {
|
||||
return replacement(ctx.state, old, value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t AtomicIncrement(EmitterState& state, uint32_t old, uint32_t limit) {
|
||||
// old >= limit ? 0 : old + 1 (unsigned).
|
||||
const auto wrap = Binary(state, OpUGreaterThanEqual, TypeBool(state), old, limit);
|
||||
const auto next = Binary(state, OpIAdd, TypeU32(state), old, ConstantU32(state, 1));
|
||||
return Select(state, TypeU32(state), wrap, ConstantU32(state, 0), next);
|
||||
}
|
||||
|
||||
uint32_t AtomicDecrement(EmitterState& state, uint32_t old, uint32_t limit) {
|
||||
// old == 0 || old > limit ? limit : old - 1 (unsigned).
|
||||
const auto zero = Binary(state, OpIEqual, TypeBool(state), old, ConstantU32(state, 0));
|
||||
const auto above = Binary(state, OpUGreaterThan, TypeBool(state), old, limit);
|
||||
const auto wrap = Binary(state, OpLogicalOr, TypeBool(state), zero, above);
|
||||
const auto next = Binary(state, OpISub, TypeU32(state), old, ConstantU32(state, 1));
|
||||
return Select(state, TypeU32(state), wrap, limit, next);
|
||||
}
|
||||
|
||||
uint32_t EmitBufferAtomic64(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem) {
|
||||
auto& state = ctx.state;
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, ctx.Arg(inst, inst.NumArgs() - 1), TypeU64(state), ConstantU64(state, 0), [&]() {
|
||||
const auto resource = PrepareStorageBufferResourceAccess(
|
||||
state, mem, state.storage_buffer_u64_variable, TypeStorageBufferU64Pointer(state));
|
||||
const auto byte_address = Binary(state, OpIAdd, TypeU32(state),
|
||||
ByteAddress(ctx, inst, mem), resource.byte_offset);
|
||||
const auto index = Binary(state, OpShiftRightLogical, TypeU32(state), byte_address,
|
||||
ConstantU32(state, 3u));
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, EmitMemoryElementInBounds(state, resource, index), TypeU64(state),
|
||||
ConstantU64(state, 0), [&]() {
|
||||
const auto value = Unary(state, OpBitcast, TypeScalarU64(state),
|
||||
ctx.Arg(inst, inst.NumArgs() - 2));
|
||||
const auto old = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{SpirvAtomicOpcode(inst.GetOpcode()), TypeScalarU64(state), old,
|
||||
EmitStorageBufferElementPointer(
|
||||
state, resource, index, TypeStorageBufferU64ElementPointer(state)),
|
||||
ConstantU32(state, ScopeDevice),
|
||||
ConstantU32(state, MemorySemanticsNone), value});
|
||||
EmitDeviceAtomicMemoryBarrier(state);
|
||||
return Unary(state, OpBitcast, TypeU64(state), old);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t FloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
bool max_value) {
|
||||
return EmitAtomicUpdate(ctx, inst, mem, [max_value](EmitterState& state, uint32_t old,
|
||||
uint32_t value) {
|
||||
return EmitFloatAtomicReplacement(state, old, value, max_value);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t SharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
|
||||
bool max_value) {
|
||||
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
|
||||
@@ -753,6 +712,9 @@ uint32_t SharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR
|
||||
|
||||
uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append) {
|
||||
auto& state = ctx.state;
|
||||
if (ctx.half == 1) {
|
||||
return ctx.other_half->Def(IR::Value(const_cast<IR::Inst*>(&inst)));
|
||||
}
|
||||
const auto m0 = ctx.Arg(inst, 0);
|
||||
const auto base =
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), m0, ConstantU32(state, 16));
|
||||
@@ -765,9 +727,7 @@ uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append)
|
||||
const auto access = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto index = EmitMemoryElementIndex(state, access, raw_index);
|
||||
const auto exec = ctx.Arg(inst, 1);
|
||||
const auto ballot = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallot, TypeU32Vector(state, 4), ballot,
|
||||
ConstantU32(state, ScopeSubgroup), exec});
|
||||
const auto ballot = ctx.Ballot(inst.Arg(1));
|
||||
const auto low = state.builder.AllocateId();
|
||||
const auto high = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeU32(state), low, ballot, 0});
|
||||
@@ -775,11 +735,12 @@ uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append)
|
||||
const auto count =
|
||||
Binary(state, OpIAdd, TypeU32(state), Unary(state, OpBitCount, TypeU32(state), low),
|
||||
Unary(state, OpBitCount, TypeU32(state), high));
|
||||
const auto first = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformBallotFindLSB, TypeU32(state), first,
|
||||
ConstantU32(state, ScopeSubgroup), ballot});
|
||||
const auto first = ctx.FirstLane(ballot);
|
||||
const auto source_lane = state.lane_count == 2 ? Binary(state, OpBitwiseAnd, TypeU32(state),
|
||||
first, ConstantU32(state, 31))
|
||||
: first;
|
||||
const auto is_first =
|
||||
Binary(state, OpIEqual, TypeBool(state), EmitSubgroupLocalInvocationId(state), first);
|
||||
Binary(state, OpIEqual, TypeBool(state), EmitSubgroupLocalInvocationId(state), source_lane);
|
||||
const auto storage_bounds = EmitMemoryElementInBounds(state, access, index);
|
||||
const auto m0_bounds =
|
||||
mem.kind == IR::ResourceKind::Gds
|
||||
@@ -787,7 +748,12 @@ uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append)
|
||||
: Binary(state, OpULessThan, TypeBool(state),
|
||||
ConstantU32(state, ctx.Memory(inst).offset + 3u), size);
|
||||
const auto condition = AndCondition(
|
||||
state, is_first, AndCondition(state, exec, AndCondition(state, storage_bounds, m0_bounds)));
|
||||
state, is_first,
|
||||
AndCondition(state,
|
||||
state.lane_count == 2
|
||||
? Binary(state, OpINotEqual, TypeBool(state), count, ConstantU32(state, 0))
|
||||
: exec,
|
||||
AndCondition(state, storage_bounds, m0_bounds)));
|
||||
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
@@ -799,12 +765,11 @@ uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool append)
|
||||
});
|
||||
const auto result = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpGroupNonUniformShuffle, TypeU32(state), result,
|
||||
ConstantU32(state, ScopeSubgroup), atomic, first});
|
||||
ConstantU32(state, ScopeSubgroup), atomic, source_lane});
|
||||
return result;
|
||||
}
|
||||
|
||||
struct PreparedFormattedMemory {
|
||||
Prospero::BufferFormat format = Prospero::BufferFormat::kInvalid;
|
||||
Format::BufferFormatInfo info;
|
||||
MemoryResourceAccess resource;
|
||||
std::array<uint32_t, 4> addresses {};
|
||||
@@ -817,10 +782,10 @@ enum class FormattedAccess { Load, Store };
|
||||
PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem,
|
||||
const MemoryResourceAccess& resource,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t components, FormattedAccess access) {
|
||||
PreparedFormattedMemory plan;
|
||||
plan.format = BufferFormat(ctx, inst, mem);
|
||||
plan.info = Format::GetFormatInfo(plan.format);
|
||||
plan.info = info;
|
||||
plan.resource = resource;
|
||||
std::array<bool, 4> required_components {};
|
||||
if (access == FormattedAccess::Load) {
|
||||
@@ -839,11 +804,11 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
|
||||
bool first_bound = true;
|
||||
for (uint32_t component = 0; component < plan.info.component_count; component++) {
|
||||
if (!required_components[component]) continue;
|
||||
const auto byte_offset = Format::GetFormatComponentByteOffset(plan.format, component);
|
||||
const auto byte_offset = Format::GetFormatComponentByteOffset(plan.info, component);
|
||||
bool reused = false;
|
||||
for (uint32_t previous = 0; previous < component; previous++) {
|
||||
if (required_components[previous] &&
|
||||
Format::GetFormatComponentByteOffset(plan.format, previous) == byte_offset) {
|
||||
Format::GetFormatComponentByteOffset(plan.info, previous) == byte_offset) {
|
||||
plan.addresses[component] = plan.addresses[previous];
|
||||
plan.indices[component] = plan.indices[previous];
|
||||
reused = true;
|
||||
@@ -851,7 +816,7 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
|
||||
}
|
||||
}
|
||||
if (reused) continue;
|
||||
const auto component_mem = RebaseFormattedComponent(mem, plan.format, component);
|
||||
const auto component_mem = RebaseFormattedComponent(mem, plan.info, component);
|
||||
plan.addresses[component] = ByteAddress(ctx, inst, component_mem);
|
||||
const auto raw_index = Binary(ctx.state, OpShiftRightLogical, TypeU32(ctx.state),
|
||||
plan.addresses[component], ConstantU32(ctx.state, 2));
|
||||
@@ -872,7 +837,7 @@ PreparedFormattedMemory PrepareFormattedMemory(ValueEmitContext& ctx, const IR::
|
||||
uint32_t LoadFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const PreparedFormattedMemory& plan, uint32_t output_component) {
|
||||
return LoadFormattedComponent(
|
||||
ctx, mem, plan.format, plan.info, output_component,
|
||||
ctx, mem, plan.info, output_component,
|
||||
[&](uint32_t component) {
|
||||
return LoadWordInBounds(ctx, plan.resource, plan.indices[component]);
|
||||
},
|
||||
@@ -921,10 +886,10 @@ uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t co
|
||||
ConstantU32CompositeZero(state, components), [&]() {
|
||||
const auto mem = ctx.Memory(inst);
|
||||
const auto resource = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto format =
|
||||
mem.formatted ? BufferFormat(ctx, inst, mem) : Prospero::BufferFormat::kInvalid;
|
||||
if (Format::IsKnownFormat(format)) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, components,
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, components,
|
||||
FormattedAccess::Load);
|
||||
return EmitValueOrDefaultIfCondition(
|
||||
state, plan.in_bounds, TypeU32Composite(state, components),
|
||||
@@ -951,10 +916,10 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
const auto mem = ctx.Memory(inst);
|
||||
const auto resource = PrepareMemoryResourceAccess(state, mem);
|
||||
const auto composite = ctx.Arg(inst, inst.NumArgs() - 2);
|
||||
const auto format =
|
||||
mem.formatted ? BufferFormat(ctx, inst, mem) : Prospero::BufferFormat::kInvalid;
|
||||
if (Format::IsKnownFormat(format)) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, components,
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, components,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(state, plan.in_bounds, [&]() {
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
@@ -990,10 +955,10 @@ uint32_t LoadWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t co
|
||||
ConstantU32(state, component * 4u));
|
||||
const auto raw_index = Binary(state, OpShiftRightLogical, TypeU32(state), address,
|
||||
ConstantU32(state, 2));
|
||||
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
|
||||
const auto index = EmitMemoryElementIndex(state, resource, raw_index);
|
||||
values[component] = EmitValueOrZeroIfCondition(
|
||||
state, EmitMemoryElementInBounds(state, access.resource, access.index),
|
||||
[&]() { return LoadWordInBounds(ctx, access.resource, access.index); });
|
||||
state, EmitMemoryElementInBounds(state, resource, index),
|
||||
[&]() { return LoadWordInBounds(ctx, resource, index); });
|
||||
}
|
||||
return ConstructU32Composite(state, components, values);
|
||||
});
|
||||
@@ -1011,11 +976,10 @@ void StoreWideShared(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
ConstantU32(state, component * 4u));
|
||||
const auto raw_index =
|
||||
Binary(state, OpShiftRightLogical, TypeU32(state), address, ConstantU32(state, 2));
|
||||
const auto access = PrepareMemoryElement(ctx, mem, resource, raw_index);
|
||||
EmitIfCondition(state, EmitMemoryElementInBounds(state, access.resource, access.index),
|
||||
const auto index = EmitMemoryElementIndex(state, resource, raw_index);
|
||||
EmitIfCondition(state, EmitMemoryElementInBounds(state, resource, index),
|
||||
[&]() {
|
||||
StoreWordInBounds(ctx, access.resource, access.index,
|
||||
ctx.Arg(inst, component + 1u));
|
||||
StoreWordInBounds(ctx, resource, index, ctx.Arg(inst, component + 1u));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1080,12 +1044,15 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
const auto buffer_components = IR::BufferComponentCount(op);
|
||||
if (buffer_components > 1u) {
|
||||
if (IR::BufferAccessOf(op) == IR::BufferAccess::Read) {
|
||||
const auto access = IR::BufferAccessOf(op);
|
||||
if (access == IR::BufferAccess::Read) {
|
||||
ctx.Define(inst, LoadWideBuffer(ctx, inst, buffer_components));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
if (access == IR::BufferAccess::Write) {
|
||||
StoreWideBuffer(ctx, inst, buffer_components);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const auto shared_components = IR::SharedComponentCount(op);
|
||||
if (shared_components > 1u) {
|
||||
@@ -1176,9 +1143,13 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
StoreWord(ctx, inst, mem);
|
||||
return true;
|
||||
}
|
||||
const auto atomic_opcode = AtomicOpcode(op);
|
||||
const auto atomic_opcode = SpirvAtomicOpcode(op);
|
||||
if (atomic_opcode != 0 && inst.GetType() == IR::Type::U64) {
|
||||
ctx.Define(inst, EmitBufferAtomic64(ctx, inst, ctx.Memory(inst)));
|
||||
return true;
|
||||
}
|
||||
if (atomic_opcode != 0) {
|
||||
ctx.Define(inst, EmitAtomic(ctx, inst, ctx.Memory(inst), true));
|
||||
ctx.Define(inst, EmitAtomic(ctx, inst, ctx.Memory(inst)));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::BufferAtomicFMin32 || op == IR::ValueOpcode::BufferAtomicFMax32) {
|
||||
@@ -1186,6 +1157,12 @@ bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
op == IR::ValueOpcode::BufferAtomicFMax32));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::SharedAtomicInc32 || op == IR::ValueOpcode::SharedAtomicDec32) {
|
||||
const auto replacement = op == IR::ValueOpcode::SharedAtomicInc32
|
||||
? AtomicIncrement : AtomicDecrement;
|
||||
ctx.Define(inst, EmitAtomicUpdate(ctx, inst, ctx.Memory(inst), replacement));
|
||||
return true;
|
||||
}
|
||||
if (op == IR::ValueOpcode::SharedAtomicFMin32 || op == IR::ValueOpcode::SharedAtomicFMax32) {
|
||||
SharedFloatAtomic(ctx, inst, ctx.Memory(inst), op == IR::ValueOpcode::SharedAtomicFMax32);
|
||||
return true;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
if (state.push_constant_variable != 0) {
|
||||
if (state.program.bindings.UsesPushData()) {
|
||||
dword_index += state.program.bindings.push_data_start_dword;
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypePushConstantElementPointer(state), pointer,
|
||||
@@ -12,11 +13,11 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
|
||||
return value;
|
||||
}
|
||||
if (state.vsharp_storage_variable != 0) {
|
||||
if (state.shader_data_storage_variable != 0) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.vsharp_storage_variable, ConstantU32(state, 0),
|
||||
state.shader_data_storage_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, dword_index)});
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
|
||||
return value;
|
||||
@@ -64,14 +65,15 @@ void EmitMemoryOffsets(EmitterState& state) {
|
||||
}
|
||||
|
||||
uint32_t LdsDwordCount(const EmitterState& state) {
|
||||
return state.stage == ShaderType::Compute ? state.input_info.compute->lds_size_dwords : 8192u;
|
||||
const auto* workgroup = ShaderWorkgroupInput(state.stage, state.input_info);
|
||||
return workgroup != nullptr ? workgroup->lds_size_dwords : 8192u;
|
||||
}
|
||||
|
||||
static void EnsureLdsStorage(EmitterState& state) {
|
||||
if (state.lds_variable != 0) {
|
||||
return;
|
||||
}
|
||||
if (state.stage != ShaderType::Compute) {
|
||||
if (ShaderWorkgroupInput(state.stage, state.input_info) == nullptr) {
|
||||
EXIT("function LDS was not prepared before SPIR-V function emission\n");
|
||||
}
|
||||
state.lds_variable = state.builder.DefineGlobalVariable(
|
||||
@@ -80,6 +82,27 @@ static void EnsureLdsStorage(EmitterState& state) {
|
||||
state.builder.AddName(state.lds_variable, "lds_dwords");
|
||||
}
|
||||
|
||||
MemoryResourceAccess PrepareStorageBufferResourceAccess(EmitterState& state,
|
||||
const IR::MemoryInfo& mem,
|
||||
uint32_t variable,
|
||||
uint32_t pointer_type) {
|
||||
if (variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
|
||||
"storage buffer descriptor array was not emitted");
|
||||
}
|
||||
const auto array_index =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
MemoryResourceAccess access {.kind = mem.kind};
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, pointer_type, access.object_pointer, variable,
|
||||
ConstantU32(state, array_index)});
|
||||
access.byte_offset = state.memory_byte_offsets[array_index];
|
||||
access.length = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpArrayLength, TypeU32(state), access.length, access.object_pointer, 0});
|
||||
return access;
|
||||
}
|
||||
|
||||
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem) {
|
||||
MemoryResourceAccess access {.kind = mem.kind};
|
||||
switch (mem.kind) {
|
||||
@@ -100,10 +123,10 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
access.length = state.gds_length;
|
||||
return access;
|
||||
case IR::ResourceKind::Scratch:
|
||||
if (state.scratch_variable == 0) {
|
||||
if (state.scratch_variable[state.lane_half] == 0) {
|
||||
EXIT("scratch storage was not prepared before SPIR-V function emission\n");
|
||||
}
|
||||
access.object_pointer = state.scratch_variable;
|
||||
access.object_pointer = state.scratch_variable[state.lane_half];
|
||||
access.length = ConstantU32(state, state.program.scratch_dwords);
|
||||
return access;
|
||||
case IR::ResourceKind::ScalarAddress:
|
||||
@@ -112,22 +135,13 @@ MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::
|
||||
EXIT("physical address memory must use the BDA emitter\n");
|
||||
case IR::ResourceKind::ScalarBuffer:
|
||||
case IR::ResourceKind::Buffer: {
|
||||
if (state.storage_buffer_variable == 0) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers,
|
||||
mem.resource,
|
||||
"storage buffer descriptor array was not emitted");
|
||||
}
|
||||
const auto binding =
|
||||
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
|
||||
access.object_pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferPointer(state),
|
||||
access.object_pointer, state.storage_buffer_variable,
|
||||
ConstantU32(state, binding.array_index)});
|
||||
access.index_offset = EmitBinaryU32(state, OpShiftRightLogical,
|
||||
state.memory_byte_offsets[binding.array_index],
|
||||
ConstantU32(state, 2u));
|
||||
access = PrepareStorageBufferResourceAccess(
|
||||
state, mem, state.storage_buffer_variable, TypeStorageBufferPointer(state));
|
||||
access.index_offset =
|
||||
EmitBinaryU32(state, OpShiftRightLogical, access.byte_offset,
|
||||
ConstantU32(state, 2u));
|
||||
access.add_index_offset = true;
|
||||
break;
|
||||
return access;
|
||||
}
|
||||
default: EXIT("unsupported memory resource kind: %u\n", static_cast<unsigned>(mem.kind));
|
||||
}
|
||||
@@ -151,45 +165,35 @@ uint32_t EmitMemoryElementInBounds(EmitterState& state, const MemoryResourceAcce
|
||||
|
||||
uint32_t EmitMemoryElementPointer(EmitterState& state, const MemoryResourceAccess& access,
|
||||
uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
if (access.kind == IR::ResourceKind::Lds || access.kind == IR::ResourceKind::Scratch) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto storage_class = access.kind == IR::ResourceKind::Scratch ? StorageClassFunction
|
||||
: state.stage == ShaderType::Compute ? StorageClassWorkgroup
|
||||
: StorageClassFunction;
|
||||
: ShaderWorkgroupInput(state.stage, state.input_info) != nullptr
|
||||
? StorageClassWorkgroup
|
||||
: StorageClassFunction;
|
||||
state.builder.AddFunction({OpAccessChain, TypeU32ElementPointer(state, storage_class),
|
||||
pointer, access.object_pointer, index});
|
||||
} else {
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
access.object_pointer, ConstantU32(state, 0), index});
|
||||
return pointer;
|
||||
}
|
||||
return EmitStorageBufferElementPointer(state, access, index,
|
||||
TypeStorageBufferElementPointer(state));
|
||||
}
|
||||
|
||||
uint32_t EmitStorageBufferElementPointer(EmitterState& state,
|
||||
const MemoryResourceAccess& access, uint32_t index,
|
||||
uint32_t pointer_type) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, pointer_type, pointer, access.object_pointer,
|
||||
ConstantU32(state, 0), index});
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint32_t EmitTBufferBitcastF32ToU32(EmitterState& state, uint32_t value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitcast, TypeU32(state), ret, value});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitTBufferBitcastU32ToF32(EmitterState& state, uint32_t value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitcast, TypeF32(state), ret, value});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitcast, TypeI32(state), ret, value});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
|
||||
uint32_t constant) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
state.builder.AddFunction({opcode, TypeBool(state), ret, value, ConstantU32(state, constant)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t EmitTBufferSelectF32(EmitterState& state, uint32_t condition, uint32_t true_value,
|
||||
uint32_t false_value) {
|
||||
const auto ret = state.builder.AllocateId();
|
||||
@@ -202,15 +206,6 @@ bool IsSignedFormatComponent(Format::ComponentType type) {
|
||||
type == Format::ComponentType::Sscaled;
|
||||
}
|
||||
|
||||
uint32_t EmitHalfToF32Bits(EmitterState& state, uint32_t raw) {
|
||||
const auto unpacked = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpExtInst, TypeF32Vector(state, 2), unpacked, GlslStd450(state), GlslUnpackHalf2x16, raw});
|
||||
state.builder.AddFunction({OpCompositeExtract, TypeF32(state), value, unpacked, 0});
|
||||
return EmitTBufferBitcastF32ToU32(state, value);
|
||||
}
|
||||
|
||||
uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
|
||||
const auto mantissa_bits = bits == 11u ? 6u : 5u;
|
||||
const auto mantissa_mask = (1u << mantissa_bits) - 1u;
|
||||
@@ -227,11 +222,11 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
|
||||
const auto mantissa_bits_32 =
|
||||
EmitBinaryU32(state, OpShiftLeftLogical, mantissa, ConstantU32(state, 23u - mantissa_bits));
|
||||
const auto normal_bits = EmitBinaryU32(state, OpBitwiseOr, exponent_bits, mantissa_bits_32);
|
||||
const auto normal = EmitTBufferBitcastU32ToF32(state, normal_bits);
|
||||
const auto normal = EmitBitcastU32ToF32(state, normal_bits);
|
||||
|
||||
const auto special_bits =
|
||||
EmitBinaryU32(state, OpBitwiseOr, ConstantU32(state, 0x7f800000u), mantissa_bits_32);
|
||||
const auto special = EmitTBufferBitcastU32ToF32(state, special_bits);
|
||||
const auto special = EmitBitcastU32ToF32(state, special_bits);
|
||||
|
||||
const auto mantissa_f32 = state.builder.AllocateId();
|
||||
const auto subnormal = state.builder.AllocateId();
|
||||
@@ -240,11 +235,11 @@ uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
|
||||
{OpFMul, TypeF32(state), subnormal, mantissa_f32,
|
||||
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits)))});
|
||||
|
||||
const auto zero_exp = EmitTBufferCompareU32Constant(state, OpIEqual, exponent, 0);
|
||||
const auto special_exp = EmitTBufferCompareU32Constant(state, OpIEqual, exponent, 31);
|
||||
const auto zero_exp = EmitCompareU32Constant(state, OpIEqual, exponent, 0);
|
||||
const auto special_exp = EmitCompareU32Constant(state, OpIEqual, exponent, 31);
|
||||
const auto finite = EmitTBufferSelectF32(state, zero_exp, subnormal, normal);
|
||||
const auto result = EmitTBufferSelectF32(state, special_exp, special, finite);
|
||||
return EmitTBufferBitcastF32ToU32(state, result);
|
||||
return EmitBitcastF32ToU32(state, result);
|
||||
}
|
||||
|
||||
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
|
||||
@@ -256,13 +251,13 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
case Format::ComponentType::Uscaled: {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpConvertUToF, TypeF32(state), value, raw});
|
||||
return EmitTBufferBitcastF32ToU32(state, value);
|
||||
return EmitBitcastF32ToU32(state, value);
|
||||
}
|
||||
case Format::ComponentType::Sscaled: {
|
||||
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpConvertSToF, TypeF32(state), value, signed_raw});
|
||||
return EmitTBufferBitcastF32ToU32(state, value);
|
||||
return EmitBitcastF32ToU32(state, value);
|
||||
}
|
||||
case Format::ComponentType::Unorm: {
|
||||
const auto value = state.builder.AllocateId();
|
||||
@@ -271,7 +266,7 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
state.builder.AddFunction({OpConvertUToF, TypeF32(state), value, raw});
|
||||
state.builder.AddFunction(
|
||||
{OpFDiv, TypeF32(state), normalized, value, ConstantF32Value(state, max_value)});
|
||||
return EmitTBufferBitcastF32ToU32(state, normalized);
|
||||
return EmitBitcastF32ToU32(state, normalized);
|
||||
}
|
||||
case Format::ComponentType::Snorm: {
|
||||
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
|
||||
@@ -284,14 +279,14 @@ uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferForma
|
||||
{OpFDiv, TypeF32(state), normalized, value, ConstantF32Value(state, max_value)});
|
||||
state.builder.AddFunction({OpExtInst, TypeF32(state), clamped, GlslStd450(state),
|
||||
GlslFMax, normalized, ConstantF32Value(state, -1.0f)});
|
||||
return EmitTBufferBitcastF32ToU32(state, clamped);
|
||||
return EmitBitcastF32ToU32(state, clamped);
|
||||
}
|
||||
case Format::ComponentType::Float:
|
||||
if (bits == 32u) {
|
||||
return raw;
|
||||
}
|
||||
if (bits == 16u) {
|
||||
return EmitHalfToF32Bits(state, raw);
|
||||
return EmitBitcastF32ToU32(state, EmitF16BitsToF32(state, raw));
|
||||
}
|
||||
return EmitUFloatToF32Bits(state, raw, bits);
|
||||
default: return raw;
|
||||
@@ -304,6 +299,38 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state) {
|
||||
{OpMemoryBarrier, ConstantU32(state, ScopeDevice), ConstantU32(state, semantics)});
|
||||
}
|
||||
|
||||
uint32_t EmitFloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
|
||||
bool max_value) {
|
||||
struct OrderedBits {
|
||||
uint32_t nan;
|
||||
uint32_t zero;
|
||||
uint32_t key;
|
||||
};
|
||||
const auto classify = [&](uint32_t bits) {
|
||||
const auto cls = EmitClassifyF32Bits(state, bits);
|
||||
const auto negative = EmitCompareU32Constant(
|
||||
state, OpINotEqual, EmitAndConstant(state, bits, 0x80000000u), 0u);
|
||||
const auto negative_key = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpNot, TypeU32(state), negative_key, bits});
|
||||
const auto positive_key = EmitBinaryU32(
|
||||
state, OpBitwiseXor, bits, ConstantU32(state, 0x80000000u));
|
||||
return OrderedBits {
|
||||
cls.nan, cls.zero,
|
||||
EmitSelectValueU32(state, negative, negative_key, positive_key)};
|
||||
};
|
||||
const auto source_class = classify(source);
|
||||
const auto old_class = classify(old);
|
||||
const auto unordered = EmitLogicalOrBool(
|
||||
state, EmitLogicalOrBool(state, source_class.nan, old_class.nan),
|
||||
EmitLogicalAndBool(state, source_class.zero, old_class.zero));
|
||||
const auto compare = state.builder.AllocateId();
|
||||
state.builder.AddFunction({max_value ? OpUGreaterThan : OpULessThan, TypeBool(state), compare,
|
||||
source_class.key, old_class.key});
|
||||
return EmitSelectValueU32(
|
||||
state, EmitLogicalAndBool(state, EmitLogicalNotBool(state, unordered), compare), source,
|
||||
old);
|
||||
}
|
||||
|
||||
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
|
||||
if ((control & 0xc000u) == 0xc000u) {
|
||||
const uint32_t mask = control & 0x1fu;
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
|
||||
uint32_t MeshArray(EmitterState& state, uint32_t storage, uint32_t type, uint32_t count) {
|
||||
const auto array = state.builder.Type(OpTypeArray, {type, ConstantU32(state, count)});
|
||||
return state.builder.DefineGlobalVariable(TypePointer(state, storage, array), storage);
|
||||
}
|
||||
|
||||
uint32_t MeshElement(EmitterState& state, uint32_t variable, uint32_t storage, uint32_t type,
|
||||
uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpAccessChain, TypePointer(state, storage, type), pointer, variable, index});
|
||||
return pointer;
|
||||
}
|
||||
|
||||
uint32_t MeshLoad(EmitterState& state, uint32_t variable, uint32_t storage, uint32_t type,
|
||||
uint32_t index) {
|
||||
const auto pointer = MeshElement(state, variable, storage, type, index);
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, type, value, pointer});
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t MeshOutputType(EmitterState& state, IR::StageOutputKind kind) {
|
||||
return kind == IR::StageOutputKind::Layer ? TypeU32(state) : TypeF32Vector(state, 4);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DefineMeshOutputs(EmitterState& state) {
|
||||
const auto& mesh = state.input_info.vertex->mesh;
|
||||
for (auto& output: state.outputs) {
|
||||
if (output.kind != IR::StageOutputKind::Position &&
|
||||
output.kind != IR::StageOutputKind::Parameter &&
|
||||
output.kind != IR::StageOutputKind::Layer) {
|
||||
EXIT("unsupported mesh output kind=%u\n", static_cast<uint32_t>(output.kind));
|
||||
}
|
||||
const auto type = MeshOutputType(state, output.kind);
|
||||
output.variable_id = MeshArray(
|
||||
state, StorageClassOutput, type,
|
||||
output.kind == IR::StageOutputKind::Layer ? mesh.max_primitives : mesh.max_vertices);
|
||||
// Only Layer is read by another invocation, through the primitive's provoking vertex.
|
||||
const bool shared = output.kind == IR::StageOutputKind::Layer;
|
||||
output.mesh_data_variable = MeshArray(
|
||||
state, shared ? StorageClassWorkgroup : StorageClassPrivate, type,
|
||||
shared ? mesh.max_vertices : state.lane_count);
|
||||
state.interface_variables.push_back(output.variable_id);
|
||||
state.builder.AddName(output.variable_id, output.debug_name.c_str());
|
||||
if (output.kind == IR::StageOutputKind::Parameter) {
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, output.variable_id, DecorationLocation, output.location});
|
||||
} else {
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, output.variable_id, DecorationBuiltIn,
|
||||
output.kind == IR::StageOutputKind::Layer ? BuiltInLayer : BuiltInPosition});
|
||||
}
|
||||
if (output.kind == IR::StageOutputKind::Layer) {
|
||||
state.builder.AddAnnotation({OpDecorate, output.variable_id, 5271u}); // PerPrimitiveEXT
|
||||
}
|
||||
}
|
||||
state.mesh_allocation = MeshArray(state, StorageClassWorkgroup, TypeU32(state), 2);
|
||||
state.mesh_primitive_data =
|
||||
MeshArray(state, StorageClassPrivate, TypeU32(state), state.lane_count);
|
||||
state.mesh_primitives =
|
||||
MeshArray(state, StorageClassOutput, TypeU32Vector(state, 3), mesh.max_primitives);
|
||||
state.mesh_cull = MeshArray(state, StorageClassOutput, TypeBool(state), mesh.max_primitives);
|
||||
state.interface_variables.push_back(state.mesh_primitives);
|
||||
state.interface_variables.push_back(state.mesh_cull);
|
||||
state.builder.AddAnnotation({OpDecorate, state.mesh_primitives, DecorationBuiltIn,
|
||||
5296u}); // PrimitiveTriangleIndicesEXT
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, state.mesh_cull, DecorationBuiltIn, 5299u}); // CullPrimitiveEXT
|
||||
state.builder.AddAnnotation({OpDecorate, state.mesh_cull, 5271u});
|
||||
}
|
||||
|
||||
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
|
||||
const auto output = std::ranges::find_if(state.outputs, [=](const OutputBinding& binding) {
|
||||
return binding.kind == kind && binding.index == index;
|
||||
});
|
||||
if (output == state.outputs.end()) {
|
||||
EXIT("mesh export has no output binding: kind=%u index=%u\n", static_cast<uint32_t>(kind),
|
||||
index);
|
||||
}
|
||||
const bool shared = kind == IR::StageOutputKind::Layer;
|
||||
return MeshElement(state, output->mesh_data_variable,
|
||||
shared ? StorageClassWorkgroup : StorageClassPrivate,
|
||||
MeshOutputType(state, kind),
|
||||
shared ? EmitLocalInvocationIndex(state)
|
||||
: ConstantU32(state, state.lane_half));
|
||||
}
|
||||
|
||||
uint32_t MeshPrimitivePointer(EmitterState& state) {
|
||||
return MeshElement(state, state.mesh_primitive_data, StorageClassPrivate, TypeU32(state),
|
||||
ConstantU32(state, state.lane_half));
|
||||
}
|
||||
|
||||
void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto first = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpIEqual, TypeBool(state), first, EmitLocalInvocationIndex(state), ConstantU32(state, 0)});
|
||||
EmitIfCondition(state, first, [&] {
|
||||
const auto allocation = ctx.Arg(inst, 0);
|
||||
for (uint32_t field = 0; field < 2; field++) {
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitFieldUExtract, TypeU32(state), value, allocation,
|
||||
ConstantU32(state, field * 12u),
|
||||
ConstantU32(state, field == 0 ? 10u : 11u)});
|
||||
const auto pointer = MeshElement(state, state.mesh_allocation, StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, field));
|
||||
state.builder.AddFunction({OpStore, pointer, value});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EmitMeshEntryPoint(EmitterState& state) {
|
||||
state.builder.AddFunction(
|
||||
{OpFunction, TypeVoid(state), state.main_func, FunctionControlNone, TypeFunction(state)});
|
||||
EmitLabel(state, state.builder.AllocateId());
|
||||
state.builder.AddFunction(
|
||||
{OpFunctionCall, TypeVoid(state), state.builder.AllocateId(), state.mesh_guest_func});
|
||||
// All guest waves finish before the uniform Vulkan allocation and output stores.
|
||||
state.builder.AddFunction(
|
||||
{OpControlBarrier, ConstantU32(state, ScopeWorkgroup), ConstantU32(state, ScopeWorkgroup),
|
||||
ConstantU32(state, MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory)});
|
||||
const auto vertices = MeshLoad(state, state.mesh_allocation, StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, 0));
|
||||
const auto primitives = MeshLoad(state, state.mesh_allocation, StorageClassWorkgroup,
|
||||
TypeU32(state), ConstantU32(state, 1));
|
||||
state.builder.AddFunction({5295u, vertices, primitives}); // OpSetMeshOutputsEXT
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.lane_half = half;
|
||||
const auto index = EmitLocalInvocationIndex(state);
|
||||
const auto is_vertex = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpULessThan, TypeBool(state), is_vertex, index, vertices});
|
||||
EmitIfCondition(state, is_vertex, [&] {
|
||||
for (const auto& output: state.outputs) {
|
||||
if (output.kind == IR::StageOutputKind::Layer) {
|
||||
continue;
|
||||
}
|
||||
const auto type = MeshOutputType(state, output.kind);
|
||||
const auto value = MeshLoad(state, output.mesh_data_variable, StorageClassPrivate,
|
||||
type, ConstantU32(state, half));
|
||||
const auto pointer =
|
||||
MeshElement(state, output.variable_id, StorageClassOutput, type, index);
|
||||
state.builder.AddFunction({OpStore, pointer, value});
|
||||
}
|
||||
});
|
||||
const auto is_primitive = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpULessThan, TypeBool(state), is_primitive, index, primitives});
|
||||
EmitIfCondition(state, is_primitive, [&] {
|
||||
const auto packed = MeshLoad(state, state.mesh_primitive_data, StorageClassPrivate,
|
||||
TypeU32(state), ConstantU32(state, half));
|
||||
uint32_t vertex[3] {};
|
||||
for (uint32_t component = 0; component < 3; component++) {
|
||||
vertex[component] = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpBitFieldUExtract, TypeU32(state), vertex[component],
|
||||
packed, ConstantU32(state, component * 10u),
|
||||
ConstantU32(state, 10)});
|
||||
}
|
||||
const auto triangle = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpCompositeConstruct, TypeU32Vector(state, 3), triangle,
|
||||
vertex[0], vertex[1], vertex[2]});
|
||||
const auto triangle_pointer = MeshElement(
|
||||
state, state.mesh_primitives, StorageClassOutput, TypeU32Vector(state, 3), index);
|
||||
state.builder.AddFunction({OpStore, triangle_pointer, triangle});
|
||||
const auto null_bit =
|
||||
EmitBinaryU32(state, OpBitwiseAnd, packed, ConstantU32(state, 0x80000000u));
|
||||
const auto culled = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpINotEqual, TypeBool(state), culled, null_bit, ConstantU32(state, 0)});
|
||||
state.builder.AddFunction(
|
||||
{OpStore,
|
||||
MeshElement(state, state.mesh_cull, StorageClassOutput, TypeBool(state), index),
|
||||
culled});
|
||||
for (const auto& output: state.outputs) {
|
||||
if (output.kind != IR::StageOutputKind::Layer) {
|
||||
continue;
|
||||
}
|
||||
const auto layer = MeshLoad(
|
||||
state, output.mesh_data_variable, StorageClassWorkgroup, TypeU32(state),
|
||||
vertex[state.input_info.vertex->mesh.provoking_vertex]);
|
||||
const auto pointer = MeshElement(state, output.variable_id, StorageClassOutput,
|
||||
TypeU32(state), index);
|
||||
state.builder.AddFunction({OpStore, pointer, layer});
|
||||
}
|
||||
});
|
||||
}
|
||||
state.lane_half = 0;
|
||||
state.builder.AddFunction({OpReturn});
|
||||
state.builder.AddFunction({OpFunctionEnd});
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
|
||||
@@ -29,6 +29,10 @@ uint32_t TypeU64(EmitterState& state) {
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddress(EmitterState& state) {
|
||||
return TypeScalarU64(state);
|
||||
}
|
||||
|
||||
uint32_t TypeScalarU64(EmitterState& state) {
|
||||
return state.builder.Type(OpTypeInt, {64, 0});
|
||||
}
|
||||
|
||||
@@ -94,6 +98,25 @@ uint32_t TypeStorageBufferElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeU32(state));
|
||||
}
|
||||
|
||||
uint32_t StorageU64RuntimeArrayType(EmitterState& state) {
|
||||
return state.builder.DecoratedType(OpTypeRuntimeArray, {TypeScalarU64(state)},
|
||||
{{OpDecorate, {DecorationArrayStride, sizeof(uint64_t)}}});
|
||||
}
|
||||
|
||||
uint32_t StorageBufferU64Type(EmitterState& state) {
|
||||
return state.builder.DecoratedType(
|
||||
OpTypeStruct, {StorageU64RuntimeArrayType(state)},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, 0}}, {OpDecorate, {DecorationBlock}}});
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferU64Pointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, StorageBufferU64Type(state));
|
||||
}
|
||||
|
||||
uint32_t TypeStorageBufferU64ElementPointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeScalarU64(state));
|
||||
}
|
||||
|
||||
uint32_t TypeDeviceAddressStoragePointer(EmitterState& state) {
|
||||
return TypePointer(state, StorageClassStorageBuffer, TypeDeviceAddress(state));
|
||||
}
|
||||
@@ -119,8 +142,7 @@ uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class) {
|
||||
namespace {
|
||||
|
||||
uint32_t PushConstantArrayType(EmitterState& state) {
|
||||
const auto count =
|
||||
ConstantU32(state, state.program.bindings.push_constant_size / sizeof(uint32_t));
|
||||
const auto count = ConstantU32(state, IR::PushData::DwordCount);
|
||||
return state.builder.DecoratedType(OpTypeArray, {TypeU32(state), count},
|
||||
{{OpDecorate, {DecorationArrayStride, sizeof(uint32_t)}}});
|
||||
}
|
||||
@@ -128,7 +150,7 @@ uint32_t PushConstantArrayType(EmitterState& state) {
|
||||
uint32_t PushConstantBlockType(EmitterState& state) {
|
||||
return state.builder.DecoratedType(
|
||||
OpTypeStruct, {PushConstantArrayType(state)},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, state.program.bindings.push_constant_offset}},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, 0}},
|
||||
{OpDecorate, {DecorationBlock}}});
|
||||
}
|
||||
|
||||
@@ -163,6 +185,13 @@ void DefineDescriptorVariables(EmitterState& state) {
|
||||
const auto pointer_type = TypePointer(state, StorageClassStorageBuffer, array_type);
|
||||
state.storage_buffer_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassStorageBuffer);
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
const auto u64_array_type =
|
||||
state.builder.Type(OpTypeArray, {StorageBufferU64Type(state), count});
|
||||
state.storage_buffer_u64_variable = state.builder.DefineGlobalVariable(
|
||||
TypePointer(state, StorageClassStorageBuffer, u64_array_type),
|
||||
StorageClassStorageBuffer);
|
||||
}
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::BdaPagetable) != nullptr) {
|
||||
state.bda_pagetable_variable = state.builder.DefineGlobalVariable(
|
||||
@@ -173,52 +202,37 @@ void DefineDescriptorVariables(EmitterState& state) {
|
||||
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
if (state.program.bindings.push_constant_size != 0) {
|
||||
if (state.program.bindings.UsesPushData() || state.stage == ShaderType::Mesh) {
|
||||
const auto pointer_type =
|
||||
TypePointer(state, StorageClassPushConstant, PushConstantBlockType(state));
|
||||
state.push_constant_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassPushConstant);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
state.vsharp_storage_variable = state.builder.DefineGlobalVariable(
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::ShaderData) != nullptr) {
|
||||
state.shader_data_storage_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
|
||||
state.flattened_srt_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
for (uint32_t i = 0; i < state.sampled_image_variables.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % SampledImageViewKindCount);
|
||||
const bool integer = i >= SampledImageViewKindCount;
|
||||
const auto kind = SampledBindingKind(integer, view);
|
||||
if (DescriptorBinding(state, kind) == nullptr) {
|
||||
for (const auto& binding: state.program.bindings.descriptors) {
|
||||
if (IR::ImageBindingResourceClass(binding.kind) == IR::ImageResourceClass::None) {
|
||||
continue;
|
||||
}
|
||||
const auto count = ConstantU32(state, DescriptorCount(state, kind));
|
||||
const auto image_type = ImageViewImageType(state, view, integer);
|
||||
const auto array_type = state.builder.Type(OpTypeArray, {image_type, count});
|
||||
const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type);
|
||||
state.sampled_image_variables[i] =
|
||||
const auto& image = state.program.info.images.at(binding.resources.front());
|
||||
const auto count = ConstantU32(state, DescriptorCount(state, binding.kind));
|
||||
const auto image_type = ImageType(state, image);
|
||||
const auto array_type = state.builder.Type(OpTypeArray, {image_type, count});
|
||||
const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type);
|
||||
state.image_variables[IR::ImageBindingIndex(binding.kind)] =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassUniformConstant);
|
||||
if (view == ImageViewKind::Dim1D || view == ImageViewKind::Dim1DArray) {
|
||||
state.builder.RequireCapability(CapabilitySampled1D);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
const auto image_class = static_cast<StorageImageClass>(i / StorageImageViewKindCount);
|
||||
const auto kind = StorageBindingKind(image_class, view);
|
||||
if (DescriptorBinding(state, kind) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto count = ConstantU32(state, DescriptorCount(state, kind));
|
||||
const auto image_type = StorageImageType(state, image_class, view);
|
||||
const auto array_type = state.builder.Type(OpTypeArray, {image_type, count});
|
||||
const auto pointer_type = TypePointer(state, StorageClassUniformConstant, array_type);
|
||||
state.storage_image_variables[i] =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassUniformConstant);
|
||||
if (view == ImageViewKind::Dim1D || view == ImageViewKind::Dim1DArray) {
|
||||
state.builder.RequireCapability(CapabilityImage1D);
|
||||
if (image.dimension == ImageDimension::Dim1D ||
|
||||
image.dimension == ImageDimension::Dim1DArray) {
|
||||
const auto capability = image.resource_class == IR::ImageResourceClass::Sampled
|
||||
? CapabilitySampled1D
|
||||
: CapabilityImage1D;
|
||||
state.builder.RequireCapability(capability);
|
||||
}
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::Samplers) != nullptr) {
|
||||
@@ -322,15 +336,8 @@ VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint3
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input) {
|
||||
uint32_t count = input.component_count;
|
||||
if (state.stage == ShaderType::Vertex && input.location < ShaderVertexInputInfo::RES_MAX &&
|
||||
input.location < static_cast<uint32_t>(state.input_info.vertex->resources_num) &&
|
||||
state.input_info.vertex->resources_dst[input.location].registers_num > 0) {
|
||||
count = static_cast<uint32_t>(
|
||||
state.input_info.vertex->resources_dst[input.location].registers_num);
|
||||
}
|
||||
return std::clamp(count, 1u, 4u);
|
||||
uint32_t VertexParameterComponentCount(const InputBinding& input) {
|
||||
return std::clamp(input.component_count, 1u, 4u);
|
||||
}
|
||||
|
||||
uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind) {
|
||||
@@ -342,45 +349,6 @@ uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind ki
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterScalarPointerType(EmitterState& state, VertexInputScalarKind kind) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint:
|
||||
return TypePointer(state, StorageClassInput, TypeI32(state));
|
||||
case VertexInputScalarKind::Uint:
|
||||
return TypePointer(state, StorageClassInput, TypeU32(state));
|
||||
case VertexInputScalarKind::Float:
|
||||
default: return TypePointer(state, StorageClassInput, TypeF32(state));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterVectorOrScalarType(EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components) {
|
||||
switch (kind) {
|
||||
case VertexInputScalarKind::Sint:
|
||||
switch (components) {
|
||||
case 1: return TypeI32(state);
|
||||
case 2: return TypeI32Vector(state, 2);
|
||||
case 3: return TypeI32Vector(state, 3);
|
||||
default: return TypeI32Vector(state, 4);
|
||||
}
|
||||
case VertexInputScalarKind::Uint:
|
||||
switch (components) {
|
||||
case 1: return TypeU32(state);
|
||||
case 2: return TypeU32Vector(state, 2);
|
||||
case 3: return TypeU32Vector(state, 3);
|
||||
default: return TypeU32Vector(state, 4);
|
||||
}
|
||||
case VertexInputScalarKind::Float:
|
||||
default:
|
||||
switch (components) {
|
||||
case 1: return TypeF32(state);
|
||||
case 2: return TypeF32Vector(state, 2);
|
||||
case 3: return TypeF32Vector(state, 3);
|
||||
default: return TypeF32Vector(state, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VertexParameterInputPointerType(EmitterState& state, VertexInputScalarKind kind,
|
||||
uint32_t components) {
|
||||
switch (kind) {
|
||||
@@ -416,6 +384,22 @@ static bool MrtUsesUintOutput(const EmitterState& state, uint32_t index) {
|
||||
}
|
||||
|
||||
void AllocateInputVariables(EmitterState& state) {
|
||||
if (state.lane_count == 2) {
|
||||
const auto add_builtin = [&](IR::StageInputKind kind, uint32_t components,
|
||||
const char* name) {
|
||||
if (std::ranges::none_of(state.inputs, [kind](const InputBinding& input) {
|
||||
return input.kind == kind;
|
||||
})) {
|
||||
state.inputs.push_back({kind, 0, components, 0, name});
|
||||
}
|
||||
};
|
||||
add_builtin(IR::StageInputKind::LocalInvocationIndex, 1, "gl_LocalInvocationIndex");
|
||||
if (std::ranges::any_of(state.inputs, [](const InputBinding& input) {
|
||||
return input.kind == IR::StageInputKind::GlobalInvocationId;
|
||||
})) {
|
||||
add_builtin(IR::StageInputKind::WorkgroupId, 3, "gl_WorkGroupID");
|
||||
}
|
||||
}
|
||||
for (auto& binding: state.inputs) {
|
||||
binding.variable_id = state.builder.AllocateId();
|
||||
state.interface_variables.push_back(binding.variable_id);
|
||||
@@ -440,6 +424,10 @@ static uint32_t AllocateSharedOutputVariable(EmitterState& state, uint32_t& vari
|
||||
}
|
||||
|
||||
void AllocateOutputVariables(EmitterState& state) {
|
||||
if (state.stage == ShaderType::Mesh) {
|
||||
DefineMeshOutputs(state);
|
||||
return;
|
||||
}
|
||||
for (auto& binding: state.outputs) {
|
||||
switch (binding.kind) {
|
||||
case IR::StageOutputKind::Position:
|
||||
@@ -463,6 +451,10 @@ void AllocateOutputVariables(EmitterState& state) {
|
||||
case IR::StageOutputKind::Layer:
|
||||
binding.variable_id = AllocateSharedOutputVariable(state, state.layer_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::ViewportIndex:
|
||||
binding.variable_id =
|
||||
AllocateSharedOutputVariable(state, state.viewport_index_variable);
|
||||
break;
|
||||
case IR::StageOutputKind::Depth:
|
||||
binding.variable_id = AllocateSharedOutputVariable(state, state.depth_variable);
|
||||
break;
|
||||
@@ -484,6 +476,8 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
|
||||
case IR::StageInputKind::InstanceIndex: return BuiltInInstanceIndex;
|
||||
case IR::StageInputKind::FragCoord: return BuiltInFragCoord;
|
||||
case IR::StageInputKind::FrontFacing: return BuiltInFrontFacing;
|
||||
case IR::StageInputKind::Layer: return BuiltInLayer;
|
||||
case IR::StageInputKind::SampleId: return BuiltInSampleId;
|
||||
case IR::StageInputKind::BaryCoordSmooth: return BuiltInBaryCoordKHR;
|
||||
case IR::StageInputKind::BaryCoordNoPerspective: return BuiltInBaryCoordNoPerspKHR;
|
||||
case IR::StageInputKind::WorkgroupId: return BuiltInWorkgroupId;
|
||||
@@ -507,6 +501,9 @@ void AddInputAnnotationsAndNames(EmitterState& state) {
|
||||
}
|
||||
for (const auto& input: state.inputs) {
|
||||
state.builder.AddName(input.variable_id, input.debug_name.c_str());
|
||||
if (input.kind == IR::StageInputKind::Layer || input.kind == IR::StageInputKind::SampleId) {
|
||||
state.builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat});
|
||||
}
|
||||
if (input.kind == IR::StageInputKind::Parameter) {
|
||||
const auto flat = PixelParameterIsFlat(state, input.location);
|
||||
if (input.per_vertex) {
|
||||
@@ -534,6 +531,9 @@ void AddInputAnnotationsAndNames(EmitterState& state) {
|
||||
}
|
||||
|
||||
void AddOutputAnnotationsAndNames(EmitterState& state) {
|
||||
if (state.stage == ShaderType::Mesh) {
|
||||
return;
|
||||
}
|
||||
if (state.per_vertex_variable != 0) {
|
||||
state.builder.AddName(PerVertexType(state), "gl_PerVertex");
|
||||
state.builder.AddName(state.per_vertex_variable, "outPerVertex");
|
||||
@@ -548,6 +548,7 @@ void AddOutputAnnotationsAndNames(EmitterState& state) {
|
||||
AddBuiltIn(state.clip_distance_variable, "gl_ClipDistance", BuiltInClipDistance);
|
||||
AddBuiltIn(state.cull_distance_variable, "gl_CullDistance", BuiltInCullDistance);
|
||||
AddBuiltIn(state.layer_variable, "gl_Layer", BuiltInLayer);
|
||||
AddBuiltIn(state.viewport_index_variable, "gl_ViewportIndex", BuiltInViewportIndex);
|
||||
if (state.depth_variable != 0) {
|
||||
state.builder.AddName(state.depth_variable, "gl_FragDepth");
|
||||
state.builder.AddAnnotation(
|
||||
@@ -586,6 +587,14 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
if (state.storage_buffer_variable != 0) {
|
||||
Decorate(state.storage_buffer_variable, "buffers", IR::DescriptorBindingKind::Buffers);
|
||||
}
|
||||
if (state.storage_buffer_u64_variable != 0) {
|
||||
Decorate(state.storage_buffer_u64_variable, "buffers_u64",
|
||||
IR::DescriptorBindingKind::Buffers);
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, state.storage_buffer_variable, DecorationAliased});
|
||||
state.builder.AddAnnotation(
|
||||
{OpDecorate, state.storage_buffer_u64_variable, DecorationAliased});
|
||||
}
|
||||
if (state.bda_pagetable_variable != 0) {
|
||||
Decorate(state.bda_pagetable_variable, "bda_pagetable",
|
||||
IR::DescriptorBindingKind::BdaPagetable);
|
||||
@@ -594,45 +603,13 @@ void AddDescriptorAnnotationsAndNames(EmitterState& state) {
|
||||
Decorate(state.fault_buffer_variable, "fault_buffer",
|
||||
IR::DescriptorBindingKind::FaultBuffer);
|
||||
}
|
||||
constexpr const char* SampledNames[] = {"sampled_1d",
|
||||
"sampled_1d_array",
|
||||
"sampled_2d",
|
||||
"sampled_2d_array",
|
||||
"sampled_3d",
|
||||
"sampled_2d_msaa",
|
||||
"sampled_2d_msaa_array",
|
||||
"sampled_uint_1d",
|
||||
"sampled_uint_1d_array",
|
||||
"sampled_uint_2d",
|
||||
"sampled_uint_2d_array",
|
||||
"sampled_uint_3d",
|
||||
"sampled_uint_2d_msaa",
|
||||
"sampled_uint_2d_msaa_array"};
|
||||
for (uint32_t i = 0; i < state.sampled_image_variables.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % SampledImageViewKindCount);
|
||||
Decorate(state.sampled_image_variables[i], SampledNames[i],
|
||||
SampledBindingKind(i >= SampledImageViewKindCount, view));
|
||||
}
|
||||
constexpr const char* StorageNames[] = {"storage_1d",
|
||||
"storage_1d_array",
|
||||
"storage_2d",
|
||||
"storage_2d_array",
|
||||
"storage_3d",
|
||||
"storage_uint_1d",
|
||||
"storage_uint_1d_array",
|
||||
"storage_uint_2d",
|
||||
"storage_uint_2d_array",
|
||||
"storage_uint_3d",
|
||||
"storage_atomic_1d",
|
||||
"storage_atomic_1d_array",
|
||||
"storage_atomic_2d",
|
||||
"storage_atomic_2d_array",
|
||||
"storage_atomic_3d"};
|
||||
for (uint32_t i = 0; i < state.storage_image_variables.size(); i++) {
|
||||
const auto view = static_cast<ImageViewKind>(i % StorageImageViewKindCount);
|
||||
const auto image_class = static_cast<StorageImageClass>(i / StorageImageViewKindCount);
|
||||
Decorate(state.storage_image_variables[i], StorageNames[i],
|
||||
StorageBindingKind(image_class, view));
|
||||
for (const auto& binding: state.program.bindings.descriptors) {
|
||||
if (IR::ImageBindingResourceClass(binding.kind) == IR::ImageResourceClass::None) {
|
||||
continue;
|
||||
}
|
||||
const auto name = "image_" + std::to_string(static_cast<uint32_t>(binding.kind));
|
||||
Decorate(state.image_variables[IR::ImageBindingIndex(binding.kind)], name.c_str(),
|
||||
binding.kind);
|
||||
}
|
||||
if (state.sampler_variable != 0) {
|
||||
Decorate(state.sampler_variable, "samplers", IR::DescriptorBindingKind::Samplers);
|
||||
@@ -651,9 +628,9 @@ void AddVsharpAnnotationsAndNames(EmitterState& state) {
|
||||
state.builder.AddName(PushConstantBlockType(state), "BufferResource");
|
||||
state.builder.AddName(state.push_constant_variable, "vsharp");
|
||||
}
|
||||
if (state.vsharp_storage_variable != 0) {
|
||||
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
|
||||
IR::DescriptorBindingKind::UserData);
|
||||
if (state.shader_data_storage_variable != 0) {
|
||||
DecorateDescriptor(state, state.shader_data_storage_variable, "shader_data",
|
||||
IR::DescriptorBindingKind::ShaderData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,9 +640,21 @@ void DefineModule(EmitterState& state) {
|
||||
state.lds_variable = state.builder.AllocateId();
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
state.scratch_variable = state.builder.AllocateId();
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.scratch_variable[half] = state.builder.AllocateId();
|
||||
}
|
||||
}
|
||||
state.main_func = state.builder.AllocateId();
|
||||
if (state.stage == ShaderType::Mesh) {
|
||||
state.mesh_guest_func = state.builder.AllocateId();
|
||||
state.builder.RequireCapability(5283u); // MeshShadingEXT
|
||||
state.builder.RequireExtension("SPV_EXT_mesh_shader");
|
||||
state.builder.AddExecutionMode({state.main_func, 5298u}); // OutputTrianglesEXT
|
||||
state.builder.AddExecutionMode(
|
||||
{state.main_func, 26u, state.input_info.vertex->mesh.max_vertices});
|
||||
state.builder.AddExecutionMode(
|
||||
{state.main_func, 5270u, state.input_info.vertex->mesh.max_primitives});
|
||||
}
|
||||
state.entry_label = state.builder.AllocateId();
|
||||
|
||||
state.builder.RequireCapability(CapabilityShader);
|
||||
@@ -675,24 +664,35 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.RequireCapability(CapabilityPhysicalStorageBufferAddresses);
|
||||
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
|
||||
}
|
||||
if (state.requirements.buffer_int64_atomics) {
|
||||
state.builder.RequireCapability(CapabilityInt64);
|
||||
state.builder.RequireCapability(CapabilityInt64Atomics);
|
||||
}
|
||||
if (state.clip_distance_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityClipDistance);
|
||||
}
|
||||
if (state.cull_distance_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityCullDistance);
|
||||
}
|
||||
if (state.layer_variable != 0) {
|
||||
state.builder.RequireCapability(CapabilityShaderViewportIndexLayerEXT);
|
||||
state.builder.RequireExtension("SPV_EXT_shader_viewport_index_layer");
|
||||
if (state.layer_variable != 0 || InputVariableForKind(state, IR::StageInputKind::Layer) != 0) {
|
||||
state.builder.RequireVersion(0x00010500u);
|
||||
state.builder.RequireCapability(CapabilityShaderLayer);
|
||||
}
|
||||
if (state.viewport_index_variable != 0) {
|
||||
state.builder.RequireVersion(0x00010500u);
|
||||
state.builder.RequireCapability(CapabilityShaderViewportIndex);
|
||||
}
|
||||
if (InputVariableForKind(state, IR::StageInputKind::SampleId) != 0) {
|
||||
state.builder.RequireCapability(CapabilitySampleRateShading);
|
||||
}
|
||||
if (state.requirements.image_gather_extended) {
|
||||
state.builder.RequireCapability(CapabilityImageGatherExtended);
|
||||
}
|
||||
if (state.requirements.subgroup_ballot || state.requirements.subgroup_shuffle ||
|
||||
state.requirements.subgroup_local_invocation_id) {
|
||||
if (state.lane_count == 2 || state.requirements.subgroup_ballot ||
|
||||
state.requirements.subgroup_shuffle || state.requirements.subgroup_local_invocation_id) {
|
||||
state.builder.RequireCapability(CapabilityGroupNonUniform);
|
||||
}
|
||||
if (state.requirements.subgroup_ballot) {
|
||||
if (state.lane_count == 2 || state.requirements.subgroup_ballot) {
|
||||
state.builder.RequireCapability(CapabilityGroupNonUniformBallot);
|
||||
}
|
||||
if (state.requirements.subgroup_shuffle) {
|
||||
@@ -717,19 +717,24 @@ void DefineModule(EmitterState& state) {
|
||||
{state.program.info.uses_dma ? AddressingModelPhysicalStorageBuffer64
|
||||
: AddressingModelLogical,
|
||||
MemoryModelGLSL450});
|
||||
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
|
||||
state.interface_variables);
|
||||
// GCN/RDNA arithmetic preserves 32-bit signed zero, infinity, and NaN. Declaring that
|
||||
// contract prevents host compilers from treating synthesized IEEE values as finite.
|
||||
state.builder.AddExecutionMode({state.main_func, ExecutionModeSignedZeroInfNanPreserve, 32u});
|
||||
if (state.stage == ShaderType::Compute) {
|
||||
if (const auto* cs = ShaderWorkgroupInput(state.stage, state.input_info)) {
|
||||
uint32_t local_x = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_y = state.requirements.compute_derivatives ? 2u : 1u;
|
||||
uint32_t local_z = 1u;
|
||||
const auto* cs = state.input_info.compute;
|
||||
local_x = cs->threads_num[0] != 0u ? cs->threads_num[0] : local_x;
|
||||
local_y = cs->threads_num[1] != 0u ? cs->threads_num[1] : local_y;
|
||||
local_z = cs->threads_num[2] != 0u ? cs->threads_num[2] : local_z;
|
||||
if (state.lane_count == 2) {
|
||||
local_x = ((local_x * local_y * local_z + 63u) / 64u) * 32u;
|
||||
local_y = local_z = 1u;
|
||||
if (state.requirements.compute_derivatives) {
|
||||
local_y = local_x / 2u;
|
||||
local_x = 2u;
|
||||
}
|
||||
}
|
||||
state.builder.AddExecutionMode(
|
||||
{state.main_func, ExecutionModeLocalSize, local_x, local_y, local_z});
|
||||
}
|
||||
@@ -752,7 +757,9 @@ void DefineModule(EmitterState& state) {
|
||||
state.builder.AddName(state.lds_variable, "lds_dwords");
|
||||
}
|
||||
if (state.requirements.function_scratch) {
|
||||
state.builder.AddName(state.scratch_variable, "scratch_dwords");
|
||||
for (uint32_t half = 0; half < state.lane_count; half++) {
|
||||
state.builder.AddName(state.scratch_variable[half], "scratch_dwords");
|
||||
}
|
||||
}
|
||||
AddInputAnnotationsAndNames(state);
|
||||
AddOutputAnnotationsAndNames(state);
|
||||
@@ -769,6 +776,8 @@ void DefineModule(EmitterState& state) {
|
||||
switch (input.kind) {
|
||||
case IR::StageInputKind::VertexIndex:
|
||||
case IR::StageInputKind::InstanceIndex:
|
||||
case IR::StageInputKind::Layer:
|
||||
case IR::StageInputKind::SampleId:
|
||||
ptr_type = TypePointer(state, StorageClassInput, TypeI32(state));
|
||||
break;
|
||||
case IR::StageInputKind::WorkgroupId:
|
||||
@@ -789,7 +798,7 @@ void DefineModule(EmitterState& state) {
|
||||
case IR::StageInputKind::Parameter:
|
||||
if (state.stage == ShaderType::Vertex) {
|
||||
const auto kind = VertexParameterScalarKind(state, input.location);
|
||||
const auto components = VertexParameterComponentCount(state, input);
|
||||
const auto components = VertexParameterComponentCount(input);
|
||||
ptr_type = VertexParameterInputPointerType(state, kind, components);
|
||||
} else if (input.per_vertex) {
|
||||
const auto array_type = state.builder.Type(
|
||||
@@ -803,6 +812,9 @@ void DefineModule(EmitterState& state) {
|
||||
}
|
||||
state.builder.DefineGlobalVariable(input.variable_id, ptr_type, StorageClassInput);
|
||||
}
|
||||
if (state.stage == ShaderType::Mesh) {
|
||||
return;
|
||||
}
|
||||
if (state.per_vertex_variable != 0) {
|
||||
state.builder.DefineGlobalVariable(
|
||||
state.per_vertex_variable, TypePointer(state, StorageClassOutput, PerVertexType(state)),
|
||||
@@ -832,6 +844,11 @@ void DefineModule(EmitterState& state) {
|
||||
state.layer_variable, TypePointer(state, StorageClassOutput, TypeU32(state)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
if (state.viewport_index_variable != 0) {
|
||||
state.builder.DefineGlobalVariable(
|
||||
state.viewport_index_variable, TypePointer(state, StorageClassOutput, TypeU32(state)),
|
||||
StorageClassOutput);
|
||||
}
|
||||
for (const auto& binding: state.outputs) {
|
||||
if (binding.kind == IR::StageOutputKind::Parameter ||
|
||||
binding.kind == IR::StageOutputKind::Mrt) {
|
||||
|
||||
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user