refactor renderer

This commit is contained in:
nmzik
2026-07-27 02:27:29 +02:00
parent a720b28b7f
commit a20cf5d298
112 changed files with 18269 additions and 18368 deletions
+93
View File
@@ -19,6 +19,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
include(utils.cmake)
include(CTest)
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
@@ -183,6 +184,9 @@ set(gpu_tiler_shader_names
prt_3d
render_target
depth
promote_d16
demote_d16
swap_bgra16
)
file(GLOB gpu_tiler_shader_includes CONFIGURE_DEPENDS "${gpu_tiler_shader_dir}/gpu_tiler_*.inc")
foreach(shader_name IN LISTS gpu_tiler_shader_names)
@@ -204,6 +208,30 @@ foreach(shader_name IN LISTS gpu_tiler_shader_names)
endforeach()
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
set(gpu_blit_shader_sources
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
)
foreach(shader_source IN LISTS gpu_blit_shader_sources)
get_filename_component(shader_name "${shader_source}" NAME_WE)
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
string(TOUPPER "${shader_name}_SPV" shader_symbol)
add_custom_command(
OUTPUT "${shader_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
-DSYMBOL=${shader_symbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${shader_source}"
VERBATIM
)
list(APPEND gpu_blit_shader_headers "${shader_header}")
endforeach()
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
list(APPEND kyty_emulator_src
emulator.h
emulator.cpp
@@ -318,6 +346,14 @@ add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
target_link_libraries(resource_mutex_tests common)
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
../tests/EventQueueLifetimeTests.cpp
kernel/eventQueue.cpp
loader/timer.cpp
)
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
add_executable(image_page_table_tests EXCLUDE_FROM_ALL
../tests/ImagePageTableTests.cpp
)
@@ -325,10 +361,67 @@ target_include_directories(image_page_table_tests PRIVATE ${inc_headers})
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
set(gpu_test_generated_dir "${PROJECT_BINARY_DIR}/gpu_test_shaders")
set(gpu_test_ms_depth_source
"${gpu_tiler_shader_dir}/gpu_test_ms_depth.comp")
set(gpu_test_ms_depth_spv
"${gpu_test_generated_dir}/gpu_test_ms_depth.spv")
set(gpu_test_ms_depth_header
"${gpu_test_generated_dir}/gpu_test_ms_depth_spv.h")
add_custom_command(
OUTPUT "${gpu_test_ms_depth_header}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_test_generated_dir}"
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.0 -Os
-o "${gpu_test_ms_depth_spv}" "${gpu_test_ms_depth_source}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${gpu_test_ms_depth_spv}
-DOUTPUT=${gpu_test_ms_depth_header} -DSYMBOL=GPU_TEST_MS_DEPTH_SPV
-P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${gpu_test_ms_depth_source}"
VERBATIM
)
target_sources(shader_recompiler_compute_tests PRIVATE
"${gpu_test_ms_depth_header}")
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
if(BUILD_TESTING)
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME command_scheduler_timeline
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
add_test(NAME stream_buffer_ring
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --stream-buffer-only)
add_test(NAME gpu_command_lane
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-command-lane-only)
add_test(NAME gpu_tiler
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
if(WIN32)
add_test(NAME texture_cache_image_overlap
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
add_test(NAME texture_cache_htile_clear
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-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
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_ranges
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
endif()
endif()
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
+4
View File
@@ -93,4 +93,8 @@ bool NggRectlistDrawEnabled() {
return g_config->ngg_rectlist_draw_enabled;
}
bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images;
}
} // namespace Config
+2
View File
@@ -36,6 +36,7 @@ struct ConfigOptions {
bool spirv_debug_printf_enabled = false;
bool renderdoc_enabled = false;
bool ngg_rectlist_draw_enabled = true;
bool readback_linear_images = false;
};
void Load(const ConfigOptions& cfg);
@@ -64,6 +65,7 @@ bool SpirvDebugPrintfEnabled();
bool RenderDocEnabled();
bool NggRectlistDrawEnabled();
bool ReadbackLinearImagesEnabled();
} // namespace Config
+119
View File
@@ -0,0 +1,119 @@
#ifndef EMULATOR_SRC_COMMON_LRUCACHE_H_
#define EMULATOR_SRC_COMMON_LRUCACHE_H_
#include <cstddef>
#include <deque>
#include <type_traits>
#include <utility>
namespace Common {
template <typename Object, typename Tick>
class LeastRecentlyUsedCache {
struct Item {
Object object {};
Tick tick {};
Item* next = nullptr;
Item* prev = nullptr;
};
public:
[[nodiscard]] size_t Insert(Object object, Tick tick) {
const auto id = Build();
auto& item = m_items[id];
item.object = std::move(object);
item.tick = tick;
Attach(item);
return id;
}
void Touch(size_t id, Tick tick) {
auto& item = m_items[id];
if (item.tick >= tick) {
return;
}
item.tick = tick;
if (&item != m_last) {
Detach(item);
Attach(item);
}
}
void Free(size_t id) {
auto& item = m_items[id];
Detach(item);
item.next = nullptr;
item.prev = nullptr;
m_free.push_back(id);
}
template <typename Function>
void ForEachItemBelow(Tick tick, Function&& function) {
constexpr bool ReturnsBool =
std::is_same_v<std::invoke_result_t<Function, Object>, bool>;
for (auto* item = m_first; item != nullptr;) {
if (item->tick > tick) {
return;
}
auto* next = item->next;
if constexpr (ReturnsBool) {
if (function(item->object)) {
return;
}
} else {
function(item->object);
}
item = next;
}
}
private:
[[nodiscard]] size_t Build() {
if (m_free.empty()) {
const auto id = m_items.size();
m_items.emplace_back();
return id;
}
const auto id = m_free.front();
m_free.pop_front();
return id;
}
void Attach(Item& item) {
if (m_first == nullptr) {
m_first = &item;
}
if (m_last == nullptr) {
m_last = &item;
return;
}
item.prev = m_last;
m_last->next = &item;
item.next = nullptr;
m_last = &item;
}
void Detach(Item& item) {
if (item.prev != nullptr) {
item.prev->next = item.next;
}
if (item.next != nullptr) {
item.next->prev = item.prev;
}
if (m_first == &item) {
m_first = item.next;
}
if (m_last == &item) {
m_last = item.prev;
}
}
std::deque<Item> m_items;
std::deque<size_t> m_free;
Item* m_first = nullptr;
Item* m_last = nullptr;
};
} // namespace Common
#endif // EMULATOR_SRC_COMMON_LRUCACHE_H_
+56
View File
@@ -0,0 +1,56 @@
#ifndef KYTY_COMMON_UNIQUEFUNCTION_H_
#define KYTY_COMMON_UNIQUEFUNCTION_H_
#include <memory>
#include <type_traits>
#include <utility>
namespace Common {
template <typename Result, typename... Args>
class UniqueFunction {
class CallableBase {
public:
virtual ~CallableBase() = default;
virtual Result Invoke(Args&&... args) = 0;
};
template <typename Function>
class Callable final: public CallableBase {
public:
explicit Callable(Function function): m_function(std::move(function)) {}
Result Invoke(Args&&... args) override {
return m_function(std::forward<Args>(args)...);
}
private:
Function m_function;
};
public:
UniqueFunction() = default;
template <typename Function>
UniqueFunction(Function&& function)
: m_callable(std::make_unique<Callable<std::decay_t<Function>>>(
std::forward<Function>(function))) {}
UniqueFunction(UniqueFunction&&) noexcept = default;
UniqueFunction& operator=(UniqueFunction&&) noexcept = default;
UniqueFunction(const UniqueFunction&) = delete;
UniqueFunction& operator=(const UniqueFunction&) = delete;
Result operator()(Args... args) const {
return m_callable->Invoke(std::forward<Args>(args)...);
}
explicit operator bool() const noexcept { return m_callable != nullptr; }
private:
std::unique_ptr<CallableBase> m_callable;
};
} // namespace Common
#endif // KYTY_COMMON_UNIQUEFUNCTION_H_
@@ -11,8 +11,6 @@
namespace Libs::Graphics {
inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
enum class Pm4ProcessResult { Complete, Blocked };
@@ -45,7 +43,7 @@ public:
int64_t flip_arg = 0;
};
CommandProcessor() = default;
explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
~CommandProcessor() = default;
KYTY_CLASS_NO_COPY(CommandProcessor);
@@ -60,16 +58,13 @@ public:
if (m_readback_active) {
EXIT("nested command-processor readback transaction\n");
}
m_readback_active = true;
m_readback_finished = false;
m_readback_active = true;
}
void FinishReadbackTransaction();
void EndReadbackTransaction() {
if (!m_readback_active) {
EXIT("command-processor readback transaction is not active\n");
}
m_readback_active = false;
m_readback_finished = false;
m_readback_active = false;
}
HW::Context& GetCtx() { return m_ctx; }
@@ -106,12 +101,10 @@ public:
void Flip(void* dst_gpu_addr, uint32_t value);
void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr,
uint32_t value);
void PrepareCpuFlip();
void PrepareCpuFlip(uint64_t request_id);
void SynchronizeGpu();
void MemoryBarrier();
void EmitGlobalBarrier();
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
void RenderTextureBarrier(uint64_t vaddr, uint64_t size);
void DepthStencilBarrier(uint64_t vaddr, uint64_t size);
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
uint32_t mode);
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
@@ -160,11 +153,12 @@ private:
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
void SuspendPm4();
CommandScheduler& GetScheduler() const { return GetRenderContext().GetCommandScheduler(); }
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
void CheckBuffer() const { GetScheduler().CheckActive(); }
GpuResourceManager& GetGpuResources() const { return GetRenderContext().GetGpuResources(); }
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
RenderContext& m_renderer;
HW::Context m_ctx;
HW::UserConfig m_ucfg;
HW::Shader m_sh_ctx;
@@ -179,8 +173,7 @@ private:
uint32_t m_de_count = 0;
uint32_t m_ce_count = 0;
bool m_ce_complete = false;
bool m_readback_active = false;
bool m_readback_finished = false;
bool m_readback_active = false;
uint32_t m_const_ram[0x3000] = {0};
@@ -6,7 +6,6 @@
#include "graphics/guest_gpu/command_processor/pm4Dispatch.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/presentation/videoOut.h"
@@ -1764,230 +1763,11 @@ static bool HwUcTrySetFakeRegisterRange(uint32_t cmd_offset, const uint32_t* buf
return true;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
KYTY_CP_OP_PARSER(CpOpAcquireMem) {
KYTY_PROFILER_FUNCTION();
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0055800 && cmd_id != 0xc0061050);
bool custom = (cmd_id == 0xc0061050);
uint32_t engine = buffer[0] >> 31u;
uint32_t stall_mode = (custom ? 1u : engine);
uint32_t cache_action = buffer[0] & 0x7fffffffu;
uint64_t size_lo = buffer[1];
uint32_t size_hi = buffer[2];
uint64_t base_lo = buffer[3];
uint32_t base_hi = buffer[4];
uint32_t poll = buffer[5];
[[maybe_unused]] uint32_t gcr_cntl = (custom ? buffer[6] : 0);
uint32_t target_mask = cache_action & 0x00007FC0u;
uint32_t extended_action = cache_action & 0x2E000000u;
uint32_t action =
((cache_action & 0x00C00000u) >> 0x12u) | ((cache_action & 0x00058000u) >> 0xfu);
if (custom && engine > 1) {
LOGF("\t warning: custom acquire_mem unsupported engine: %" PRIu32 "\n", engine);
}
// EXIT_NOT_IMPLEMENTED(stall_mode != 1);
EXIT_NOT_IMPLEMENTED(size_hi != 0);
EXIT_NOT_IMPLEMENTED(base_hi != 0);
if (poll != 10) {
LOGF("\t warning: acquire_mem unexpected poll interval: %" PRIu32 "\n", poll);
}
switch (cache_action) {
case 0x00000000: {
if (custom && gcr_cntl != 0) {
LOGF("\t custom acquire_mem GCR-only barrier, gcr_cntl = 0x%08" PRIx32
", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
gcr_cntl, base_lo << 8u, size_lo << 8u);
cp.MemoryBarrier();
if ((gcr_cntl & AcquireGcrGl2Writeback) != 0) {
cp.SynchronizeGpu();
}
}
} break;
case 0x00000040:
case 0x00003fc0:
case 0x00004000:
case 0x00007fc0: {
// target_mask set, no CB/DB action bits. Treat as an ordering barrier.
EXIT_IF(target_mask != cache_action);
EXIT_IF(extended_action != 0x00000000);
EXIT_IF(action != 0x00);
LOGF("\t temporary: acquire_mem target-mask-only barrier, target_mask = 0x%08" PRIx32
", gcr_cntl = 0x%08" PRIx32 ", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
target_mask, gcr_cntl, base_lo << 8u, size_lo << 8u);
cp.MemoryBarrier();
} break;
case 0x02000000: {
// target_mask: 0x00000000 (none)
// extended_action: 0x02000000 (FlushAndInvalidateCbCache)
// action: 0x00 (none)
EXIT_IF(target_mask != 0x00000000);
EXIT_IF(extended_action != 0x02000000);
EXIT_IF(action != 0x00);
LOGF("\t temporary: acquire_mem CB-cache-only barrier, gcr_cntl = 0x%08" PRIx32
", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
gcr_cntl, base_lo << 8u, size_lo << 8u);
cp.MemoryBarrier();
} break;
case 0x04000000: {
// target_mask: 0x00000000 (none)
// extended_action: 0x04000000 (FlushAndInvalidateDbCache)
// action: 0x00 (none)
EXIT_IF(target_mask != 0x00000000);
EXIT_IF(extended_action != 0x04000000);
EXIT_IF(action != 0x00);
LOGF("\t temporary: acquire_mem DB-cache-only barrier, gcr_cntl = 0x%08" PRIx32
", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
gcr_cntl, base_lo << 8u, size_lo << 8u);
cp.MemoryBarrier();
} break;
case 0x04004000:
case 0x04007fc0: {
// target_mask: 0x00004000 (Depth Target), 0x00007fc0 (all rt and depth)
// extended_action: 0x04000000 (FlushAndInvalidateDbCache)
// action: 0x00 (none)
EXIT_IF(target_mask != 0x00004000 && target_mask != 0x00007FC0);
EXIT_IF(extended_action != 0x04000000);
EXIT_IF(action != 0x00);
LOGF("\t temporary: acquire_mem DB target barrier, target_mask = 0x%08" PRIx32
", gcr_cntl = 0x%08" PRIx32 ", base = 0x%016" PRIx64 ", size = 0x%016" PRIx64 "\n",
target_mask, gcr_cntl, base_lo << 8u, size_lo << 8u);
if (size_lo != 0) {
cp.DepthStencilBarrier(base_lo << 8u, size_lo << 8u);
} else {
cp.MemoryBarrier();
}
} break;
case 0x02c40040:
case 0x02c43fc0:
case 0x02c47fc0: {
// target_mask: 0x00000040 (rt0), 0x00003fc0 (all rt), 0x00007fc0 (all rt and depth)
// extended_action: 0x02000000 (FlushAndInvalidateCbCache)
// action: 0x38 (WriteBackAndInvalidateL1andL2)
EXIT_IF(target_mask != 0x00000040 && target_mask != 0x00003FC0 &&
target_mask != 0x00007FC0);
EXIT_IF(extended_action != 0x02000000);
EXIT_IF(action != 0x38);
EXIT_NOT_IMPLEMENTED(size_lo == 0);
EXIT_NOT_IMPLEMENTED(base_lo == 0);
cp.RenderTextureBarrier(base_lo << 8u, size_lo << 8u);
cp.SynchronizeGpu();
} break;
case 0x02003fc0:
case 0x02007fc0: {
// target_mask: 0x00003FC0 (all rt), 0x00007fc0 (all rt and depth)
// extended_action: 0x02000000 (FlushAndInvalidateCbCache)
// action: 0x00 (none)
EXIT_IF(target_mask != 0x00003FC0 && target_mask != 0x00007fc0);
EXIT_IF(extended_action != 0x02000000);
EXIT_IF(action != 0x00);
if (size_lo == 0) {
if (base_lo != 0) {
LOGF("\t warning: acquire_mem CB-cache barrier with non-zero base");
}
cp.MemoryBarrier();
} else {
EXIT_NOT_IMPLEMENTED(base_lo == 0);
cp.RenderTextureBarrier(base_lo << 8u, size_lo << 8u);
}
} break;
case 0x00C40000: {
// target_mask: 0x00000000 (none)
// extended_action: 0x00000000 (none)
// action: 0x38 (WriteBackAndInvalidateL1andL2)
EXIT_IF(target_mask != 0x00000000);
EXIT_IF(extended_action != 0x00000000);
EXIT_IF(action != 0x38);
EXIT_NOT_IMPLEMENTED(size_lo != 1);
EXIT_NOT_IMPLEMENTED(base_lo != 0);
cp.MemoryBarrier();
cp.SynchronizeGpu();
} break;
case 0x00400000: {
// target_mask: 0x00000000 (none)
// extended_action: 0x00000000 (none)
// action: 0x10 (InvalidateL1)
EXIT_IF(target_mask != 0x00000000);
EXIT_IF(extended_action != 0x00000000);
EXIT_IF(action != 0x10);
EXIT_NOT_IMPLEMENTED(size_lo != 1);
EXIT_NOT_IMPLEMENTED(base_lo != 0);
cp.MemoryBarrier();
} break;
case 0x04c44000: {
// target_mask: 0x00004000 (Depth Target)
// extended_action: 0x04000000 (FlushAndInvalidateDbCache)
// action: 0x38 (WriteBackAndInvalidateL1andL2)
EXIT_IF(target_mask != 0x00004000);
EXIT_IF(extended_action != 0x04000000);
EXIT_IF(action != 0x38);
cp.DepthStencilBarrier(base_lo << 8u, size_lo << 8u);
cp.SynchronizeGpu();
} break;
case 0x06000040:
case 0x06000080:
case 0x06003fc0:
case 0x06007fc0: {
// target_mask: 0x00000040 (rt0), 0x00000080 (rt1), 0x00003fc0 (all rt), 0x00007fc0
// (all rt and depth) extended_action: 0x06000000 (Flush Cb & Db) action: 0x00
// (none)
if (gcr_cntl != 0 && gcr_cntl != 0x280 && gcr_cntl != 0x300) {
LOGF("\t temporary: acquire_mem CB+DB barrier with unhandled GCR control "
"0x%08" PRIx32 "\n",
gcr_cntl);
}
EXIT_IF(target_mask != 0x00000040 && target_mask != 0x00000080 &&
target_mask != 0x00003fc0 && target_mask != 0x00007fc0);
EXIT_IF(extended_action != 0x06000000);
EXIT_IF(action != 0x00);
if (size_lo != 0) {
if ((target_mask & 0x00003fc0) != 0) {
cp.RenderTextureBarrier(base_lo << 8u, size_lo << 8u);
}
if ((target_mask & 0x00004000) != 0) {
cp.DepthStencilBarrier(base_lo << 8u, size_lo << 8u);
}
} else {
cp.MemoryBarrier();
}
} break;
default:
EXIT("unknown barrier: 0x%08" PRIx32 ", 0x%08" PRIx32 ", 0x%08" PRIx32 ", 0x%08" PRIx32
"\n",
cache_action, target_mask, extended_action, action);
}
if (stall_mode == 0) {
cp.BufferWait();
}
return (custom ? 7 : 6);
return (cmd_id == 0xc0061050 ? 7 : 6);
}
KYTY_CP_OP_PARSER(CpOpDispatchDirect) {
@@ -2930,7 +2710,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
if (data_sel == 0 || interrupt_selector == 4) {
if (eop_event_type != 0x28 || gcr_cntl != 0) {
cp.MemoryBarrier();
cp.EmitGlobalBarrier();
}
if (gl2_writeback) {
@@ -2944,7 +2724,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
if (release_dst == ReleaseMemDstMemory && dst_gpu_addr == nullptr) {
if (eop_event_type != 0x28 || gcr_cntl != 0) {
cp.MemoryBarrier();
cp.EmitGlobalBarrier();
}
if (gl2_writeback) {
@@ -2957,7 +2737,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
}
if (ReleaseMemGcrNeedsBarrier(eop_event_type, gcr_cntl)) {
cp.MemoryBarrier();
cp.EmitGlobalBarrier();
}
auto cache_action = ReleaseMemCacheActionFromGcr(gcr_cntl);
+314 -174
View File
@@ -10,11 +10,9 @@
#include "graphics/guest_gpu/command_processor/pm4Dispatch.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pm4.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/sync.h"
#include "graphics/presentation/displayBuffer.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window.h"
#include "graphics/shader/shader.h"
@@ -28,6 +26,8 @@
#include <cstdio>
#include <deque>
#include <memory>
#include <semaphore>
#include <thread>
#include <vector>
namespace Libs::Graphics {
@@ -36,6 +36,7 @@ static thread_local CommandProcessor* g_current_processor = nullptr;
static thread_local Pm4Execution* g_current_execution = nullptr;
static thread_local uint32_t g_submission_pause_depth = 0;
static thread_local bool g_gpu_mutex_owned = false;
static thread_local bool g_gpu_thread = false;
class GpuMutexLock final {
public:
@@ -75,32 +76,37 @@ private:
std::vector<uint32_t> m_words;
};
class Gpu {
class GpuState {
public:
static constexpr uint32_t ComputePipeCount = 7;
static constexpr uint32_t QueuesPerComputePipe = 8;
static constexpr uint32_t ComputeQueueCount = ComputePipeCount * QueuesPerComputePipe;
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
Gpu() {
explicit GpuState(RenderContext& renderer): m_renderer(renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
m_gfx_cp = std::make_unique<CommandProcessor>();
Common::Thread thread(ThreadRun, this);
thread.Detach();
m_gfx_cp = std::make_unique<CommandProcessor>(renderer);
m_thread = std::jthread(ThreadRun, this);
}
~Gpu() { KYTY_NOT_IMPLEMENTED; }
~GpuState();
KYTY_CLASS_NO_COPY(Gpu);
KYTY_CLASS_NO_COPY(GpuState);
void Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done);
void SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done);
void SubmitFlipPreparation();
void SubmitFlipPreparation(uint64_t request_id);
void Done();
void PauseSubmissions();
void ResumeSubmissions();
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command);
int GetFrameNum();
[[nodiscard]] static bool IsGpuThread() noexcept { return g_gpu_thread; }
private:
enum class SubmissionType { Graphics, Compute, FlipPreparation };
@@ -118,6 +124,7 @@ private:
bool command_complete = false;
bool constant_complete = false;
bool blocked = false;
uint64_t flip_request_id = 0;
};
void WaitLocked();
@@ -127,42 +134,100 @@ private:
static void ThreadRun(void* data);
CommandProcessor& GetProcessor(uint32_t queue_id);
RenderContext& m_renderer;
Common::Mutex m_submission_mutex;
Common::Mutex m_queue_mutex;
std::mutex m_shutdown_mutex;
Common::CondVar m_work_available;
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
bool m_graphics_done = true;
std::deque<Common::UniqueFunction<void>> m_commands;
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
bool m_graphics_done = true;
bool m_accepting = true;
bool m_stopping = false;
bool m_shutdown_complete = false;
std::unique_ptr<CommandProcessor> m_gfx_cp;
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
uint64_t m_submit_id = 0;
std::atomic_int m_done_num = 0;
std::jthread m_thread;
};
static Gpu* g_gpu = nullptr;
static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
}
void GraphicsRunInit() {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
EXIT_IF(g_gpu != nullptr);
GraphicsInitJmpTables();
g_gpu = new Gpu;
GpuState::~GpuState() {
Shutdown();
}
void Gpu::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
void GpuState::Shutdown() {
std::lock_guard shutdown_lock(m_shutdown_mutex);
if (m_shutdown_complete) {
return;
}
{
Common::LockGuard lock(m_queue_mutex);
m_accepting = false;
m_stopping = true;
m_work_available.SignalAll();
}
if (m_thread.joinable()) {
m_thread.join();
}
m_shutdown_complete = true;
}
bool GpuState::IsStopping() {
Common::LockGuard lock(m_queue_mutex);
return m_stopping;
}
void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
return;
}
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(!m_accepting);
m_commands.push_back(std::move(command));
m_work_available.Signal();
}
void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
return;
}
std::binary_semaphore done {0};
SendCommand([operation = std::move(command), &done]() mutable {
operation();
done.release();
});
done.acquire();
}
void GpuState::SendCommandSyncWithProcessor(
Common::UniqueFunction<void, CommandProcessor&>&& command) {
EXIT_IF(!command);
SendCommandSync([this, operation = std::move(command)]() mutable {
EXIT_IF(g_current_processor != nullptr);
g_current_processor = m_gfx_cp.get();
operation(*m_gfx_cp);
g_current_processor = nullptr;
});
}
void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::Graphics;
@@ -175,8 +240,8 @@ void Gpu::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_
Enqueue(std::move(submission));
}
void Gpu::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done) {
void GpuState::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done) {
GpuMutexLock lock(m_submission_mutex);
constexpr uint32_t compute_queue_base = 0x20u;
@@ -192,60 +257,52 @@ void Gpu::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
Enqueue(std::move(submission));
}
void Gpu::SubmitFlipPreparation() {
void GpuState::SubmitFlipPreparation(uint64_t request_id) {
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::FlipPreparation;
submission.queue_id = 0;
submission.reset_processor = m_graphics_done;
submission.flip_request_id = request_id;
m_graphics_done = false;
Enqueue(std::move(submission));
}
void Gpu::Done() {
void GpuState::Done() {
GpuMutexLock lock(m_submission_mutex);
WaitLocked();
if (IsGpuThread()) {
m_gfx_cp->BufferWait();
} else {
WaitLocked();
}
m_graphics_done = true;
m_done_num++;
}
int Gpu::GetFrameNum() {
int GpuState::GetFrameNum() {
return m_done_num;
}
void Gpu::WaitLocked() {
void GpuState::WaitLocked() {
WaitForIdle();
m_gfx_cp->BufferWait();
SendCommandSync([this] { m_gfx_cp->BufferWait(); });
}
CommandProcessor& Gpu::GetProcessor(uint32_t queue_id) {
CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
EXIT_IF(queue_id >= QueueCount);
if (queue_id == 0) {
return *m_gfx_cp;
}
auto& processor = m_compute_cp[queue_id - 1];
if (processor == nullptr) {
processor = std::make_unique<CommandProcessor>();
processor = std::make_unique<CommandProcessor>(m_renderer);
}
return *processor;
}
void CommandProcessor::FinishReadbackTransaction() {
if (GraphicsRunCurrentCommandProcessor() != this || !m_readback_active) {
EXIT("GPU readback finish requires a command-processor thread\n");
}
if (m_readback_finished) {
return;
}
GetScheduler().FinishCurrent();
m_readback_finished = true;
}
void CommandProcessor::Reset() {
BufferWait();
Sync::DeleteBuffers();
m_sh_ctx.Reset();
m_ucfg.Reset();
m_ctx.Reset();
@@ -396,56 +453,83 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
EXIT_NOT_IMPLEMENTED(wait_for_previous > 1);
EXIT_NOT_IMPLEMENTED(write_confirm > 1);
EXIT_NOT_IMPLEMENTED(block_engine > 1);
const bool dst_memory = dst_sel == 0 || dst_sel == 3;
const bool src_memory = src_sel == 0 || src_sel == 3;
if (!dst_memory) {
if (static_cast<uint32_t>(dst_address_or_offset) == 0x3022cu) {
return;
}
auto decode_gds = [](uint8_t selector, bool& is_gds) {
switch (selector) {
case 0:
case 3: is_gds = false; return true;
case 1: is_gds = true; return true;
default: return false;
}
};
bool dst_gds = false;
if (!decode_gds(dst_sel, dst_gds)) {
EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel);
}
auto& buffer_cache = GetGpuResources().GetBufferCache();
if (src_sel == 2) {
GetGpuResources().FillBuffer(
CurrentBuffer(), dst_address_or_offset, num_bytes,
static_cast<uint32_t>(src_address_or_offset_or_immediate & 0xffffffffu));
buffer_cache.FillBuffer(
dst_address_or_offset, num_bytes,
static_cast<uint32_t>(src_address_or_offset_or_immediate & 0xffffffffu), dst_gds);
return;
}
if (src_memory) {
GetGpuResources().CopyBuffer(CurrentBuffer(), dst_address_or_offset,
src_address_or_offset_or_immediate, num_bytes);
return;
bool src_gds = false;
if (!decode_gds(src_sel, src_gds)) {
EXIT("unsupported dmaData source selector 0x%02" PRIx8 "\n", src_sel);
}
EXIT("unsupported dmaData source selector 0x%02" PRIx8 "\n", src_sel);
if (src_gds && dst_gds) {
EXIT("unsupported dmaData GDS-to-GDS copy\n");
}
buffer_cache.CopyBuffer(dst_address_or_offset, src_address_or_offset_or_immediate, num_bytes,
dst_gds, src_gds);
}
void Gpu::Enqueue(Submission submission) {
void GpuState::Enqueue(Submission submission) {
EXIT_IF(submission.queue_id >= QueueCount);
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(!m_accepting);
m_queues[submission.queue_id].push_back(std::move(submission));
m_submission_count++;
m_work_available.Signal();
}
void Gpu::WaitForIdle() {
void GpuState::WaitForIdle() {
Common::LockGuard lock(m_queue_mutex);
while (m_processing || m_submission_count != 0) {
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
m_idle.Wait(&m_queue_mutex);
}
}
void Gpu::ThreadRun(void* data) {
auto* gpu = static_cast<Gpu*>(data);
void GpuState::ThreadRun(void* data) {
auto* gpu = static_cast<GpuState*>(data);
EXIT_IF(gpu == nullptr);
KYTY_PROFILER_THREAD("Thread_Gpu");
g_gpu_thread = true;
for (;;) {
Submission submission;
Submission submission;
Common::UniqueFunction<void> command;
bool has_submission = false;
bool should_stop = false;
{
Common::LockGuard lock(gpu->m_queue_mutex);
int selected_queue = -1;
while (selected_queue < 0) {
while (gpu->m_submission_count == 0) {
gpu->m_processing = false;
gpu->m_idle.Signal();
gpu->m_work_available.Wait(&gpu->m_queue_mutex);
}
while (gpu->m_commands.empty() && gpu->m_submission_count == 0 && !gpu->m_stopping) {
gpu->m_processing = false;
gpu->m_idle.Signal();
gpu->m_work_available.Wait(&gpu->m_queue_mutex);
}
if (gpu->m_stopping && gpu->m_commands.empty() && gpu->m_submission_count == 0) {
gpu->m_processing = false;
gpu->m_idle.SignalAll();
should_stop = true;
} else if (!gpu->m_commands.empty()) {
command = std::move(gpu->m_commands.front());
gpu->m_commands.pop_front();
gpu->m_processing = true;
} else {
int selected_queue = -1;
for (uint32_t offset = 0; offset < QueueCount; offset++) {
const auto id = (gpu->m_next_queue + offset) % QueueCount;
if (!gpu->m_queues[id].empty() && !gpu->m_queues[id].front().blocked) {
@@ -461,16 +545,36 @@ void Gpu::ThreadRun(void* data) {
queue.front().blocked = false;
}
}
continue;
}
auto& queue = gpu->m_queues[static_cast<uint32_t>(selected_queue)];
submission = std::move(queue.front());
queue.pop_front();
gpu->m_submission_count--;
gpu->m_next_queue = (static_cast<uint32_t>(selected_queue) + 1) % QueueCount;
gpu->m_processing = true;
has_submission = true;
}
auto& queue = gpu->m_queues[static_cast<uint32_t>(selected_queue)];
submission = std::move(queue.front());
queue.pop_front();
gpu->m_submission_count--;
gpu->m_next_queue = (static_cast<uint32_t>(selected_queue) + 1) % QueueCount;
gpu->m_processing = true;
}
if (should_stop) {
gpu->m_gfx_cp->BufferWait();
g_gpu_thread = false;
return;
}
if (command) {
EXIT_IF(g_current_processor != nullptr);
command();
Common::LockGuard lock(gpu->m_queue_mutex);
gpu->m_processing = false;
if (gpu->m_commands.empty() && gpu->m_submission_count == 0) {
gpu->m_idle.SignalAll();
}
continue;
}
EXIT_IF(!has_submission);
const bool complete = gpu->Process(submission);
Common::LockGuard lock(gpu->m_queue_mutex);
@@ -486,13 +590,13 @@ void Gpu::ThreadRun(void* data) {
}
}
gpu->m_processing = false;
if (gpu->m_submission_count == 0) {
gpu->m_idle.Signal();
if (gpu->m_commands.empty() && gpu->m_submission_count == 0) {
gpu->m_idle.SignalAll();
}
}
}
bool Gpu::Process(Submission& submission) {
bool GpuState::Process(Submission& submission) {
auto& cp = GetProcessor(submission.queue_id);
const bool first_slice = !submission.started;
@@ -537,10 +641,15 @@ bool Gpu::Process(Submission& submission) {
}
}
if (progressed) {
if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
}
cp.BufferFlush();
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
}
if (complete && submission.trigger_agc_interrupt_on_done) {
Sync::TriggerEopEvent(0);
m_renderer.TriggerEopEvent(0);
}
break;
}
@@ -561,30 +670,40 @@ bool Gpu::Process(Submission& submission) {
complete = cp.Process(submission.command_execution, buffer, num_dw) ==
Pm4ProcessResult::Complete;
if (submission.command_execution.MadeProgress()) {
if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
}
cp.BufferFlush();
} else if (complete) {
m_renderer.GetGpuResources().RunGarbageCollector();
}
if (complete && submission.trigger_agc_interrupt_on_done) {
Sync::TriggerAgcUserInterrupt();
}
break;
}
case SubmissionType::FlipPreparation: cp.PrepareCpuFlip(); break;
case SubmissionType::FlipPreparation:
m_renderer.GetGpuResources().RunGarbageCollector();
cp.PrepareCpuFlip(submission.flip_request_id);
break;
}
return complete;
}
void Gpu::PauseSubmissions() {
void GpuState::PauseSubmissions() {
if (g_gpu_mutex_owned) {
EXIT("GPU submissions are already paused by this thread\n");
}
g_gpu_mutex_owned = true;
m_submission_mutex.Lock();
WaitLocked();
LabelDrain();
if (!IsGpuThread()) {
WaitLocked();
}
m_renderer.GetCommandScheduler().DrainPriorityOperations();
}
void Gpu::ResumeSubmissions() {
void GpuState::ResumeSubmissions() {
if (!g_gpu_mutex_owned) {
EXIT("GPU submissions resumed without an active pause\n");
}
@@ -823,9 +942,9 @@ void CommandProcessor::DrawIndex(uint32_t index_count, const void* index_addr, u
"\n",
vertex_offset_add, first_instance);
}
RenderDrawIndex(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(), m_index_type_and_size, index_count, index_addr, flags, type,
instance_count, render_target_slice_offset, vertex_offset_add, first_instance);
}
void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count,
@@ -843,8 +962,9 @@ 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);
RenderDrawIndex(m_submit_id, CurrentBuffer(), m_index_type_and_size, index_count, index_addr,
flags, 1, m_num_instances);
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(),
m_index_type_and_size, index_count, index_addr,
flags, 1, m_num_instances);
}
void CommandProcessor::DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed) {
@@ -1036,7 +1156,7 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
{
CheckBuffer();
frame_num = GraphicsRunGetFrameNum();
frame_num = m_renderer.GetGpu().GetFrameNum();
if (GraphicsRunDebugDumpEnabled()) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 1024) {
@@ -1070,8 +1190,8 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
}
}
RenderDispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y,
thread_group_z, mode);
m_renderer.GetRenderExecutor().DispatchDirect(
m_submit_id, CurrentBuffer(), thread_group_x, thread_group_y, thread_group_z, mode);
}
constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
@@ -1117,15 +1237,16 @@ void CommandProcessor::DrawIndexAuto(uint32_t index_count, uint32_t flags,
uint32_t first_vertex, uint32_t first_instance) {
CheckBuffer();
RenderDrawIndexAuto(m_submit_id, CurrentBuffer(), index_count, flags,
render_target_slice_offset, instance_count, first_vertex, first_instance);
m_renderer.GetRenderExecutor().DrawAuto(
m_submit_id, CurrentBuffer(), index_count, flags, render_target_slice_offset,
instance_count, first_vertex, first_instance);
}
void CommandProcessor::WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index) {
BufferFlush();
VideoOut::VideoOutWaitFlipDone(static_cast<int>(video_out_handle),
static_cast<int>(display_buffer_index));
m_renderer.GetVideoOut().WaitFlipDone(static_cast<int>(video_out_handle),
static_cast<int>(display_buffer_index));
}
template <typename T>
@@ -1196,7 +1317,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
SynchronizeGpu();
Sync::ReadGds(dst, value & 0xffffu, value >> 16u);
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst,
value & 0xffffu, value >> 16u);
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
value >> 16u);
return;
@@ -1355,10 +1477,23 @@ void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_
interrupt_context_id);
}
void CommandProcessor::MemoryBarrier() {
void CommandProcessor::EmitGlobalBarrier() {
CheckBuffer();
GraphicsRenderMemoryBarrier(CurrentBuffer());
Common::LockGuard lock(m_renderer.GetMutex());
vk::MemoryBarrier2 barrier {};
barrier.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands;
barrier.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite;
barrier.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands;
barrier.dstAccessMask =
vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite;
vk::DependencyInfo dependency {};
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
GetScheduler().EndRendering();
CurrentBuffer().Handle().pipelineBarrier2(dependency);
}
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
@@ -1367,18 +1502,6 @@ void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id)
Sync::TriggerEopEventAtEndOfPipe(CurrentBuffer(), interrupt_context_id);
}
void CommandProcessor::RenderTextureBarrier(uint64_t vaddr, uint64_t size) {
CheckBuffer();
GraphicsRenderTextureBarrier(CurrentBuffer(), vaddr, size);
}
void CommandProcessor::DepthStencilBarrier(uint64_t vaddr, uint64_t size) {
CheckBuffer();
GraphicsRenderDepthStencilBarrier(CurrentBuffer(), vaddr, size);
}
void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
if (GraphicsRunDebugDumpEnabled()) {
LOGF("CommandProcessor::TriggerEvent()\n"
@@ -1392,7 +1515,7 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
// CsPartialFlush, GsPartialFlush, PsPartialFlush.
case 0x00000007:
case 0x0000000f:
case 0x00000010: MemoryBarrier(); break;
case 0x00000010: EmitGlobalBarrier(); break;
// CbDbDataWritebackInvalidate, CbDataWritebackInvalidate.
case 0x00000016:
case 0x00000031:
@@ -1400,7 +1523,7 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type,
event_index);
}
MemoryBarrier();
EmitGlobalBarrier();
SynchronizeGpu();
break;
// DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate.
@@ -1411,7 +1534,7 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
EXIT("unknown event type: 0x%08" PRIx32 ", 0x%08" PRIx32 "\n", event_type,
event_index);
}
MemoryBarrier();
EmitGlobalBarrier();
break;
case 0x0000000d:
case 0x0000000e:
@@ -1441,8 +1564,8 @@ void CommandProcessor::Flip() {
}
auto& command = CurrentBuffer();
auto request = Sync::PrepareDisplayBufferFlip(command, m_flip.handle, m_flip.index,
m_flip.flip_mode, m_flip.flip_arg);
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
m_flip.flip_arg);
Sync::WriteAtEndOfPipeOnlyFlip(m_submit_id, command, m_flip.handle, m_flip.index,
m_flip.flip_mode, m_flip.flip_arg, request);
GetScheduler().Flush();
@@ -1460,8 +1583,8 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
std::memcpy(dst_gpu_addr, &value, sizeof(value));
auto& command = CurrentBuffer();
auto request = Sync::PrepareDisplayBufferFlip(command, m_flip.handle, m_flip.index,
m_flip.flip_mode, m_flip.flip_arg);
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
m_flip.flip_arg);
Sync::WriteAtEndOfPipeWithFlip32(m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr),
value, m_flip.handle, m_flip.index, m_flip.flip_mode,
m_flip.flip_arg, request);
@@ -1486,15 +1609,15 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
}
std::memcpy(dst_gpu_addr, &value, sizeof(value));
auto& command = CurrentBuffer();
auto request = Sync::PrepareDisplayBufferFlip(command, m_flip.handle, m_flip.index,
m_flip.flip_mode, m_flip.flip_arg);
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
m_flip.flip_arg);
Sync::WriteAtEndOfPipeWithInterruptWriteBackFlip32(
m_submit_id, command, static_cast<uint32_t*>(dst_gpu_addr), value, m_flip.handle,
m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request);
GetScheduler().Flush();
}
void CommandProcessor::PrepareCpuFlip() {
void CommandProcessor::PrepareCpuFlip(uint64_t request_id) {
CheckBuffer();
if (g_current_processor != nullptr) {
EXIT("invalid graphics-thread CPU flip preparation\n");
@@ -1505,90 +1628,107 @@ void CommandProcessor::PrepareCpuFlip() {
};
ProcessorScope processor_scope(*this);
auto prepared_id = Presentation::DisplayBufferPrepareNextFlipOnGpu(CurrentBuffer());
m_renderer.GetVideoOut().PrepareFlip(request_id, CurrentBuffer());
GetScheduler().Flush();
Presentation::DisplayBufferCompleteFlipFromGpu(prepared_id);
m_renderer.GetVideoOut().CompleteFlip(request_id);
}
void CommandProcessor::SynchronizeGpu() {
GetScheduler().FinishCurrent();
}
void GraphicsRunSubmit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
EXIT_IF(cmd_draw_buffer == nullptr);
EXIT_IF(num_draw_dw == 0);
EXIT_IF(g_gpu == nullptr);
g_gpu->Submit(cmd_draw_buffer, num_draw_dw, cmd_const_buffer, num_const_dw,
trigger_agc_interrupt_on_done);
Gpu::Gpu(RenderContext& renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
GraphicsInitJmpTables();
m_state = std::make_unique<GpuState>(renderer);
}
void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done) {
EXIT_IF(cmd_buffer == nullptr);
EXIT_IF(num_dw == 0);
EXIT_IF(g_gpu == nullptr);
Gpu::~Gpu() = default;
g_gpu->SubmitCompute(queue, cmd_buffer, num_dw, trigger_agc_interrupt_on_done);
void Gpu::Shutdown() {
m_state->Shutdown();
}
void GraphicsRunSubmitFlipPreparation() {
EXIT_IF(g_gpu == nullptr);
g_gpu->SubmitFlipPreparation();
bool Gpu::IsStopping() {
return m_state->IsStopping();
}
GraphicsRunSubmissionLock::GraphicsRunSubmissionLock() {
if (g_gpu == nullptr || g_current_processor != nullptr ||
g_submission_pause_depth == UINT32_MAX) {
void Gpu::SendCommand(Common::UniqueFunction<void>&& command) {
m_state->SendCommand(std::move(command));
}
void Gpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
m_state->SendCommandSync(std::move(command));
}
void Gpu::SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command) {
m_state->SendCommandSyncWithProcessor(std::move(command));
}
void Gpu::Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done) {
EXIT_IF(draw_commands == nullptr || draw_size_dw == 0);
m_state->Submit(draw_commands, draw_size_dw, constant_commands, constant_size_dw,
trigger_agc_interrupt_on_done);
}
void Gpu::SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done) {
EXIT_IF(commands == nullptr || size_dw == 0);
m_state->SubmitCompute(queue, commands, size_dw, trigger_agc_interrupt_on_done);
}
void Gpu::SubmitFlipPreparation(uint64_t request_id) {
m_state->SubmitFlipPreparation(request_id);
}
void Gpu::Done() {
m_state->Done();
}
int Gpu::GetFrameNum() const {
return m_state->GetFrameNum();
}
void Gpu::PauseSubmissions() {
m_state->PauseSubmissions();
}
void Gpu::ResumeSubmissions() {
m_state->ResumeSubmissions();
}
Gpu::SubmissionLock::SubmissionLock(Gpu& gpu): m_gpu(gpu) {
if (g_current_processor != nullptr || g_submission_pause_depth == UINT32_MAX) {
EXIT("cannot acquire GPU submission lock in the current state\n");
}
if (g_submission_pause_depth++ == 0) {
g_gpu->PauseSubmissions();
m_gpu.PauseSubmissions();
}
}
GraphicsRunSubmissionLock::~GraphicsRunSubmissionLock() {
if (g_gpu == nullptr || g_submission_pause_depth == 0) {
Gpu::SubmissionLock::~SubmissionLock() {
if (g_submission_pause_depth == 0) {
EXIT("GPU submission lock released without ownership\n");
}
if (--g_submission_pause_depth == 0) {
g_gpu->ResumeSubmissions();
m_gpu.ResumeSubmissions();
}
}
void GraphicsRunDone() {
EXIT_IF(g_gpu == nullptr);
g_gpu->Done();
}
int GraphicsRunGetFrameNum() {
EXIT_IF(g_gpu == nullptr);
return g_gpu->GetFrameNum();
}
bool GraphicsRunIsCommandProcessorThread() noexcept {
bool Gpu::IsCommandProcessorThread() noexcept {
return g_current_processor != nullptr;
}
CommandProcessor* GraphicsRunCurrentCommandProcessor() noexcept {
CommandProcessor* Gpu::CurrentCommandProcessor() noexcept {
return g_current_processor;
}
void GraphicsRunFinishScheduler() {
if (g_current_processor == nullptr) {
EXIT("GPU readback finish requires a command-processor thread\n");
}
g_current_processor->FinishReadbackTransaction();
}
bool GraphicsRunSubmissionLockHeld() noexcept {
bool Gpu::SubmissionLockHeld() noexcept {
return g_submission_pause_depth != 0;
}
bool GraphicsRunGpuLockHeld() noexcept {
bool Gpu::MutexHeld() noexcept {
return g_gpu_mutex_owned;
}
+46 -19
View File
@@ -3,32 +3,59 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/uniqueFunction.h"
#include <memory>
namespace Libs::Graphics {
class CommandProcessor;
class GpuState;
class RenderContext;
class GraphicsRunSubmissionLock final {
class Gpu final {
public:
GraphicsRunSubmissionLock();
~GraphicsRunSubmissionLock();
KYTY_CLASS_NO_COPY(GraphicsRunSubmissionLock);
explicit Gpu(RenderContext& renderer);
~Gpu();
KYTY_CLASS_NO_COPY(Gpu);
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
void SendCommandSyncWithProcessor(Common::UniqueFunction<void, CommandProcessor&>&& command);
void Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done = false);
void SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done = false);
void SubmitFlipPreparation(uint64_t request_id);
void Done();
[[nodiscard]] int GetFrameNum() const;
[[nodiscard]] static bool IsCommandProcessorThread() noexcept;
[[nodiscard]] static CommandProcessor* CurrentCommandProcessor() noexcept;
[[nodiscard]] static bool SubmissionLockHeld() noexcept;
[[nodiscard]] static bool MutexHeld() noexcept;
class SubmissionLock final {
public:
explicit SubmissionLock(Gpu& gpu);
~SubmissionLock();
KYTY_CLASS_NO_COPY(SubmissionLock);
private:
Gpu& m_gpu;
};
private:
friend class SubmissionLock;
void PauseSubmissions();
void ResumeSubmissions();
std::unique_ptr<GpuState> m_state;
};
void GraphicsRunInit();
void GraphicsRunSubmit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done = false);
void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done = false);
void GraphicsRunSubmitFlipPreparation();
void GraphicsRunDone();
int GraphicsRunGetFrameNum();
[[nodiscard]] bool GraphicsRunIsCommandProcessorThread() noexcept;
[[nodiscard]] CommandProcessor* GraphicsRunCurrentCommandProcessor() noexcept;
void GraphicsRunFinishScheduler();
[[nodiscard]] bool GraphicsRunSubmissionLockHeld() noexcept;
[[nodiscard]] bool GraphicsRunGpuLockHeld() noexcept;
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */
-537
View File
@@ -1,537 +0,0 @@
#include "graphics/host_gpu/gpuTiler.h"
#include "common/assert.h"
#include "common/threads.h"
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_prt_3d_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_prt_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_render_target_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard256_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard4_3d_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard4_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
#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 <algorithm>
#include <array>
#include <bit>
#include <cstring>
#include <memory>
#include <vector>
namespace Libs::Graphics {
namespace {
constexpr uint32_t FAMILY_COUNT = static_cast<uint32_t>(TileBlockFamily::Count);
constexpr uint32_t BYTES_PER_ELEMENT_COUNT = 5;
constexpr uint32_t DIRECTION_COUNT = 2;
constexpr uint32_t PIPELINE_COUNT = FAMILY_COUNT * BYTES_PER_ELEMENT_COUNT * DIRECTION_COUNT;
static_assert(FAMILY_COUNT == 9);
struct Push {
uint32_t src_base;
uint32_t dst_base;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t surface_z;
uint32_t pitch_bytes;
uint32_t slice_bytes;
uint32_t blocks_per_row;
uint32_t blocks_per_slice;
uint32_t tail_x;
uint32_t tail_y;
uint32_t tail;
};
static_assert(sizeof(Push) == 52);
struct Shader {
const uint32_t* code;
size_t words;
};
constexpr std::array<Shader, FAMILY_COUNT> SHADERS {{
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
}};
struct Dispatch {
Push push {};
uint32_t pipeline_slot = 0;
};
struct Resources {
vk::DescriptorSetLayout descriptor_layout = nullptr;
vk::PipelineLayout pipeline_layout = nullptr;
vk::DescriptorPool descriptor_pool = nullptr;
vk::DescriptorSet descriptor_set = nullptr;
std::array<vk::Pipeline, PIPELINE_COUNT> pipelines {};
VulkanBuffer staging;
VulkanBuffer linear;
void* mapped = nullptr;
};
bool CheckedAdd(uint64_t a, uint64_t b, uint64_t& result) {
return b <= UINT64_MAX - a && (result = a + b, true);
}
bool CheckedMultiply(uint64_t a, uint64_t b, uint64_t& result) {
return (a == 0 || b <= UINT64_MAX / a) && (result = a * b, true);
}
bool CheckedAddProduct(uint64_t& value, uint64_t count, uint64_t stride) {
uint64_t bytes = 0;
return CheckedMultiply(count, stride, bytes) && CheckedAdd(value, bytes, value);
}
bool IsRangeValid(uint64_t offset, uint64_t size, uint64_t capacity) {
return size != 0 && offset <= capacity && size <= capacity - offset;
}
uint64_t AlignToDword(uint64_t value) {
return (value + 3u) & ~uint64_t {3};
}
uint32_t GetPipelineSlot(bool to_tiled, TileBlockFamily family, uint32_t bytes_per_element) {
const uint32_t direction_index = to_tiled ? 1u : 0u;
const uint32_t family_index = static_cast<uint32_t>(family);
const uint32_t element_size_index = std::countr_zero(bytes_per_element);
return (direction_index * FAMILY_COUNT + family_index) * BYTES_PER_ELEMENT_COUNT +
element_size_index;
}
void Barrier(vk::CommandBuffer command, vk::Buffer buffer, vk::AccessFlags src_access,
vk::AccessFlags dst_access, vk::PipelineStageFlags src_stage,
vk::PipelineStageFlags dst_stage) {
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = src_access;
barrier.dstAccessMask = dst_access;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer;
barrier.size = VK_WHOLE_SIZE;
command.pipelineBarrier(src_stage, dst_stage, {}, 0, nullptr, 1, &barrier, 0, nullptr);
}
class TileCompute final {
public:
explicit TileCompute(GraphicContext& graphics): graphics(graphics) {}
void Run(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
const GpuTileRecord& record);
void Release();
private:
void Prepare(bool to_tiled, uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos, std::vector<Dispatch>& dispatches) const;
void Init();
void CreatePipelines(std::span<const Dispatch> dispatches);
void CreatePipeline(uint32_t pipeline_slot);
void Resize(uint64_t staging_size, uint64_t linear_size);
void CreateBuffer(uint64_t size, bool mapped, VulkanBuffer& buffer, void** data) const;
void Execute(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const Dispatch> dispatches,
const GpuTileRecord& record);
void Destroy(Resources& target) const;
GraphicContext& graphics;
Resources resources;
};
Common::Mutex g_tiler_mutex;
std::unique_ptr<TileCompute> g_tiler;
void TileCompute::Prepare(bool to_tiled, uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos,
std::vector<Dispatch>& dispatches) const {
EXIT_IF(infos.empty() || tiled_capacity == 0 || linear_capacity == 0);
const auto& limits = graphics.GetPhysicalDeviceProperties().limits;
EXIT_NOT_IMPLEMENTED(tiled_capacity > UINT32_MAX || linear_capacity > UINT32_MAX ||
AlignToDword(tiled_capacity) > limits.maxStorageBufferRange ||
AlignToDword(linear_capacity) > limits.maxStorageBufferRange);
dispatches.clear();
dispatches.reserve(infos.size());
for (const auto& info: infos) {
TileBlockLayout block {};
const uint32_t tiled_width = info.tiled_width != 0 ? info.tiled_width : info.pitch;
const uint32_t tiled_height = info.tiled_height != 0 ? info.tiled_height : info.height;
const uint64_t groups_x = (static_cast<uint64_t>(info.width) + 7u) / 8u;
const uint64_t groups_y = (static_cast<uint64_t>(info.height) + 7u) / 8u;
EXIT_NOT_IMPLEMENTED(
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
groups_x > limits.maxComputeWorkGroupCount[0] ||
groups_y > limits.maxComputeWorkGroupCount[1] ||
info.depth > limits.maxComputeWorkGroupCount[2] ||
(!info.tail && (tiled_width < info.width || tiled_height < info.height)) ||
!IsRangeValid(info.linear_offset, info.linear_size, linear_capacity) ||
!IsRangeValid(info.tiled_offset, info.tiled_size, tiled_capacity) ||
(block.block_depth == 1 && info.depth != 1));
uint64_t pitch_bytes = 0;
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
pitch_bytes > UINT32_MAX);
uint64_t slice_bytes = info.linear_slice_stride;
EXIT_NOT_IMPLEMENTED(slice_bytes == 0 &&
!CheckedMultiply(pitch_bytes, info.height, slice_bytes));
uint64_t linear_used = 0, minimum_slice = 0;
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(pitch_bytes, info.height, minimum_slice) ||
(info.depth > 1 && slice_bytes < minimum_slice) ||
!CheckedAddProduct(linear_used, info.depth - 1u, slice_bytes) ||
!CheckedAddProduct(linear_used, info.height - 1u, pitch_bytes) ||
!CheckedAddProduct(linear_used, info.width, info.bytes_per_element) ||
linear_used > info.linear_size || slice_bytes > UINT32_MAX);
const uint64_t columns =
(static_cast<uint64_t>(tiled_width) + block.block_width - 1u) / block.block_width;
const uint64_t rows =
(static_cast<uint64_t>(tiled_height) + block.block_height - 1u) / block.block_height;
uint64_t blocks_per_slice = 0;
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(columns, rows, blocks_per_slice) ||
columns > UINT32_MAX || blocks_per_slice > UINT32_MAX ||
rows * block.block_height > UINT32_MAX);
if (info.tail) {
const bool supported = info.family != TileBlockFamily::Standard256B;
EXIT_NOT_IMPLEMENTED(
!supported || info.depth > block.block_depth || info.tail_x >= block.block_width ||
info.width > block.block_width - info.tail_x || info.tail_y >= block.block_height ||
info.height > block.block_height - info.tail_y ||
info.tiled_size < block.block_size);
} else {
const uint64_t slices =
(static_cast<uint64_t>(info.depth) + block.block_depth - 1u) / block.block_depth;
uint64_t tiled_used = 0;
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(blocks_per_slice, slices, tiled_used) ||
!CheckedMultiply(tiled_used, block.block_size, tiled_used) ||
tiled_used > info.tiled_size);
}
const uint32_t alignment = std::min(info.bytes_per_element, 4u);
EXIT_NOT_IMPLEMENTED(((info.linear_offset | info.tiled_offset | pitch_bytes | slice_bytes) &
(alignment - 1u)) != 0);
Dispatch dispatch {};
dispatch.pipeline_slot = GetPipelineSlot(to_tiled, info.family, info.bytes_per_element);
dispatch.push.src_base =
static_cast<uint32_t>(to_tiled ? info.linear_offset : info.tiled_offset);
dispatch.push.dst_base =
static_cast<uint32_t>(to_tiled ? info.tiled_offset : info.linear_offset);
dispatch.push.width = info.width;
dispatch.push.height = info.height;
dispatch.push.depth = info.depth;
dispatch.push.surface_z = info.surface_z;
dispatch.push.pitch_bytes = static_cast<uint32_t>(pitch_bytes);
dispatch.push.slice_bytes = static_cast<uint32_t>(slice_bytes);
dispatch.push.blocks_per_row = static_cast<uint32_t>(columns);
dispatch.push.blocks_per_slice = static_cast<uint32_t>(blocks_per_slice);
dispatch.push.tail_x = info.tail_x;
dispatch.push.tail_y = info.tail_y;
dispatch.push.tail = info.tail;
dispatches.push_back(dispatch);
}
}
void TileCompute::Destroy(Resources& target) const {
if (target.mapped != nullptr) {
graphics.UnmapMemory(target.staging.memory);
}
if (target.staging.buffer != nullptr) {
graphics.DeleteBuffer(target.staging);
}
if (target.linear.buffer != nullptr) {
graphics.DeleteBuffer(target.linear);
}
for (auto pipeline: target.pipelines) {
if (pipeline != nullptr) {
graphics.device.destroyPipeline(pipeline, nullptr);
}
}
if (target.descriptor_pool != nullptr) {
graphics.device.destroyDescriptorPool(target.descriptor_pool, nullptr);
}
if (target.pipeline_layout != nullptr) {
graphics.device.destroyPipelineLayout(target.pipeline_layout, nullptr);
}
if (target.descriptor_layout != nullptr) {
graphics.device.destroyDescriptorSetLayout(target.descriptor_layout, nullptr);
}
target = {};
}
void TileCompute::Init() {
if (resources.pipeline_layout != nullptr) {
return;
}
std::array<vk::DescriptorSetLayoutBinding, 2> bindings {};
for (uint32_t i = 0; i < bindings.size(); i++) {
bindings[i] = {i, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute,
nullptr};
}
vk::DescriptorSetLayoutCreateInfo descriptor_info {};
descriptor_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
descriptor_info.bindingCount = static_cast<uint32_t>(bindings.size());
descriptor_info.pBindings = bindings.data();
RequireVulkanSuccess(graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
&resources.descriptor_layout),
"create GPU tiler descriptor layout");
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 = &resources.descriptor_layout;
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = &push_range;
RequireVulkanSuccess(
graphics.device.createPipelineLayout(&layout_info, nullptr, &resources.pipeline_layout),
"create GPU tiler pipeline layout");
vk::DescriptorPoolSize pool_size {vk::DescriptorType::eStorageBuffer, 2};
vk::DescriptorPoolCreateInfo pool_info {};
pool_info.sType = vk::StructureType::eDescriptorPoolCreateInfo;
pool_info.maxSets = 1;
pool_info.poolSizeCount = 1;
pool_info.pPoolSizes = &pool_size;
RequireVulkanSuccess(
graphics.device.createDescriptorPool(&pool_info, nullptr, &resources.descriptor_pool),
"create GPU tiler descriptor pool");
vk::DescriptorSetAllocateInfo set_info {};
set_info.sType = vk::StructureType::eDescriptorSetAllocateInfo;
set_info.descriptorPool = resources.descriptor_pool;
set_info.descriptorSetCount = 1;
set_info.pSetLayouts = &resources.descriptor_layout;
RequireVulkanSuccess(
graphics.device.allocateDescriptorSets(&set_info, &resources.descriptor_set),
"allocate GPU tiler descriptor set");
}
void TileCompute::CreatePipeline(uint32_t pipeline_slot) {
const uint32_t element_size_index = pipeline_slot % BYTES_PER_ELEMENT_COUNT;
const uint32_t family_direction_index = pipeline_slot / BYTES_PER_ELEMENT_COUNT;
const uint32_t family_index = family_direction_index % FAMILY_COUNT;
const uint32_t direction_index = family_direction_index / FAMILY_COUNT;
const uint32_t specialization_values[] {1u << element_size_index, direction_index};
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
vk::SpecializationInfo specialization {2, entries, sizeof(specialization_values),
specialization_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(graphics.device.createShaderModule(&module_info, nullptr, &module),
"create GPU tiler 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 info {};
info.sType = vk::StructureType::eComputePipelineCreateInfo;
info.stage = stage;
info.layout = resources.pipeline_layout;
vk::Pipeline pipeline = nullptr;
const auto result =
graphics.device.createComputePipelines(nullptr, 1, &info, nullptr, &pipeline);
graphics.device.destroyShaderModule(module, nullptr);
RequireVulkanSuccess(result, "create GPU tiler pipeline");
resources.pipelines[pipeline_slot] = pipeline;
}
void TileCompute::CreatePipelines(std::span<const Dispatch> dispatches) {
for (const auto& dispatch: dispatches) {
if (resources.pipelines[dispatch.pipeline_slot] == nullptr) {
CreatePipeline(dispatch.pipeline_slot);
}
}
}
void TileCompute::CreateBuffer(uint64_t size, bool mapped, VulkanBuffer& buffer,
void** data) const {
buffer.usage = vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst;
buffer.memory.property =
mapped
? vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent
: vk::MemoryPropertyFlags(vk::MemoryPropertyFlagBits::eDeviceLocal);
graphics.CreateBuffer(size, buffer);
if (mapped) graphics.MapMemory(buffer.memory, *data);
}
void TileCompute::Resize(uint64_t staging_size, uint64_t linear_size) {
if (resources.staging.buffer_size >= staging_size &&
resources.linear.buffer_size >= linear_size) {
return;
}
staging_size = std::max(staging_size, resources.staging.buffer_size);
linear_size = std::max(linear_size, resources.linear.buffer_size);
VulkanBuffer staging {}, linear {};
void* mapped = nullptr;
CreateBuffer(staging_size, true, staging, &mapped);
CreateBuffer(linear_size, false, linear, nullptr);
if (resources.mapped != nullptr) graphics.UnmapMemory(resources.staging.memory);
if (resources.staging.buffer != nullptr) graphics.DeleteBuffer(resources.staging);
if (resources.linear.buffer != nullptr) graphics.DeleteBuffer(resources.linear);
resources.staging = staging;
resources.linear = linear;
resources.mapped = mapped;
}
void TileCompute::Execute(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const Dispatch> dispatches,
const GpuTileRecord& record) {
const uint64_t tiled_size = AlignToDword(tiled_capacity);
const uint64_t linear_size = AlignToDword(linear_capacity);
const uint64_t input_size = to_tiled ? linear_capacity : tiled_capacity;
if (input != nullptr) {
std::memcpy(resources.mapped, input, static_cast<size_t>(input_size));
std::memset(static_cast<uint8_t*>(resources.mapped) + input_size, 0,
static_cast<size_t>(AlignToDword(input_size) - input_size));
}
std::array<vk::DescriptorBufferInfo, 2> buffer_info {{
{to_tiled ? resources.linear.buffer : resources.staging.buffer, 0,
to_tiled ? linear_size : tiled_size},
{to_tiled ? resources.staging.buffer : resources.linear.buffer, 0,
to_tiled ? tiled_size : linear_size},
}};
std::array<vk::WriteDescriptorSet, 2> writes {};
for (uint32_t i = 0; i < writes.size(); i++) {
writes[i].sType = vk::StructureType::eWriteDescriptorSet;
writes[i].dstSet = resources.descriptor_set;
writes[i].dstBinding = i;
writes[i].descriptorCount = 1;
writes[i].descriptorType = vk::DescriptorType::eStorageBuffer;
writes[i].pBufferInfo = &buffer_info[i];
}
graphics.device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0,
nullptr);
CommandBuffer command;
command.Begin();
auto vk_command = command.Handle();
if (input != nullptr) {
Barrier(vk_command, resources.staging.buffer, vk::AccessFlagBits::eHostWrite,
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead,
vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer);
}
if (to_tiled && input != nullptr) {
const vk::BufferCopy copy {0, 0, linear_size};
vk_command.copyBuffer(resources.staging.buffer, resources.linear.buffer, 1, &copy);
Barrier(vk_command, resources.linear.buffer, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eComputeShader);
Barrier(vk_command, resources.staging.buffer, vk::AccessFlagBits::eTransferRead,
vk::AccessFlagBits::eTransferWrite, vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eTransfer);
}
if (to_tiled && record) {
record(command, resources.linear);
Barrier(vk_command, resources.linear.buffer,
vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eShaderRead, vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eComputeShader);
}
const auto output_buffer = to_tiled ? resources.staging.buffer : resources.linear.buffer;
const auto output_size = to_tiled ? tiled_size : linear_size;
vk_command.fillBuffer(output_buffer, 0, output_size, 0);
Barrier(vk_command, output_buffer, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite,
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader);
vk_command.bindDescriptorSets(vk::PipelineBindPoint::eCompute, resources.pipeline_layout, 0, 1,
&resources.descriptor_set, 0, nullptr);
for (const auto& dispatch: dispatches) {
vk_command.bindPipeline(vk::PipelineBindPoint::eCompute,
resources.pipelines[dispatch.pipeline_slot]);
vk_command.pushConstants(resources.pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
sizeof(dispatch.push), &dispatch.push);
vk_command.dispatch((dispatch.push.width + 7u) / 8u, (dispatch.push.height + 7u) / 8u,
dispatch.push.depth);
}
Barrier(vk_command, output_buffer, vk::AccessFlagBits::eShaderWrite,
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eHostRead,
vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost);
if (!to_tiled && record) {
record(command, resources.linear);
}
if (!to_tiled && output != nullptr) {
const vk::BufferCopy copy {0, 0, linear_size};
vk_command.copyBuffer(resources.linear.buffer, resources.staging.buffer, 1, &copy);
Barrier(vk_command, resources.staging.buffer, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eHostRead, vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eHost);
}
command.End();
command.Execute();
command.WaitForFence();
if (output != nullptr) {
std::memcpy(output, resources.mapped,
static_cast<size_t>(to_tiled ? tiled_capacity : linear_capacity));
}
}
void TileCompute::Run(bool to_tiled, const void* input, void* output, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
const GpuTileRecord& record) {
EXIT_IF((to_tiled && (output == nullptr || (input == nullptr && !record))) ||
(!to_tiled && (input == nullptr || (output == nullptr && !record))));
std::vector<Dispatch> dispatches;
Prepare(to_tiled, tiled_capacity, linear_capacity, infos, dispatches);
Init();
CreatePipelines(dispatches);
const uint64_t staging_size =
std::max(AlignToDword(tiled_capacity), AlignToDword(linear_capacity));
const uint64_t linear_size = AlignToDword(linear_capacity);
Resize(staging_size, linear_size);
Execute(to_tiled, input, output, tiled_capacity, linear_capacity, dispatches, record);
}
void TileCompute::Release() {
Destroy(resources);
}
} // namespace
void GpuDetile(const void* tiled, void* linear, uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos, const GpuTileRecord& after) {
Common::LockGuard lock(g_tiler_mutex);
if (!g_tiler) {
g_tiler = std::make_unique<TileCompute>(GetRenderContext().GetGraphics());
}
g_tiler->Run(false, tiled, linear, tiled_capacity, linear_capacity, infos, after);
}
void GpuTile(const void* linear, void* tiled, uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos, const GpuTileRecord& before) {
Common::LockGuard lock(g_tiler_mutex);
if (!g_tiler) {
g_tiler = std::make_unique<TileCompute>(GetRenderContext().GetGraphics());
}
g_tiler->Run(true, linear, tiled, tiled_capacity, linear_capacity, infos, before);
}
void GpuTileRelease() {
Common::LockGuard lock(g_tiler_mutex);
if (g_tiler) {
g_tiler->Release();
g_tiler.reset();
}
}
} // namespace Libs::Graphics
-45
View File
@@ -1,45 +0,0 @@
#pragma once
#include "graphics/guest_gpu/tile.h"
#include <cstdint>
#include <functional>
#include <span>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanBuffer;
class CommandBuffer;
struct GpuTileInfo {
TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0;
uint64_t linear_offset = 0;
uint64_t linear_size = 0;
uint64_t tiled_offset = 0;
uint64_t tiled_size = 0;
uint64_t linear_slice_stride = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t pitch = 0;
uint32_t tail_x = 0;
uint32_t tail_y = 0;
bool tail = false;
uint32_t tiled_width = 0;
uint32_t tiled_height = 0;
uint32_t surface_z = 0;
};
using GpuTileRecord = std::function<void(CommandBuffer&, VulkanBuffer&)>;
void GpuDetile(const void* tiled, void* linear, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
const GpuTileRecord& after = {});
void GpuTile(const void* linear, void* tiled, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
const GpuTileRecord& before = {});
void GpuTileRelease();
} // namespace Libs::Graphics
+27 -106
View File
@@ -6,8 +6,6 @@
#include "graphics/host_gpu/vulkanCommon.h" // IWYU pragma: export
#include "graphics/host_gpu/vulkanInstance.h"
#include <memory>
#include <mutex>
#include <vector>
#include <vk_mem_alloc.h>
@@ -18,32 +16,20 @@ struct VulkanBuffer;
struct VulkanImage;
struct VulkanMemory;
struct VulkanSwapchain {
~VulkanSwapchain();
vk::SwapchainKHR swapchain = nullptr;
vk::Format swapchain_format = vk::Format::eUndefined;
vk::Extent2D swapchain_extent = {};
std::unique_ptr<vk::Image[]> swapchain_images;
std::unique_ptr<vk::ImageView[]> swapchain_image_views;
uint32_t swapchain_images_count = 0;
std::unique_ptr<vk::Semaphore[]> image_acquired_semaphores;
std::unique_ptr<vk::Semaphore[]> render_complete_semaphores;
uint32_t current_index = 0;
uint32_t present_frame = 0;
};
struct GraphicContext: public VulkanInstance {
[[nodiscard]] bool CreateAllocator();
void DestroyAllocator();
void LogMemoryBudget() const;
void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
void DeleteBuffer(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(
[[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);
void DeleteBuffer(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);
void LoadHardwareRayTracingFunctions() const;
@@ -64,92 +50,29 @@ struct VulkanMemory {
uint64_t unique_id = 0;
};
enum class VulkanImageType {
Unknown,
VideoOut,
DepthStencil,
Texture,
StorageTexture,
RenderTexture
};
struct ImageViewInfo {
vk::Format format = vk::Format::eUndefined;
vk::ImageViewType type = vk::ImageViewType::e2D;
vk::ImageAspectFlags aspect = {};
uint32_t base_level = 0;
uint32_t level_count = 0;
uint32_t base_layer = 0;
uint32_t layer_count = 1;
uint32_t swizzle = 0;
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
bool operator==(const ImageViewInfo&) const = default;
};
struct CachedImageView {
ImageViewInfo info;
vk::ImageView view = nullptr;
};
struct ImageViewCache {
std::mutex mutex;
std::vector<CachedImageView> views;
ImageViewCache() = default;
KYTY_CLASS_NO_COPY(ImageViewCache);
struct VulkanImageState {
vk::PipelineStageFlags2 pl_stage = vk::PipelineStageFlagBits2::eAllCommands;
vk::AccessFlags2 access_mask = vk::AccessFlagBits2::eNone;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
};
struct VulkanImage {
static constexpr int VIEW_MAX = 4;
static constexpr int VIEW_DEFAULT = 0;
static constexpr int VIEW_DEFAULT_ARRAY = 1;
static constexpr int VIEW_STORAGE = 2;
static constexpr int VIEW_STORAGE_ARRAY = 3;
explicit VulkanImage(VulkanImageType type): type(type) {}
VulkanImage() = default;
KYTY_CLASS_NO_COPY(VulkanImage);
VulkanImageType type = VulkanImageType::Unknown;
vk::Format format = vk::Format::eUndefined;
vk::Extent2D extent = {};
uint32_t guest_pitch = 0;
uint32_t layers = 1;
uint32_t mip_levels = 1;
uint32_t samples = 1;
vk::Image image = nullptr;
vk::ImageView image_view[VIEW_MAX] = {};
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
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;
vk::ImageUsageFlags usage = {};
vk::ImageCreateFlags flags = {};
vk::Image image = nullptr;
VulkanImageState state;
std::vector<VulkanImageState> subresource_states;
Graphics::VulkanMemory memory;
ImageViewCache view_cache;
};
struct VideoOutVulkanImage: public VulkanImage {
VideoOutVulkanImage(): VulkanImage(VulkanImageType::VideoOut) {}
};
struct DepthStencilVulkanImage: public VulkanImage {
DepthStencilVulkanImage(): VulkanImage(VulkanImageType::DepthStencil) {}
bool compressed = false;
bool initial_depth_clear_pending = false;
bool initial_stencil_clear_pending = false;
};
struct GpuTextureVulkanImage: public VulkanImage {
explicit GpuTextureVulkanImage(VulkanImageType type): VulkanImage(type) {}
};
struct TextureVulkanImage: public GpuTextureVulkanImage {
TextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::Texture) {}
};
struct StorageTextureVulkanImage: public GpuTextureVulkanImage {
StorageTextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::StorageTexture) {}
};
struct RenderTextureVulkanImage: public VulkanImage {
RenderTextureVulkanImage(): VulkanImage(VulkanImageType::RenderTexture) {}
bool initial_clear_pending = false;
};
struct VulkanBuffer {
@@ -159,8 +82,6 @@ struct VulkanBuffer {
uint64_t buffer_size = 0;
};
struct StorageVulkanBuffer: public VulkanBuffer {};
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_ */
+38
View File
@@ -31,6 +31,44 @@ MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_
MemoryTracker::~MemoryTracker() = default;
void MemoryTracker::ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept {
if (vaddr == 0 || size == 0 || size > UINT64_MAX - vaddr ||
(vaddr & (TRACKER_PAGE_SIZE - 1)) != 0 || (size & (TRACKER_PAGE_SIZE - 1)) != 0) {
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) {
EXIT("MemoryTracker: GPU-dirty tracker page has no dirty bytes, operation=%s "
"addr=0x%016" PRIx64 "\n",
operation, page);
}
}
}
void MemoryTracker::ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) {
ValidateRange(vaddr, size);
if (vaddr + size > UINT64_MAX - (TRACKER_PAGE_SIZE - 1)) {
EXIT("MemoryTracker: dirty ownership range alignment overflow\n");
}
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; });
if (IsRegionGpuModified(page, TRACKER_PAGE_SIZE) != has_dirty_bytes) {
EXIT("MemoryTracker: tracker and byte ownership disagree, operation=%s "
"addr=0x%016" PRIx64 "\n",
operation, page);
}
}
}
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
+13 -5
View File
@@ -3,6 +3,7 @@
#include "common/assert.h"
#include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/regionManager.h"
#include <algorithm>
@@ -10,6 +11,7 @@
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>
#include <vector>
namespace Libs::Graphics {
@@ -38,6 +40,10 @@ public:
PageFaultPhase phase) noexcept;
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
uint64_t size, PageFaultPhase phase) noexcept;
void ValidateGpuDirtyPages(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation) const noexcept;
void ValidateGpuDirtyOwnership(const RangeSet& dirty, uint64_t vaddr, uint64_t size,
const char* operation);
template <bool clear, typename Preflight, typename Func>
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
@@ -99,7 +105,7 @@ public:
std::unique_lock access(m_access_mutex);
RequireMapped(vaddr, size);
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
s_upload_owner = this;
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
manager->lock.lock();
manager->Track(manager->GetCpuAddr() + offset, bytes);
@@ -119,15 +125,15 @@ public:
manager->lock.unlock();
});
}
s_upload_owner = nullptr;
s_upload_owner = previous_upload_owner;
}
private:
static constexpr size_t REGION_COUNT = TRACKER_ADDRESS_SIZE / TRACKER_REGION_SIZE;
inline static thread_local const MemoryTracker* s_upload_owner = nullptr;
static void CheckNotInUploadCallback() noexcept {
if (s_upload_owner != nullptr) {
void CheckNotInUploadCallback() const noexcept {
if (s_upload_owner == this) {
EXIT("memory tracker re-entered from upload callback\n");
}
}
@@ -167,7 +173,9 @@ private:
void RequireMapped(uint64_t vaddr, uint64_t size) const {
ValidateRange(vaddr, size);
if (!m_page_manager.IsMapped(vaddr, size)) {
EXIT("memory tracker range is not mapped\n");
EXIT("memory tracker range [0x%llx, 0x%llx) is not mapped\n",
static_cast<unsigned long long>(vaddr),
static_cast<unsigned long long>(vaddr + size));
}
}
RegionManager* GetOrCreateRegion(uint64_t index);
-348
View File
@@ -1,348 +0,0 @@
#include "graphics/host_gpu/objects/label.h"
#include "common/assert.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <memory>
#include <vector>
namespace Libs::Graphics {
enum LabelStatus {
New,
Active,
ActiveDeleted,
NotActive,
};
struct LabelCallbacks {
LabelCallback callback_1 = nullptr;
LabelCallback callback_2 = nullptr;
uint64_t args[LABEL_ARGS_MAX] = {};
};
struct LabelEvent final {
vk::Device device = nullptr;
vk::Event event = nullptr;
~LabelEvent() {
if (event != nullptr) {
device.destroyEvent(event, nullptr);
}
}
};
struct LabelSubmission {
std::shared_ptr<LabelEvent> completion;
LabelCallbacks callbacks;
};
struct Label {
vk::Device device = nullptr;
LabelStatus status = LabelStatus::New;
LabelCallbacks callbacks;
std::vector<LabelSubmission> submissions;
};
class LabelManager {
public:
LabelManager() {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
Common::Thread t(ThreadRun, this);
t.Detach();
}
~LabelManager() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(LabelManager);
Label* Create(LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
void Delete(Label& label);
void Set(CommandBuffer& buffer, Label& label);
void Drain();
private:
static void ThreadRun(void* data);
bool Remove(Label& label);
static void Destroy(Label& label);
Common::Mutex m_mutex;
Common::CondVar m_cond_var;
std::vector<Label*> m_labels;
uint64_t m_callbacks_in_flight = 0;
};
static LabelManager* g_label_manager = nullptr;
static thread_local bool g_in_label_callback = false;
class LabelCallbackScope final {
public:
LabelCallbackScope() {
if (g_in_label_callback) {
EXIT("recursive GPU label callback\n");
}
g_in_label_callback = true;
}
~LabelCallbackScope() {
if (!g_in_label_callback) {
EXIT("GPU label callback scope is not active\n");
}
g_in_label_callback = false;
}
};
void LabelManager::ThreadRun(void* data) {
auto* manager = static_cast<LabelManager*>(data);
for (;;) {
manager->m_mutex.Lock();
uint64_t active_count = 0;
std::vector<Label*> deleted_labels;
std::vector<LabelCallbacks> fired_labels;
std::vector<LabelSubmission> finished_submissions;
deleted_labels.reserve(manager->m_labels.size());
for (auto& label: manager->m_labels) {
for (auto it = label->submissions.begin(); it != label->submissions.end();) {
active_count++;
if (it->completion == nullptr || it->completion->device == nullptr ||
it->completion->event == nullptr) {
EXIT("GPU label submission has no completion event\n");
}
const auto status = it->completion->device.getEventStatus(it->completion->event);
switch (status) {
case vk::Result::eEventSet:
fired_labels.push_back(it->callbacks);
finished_submissions.push_back(*it);
it = label->submissions.erase(it);
break;
case vk::Result::eEventReset: ++it; break;
default: EXIT("vkGetEventStatus returned an unexpected result\n");
}
}
if (label->submissions.empty()) {
switch (label->status) {
case LabelStatus::ActiveDeleted: deleted_labels.push_back(label); break;
case LabelStatus::Active: label->status = LabelStatus::NotActive; break;
default: break;
}
}
}
if (active_count == 0) {
manager->m_cond_var.Wait(&manager->m_mutex);
}
if (fired_labels.size() > UINT64_MAX - manager->m_callbacks_in_flight) {
EXIT("GPU label callback count overflow\n");
}
manager->m_callbacks_in_flight += fired_labels.size();
for (auto& label: deleted_labels) {
bool removed = manager->Remove(*label);
EXIT_NOT_IMPLEMENTED(!removed);
}
manager->m_mutex.Unlock();
// Each completion event is shared with the recording command buffer's fence retainer.
// The event is destroyed only after both the label thread observed it and that command
// buffer completed, even when either side wins the race.
(void)finished_submissions;
for (auto& label: deleted_labels) {
Destroy(*label);
}
for (auto& label: fired_labels) {
LabelCallbackScope callback_scope;
if (label.callback_1 != nullptr) {
(void)label.callback_1(label.args);
}
if (label.callback_2 != nullptr) {
label.callback_2(label.args);
}
}
if (!fired_labels.empty()) {
Common::LockGuard lock(manager->m_mutex);
if (manager->m_callbacks_in_flight < fired_labels.size()) {
EXIT("GPU label callback count underflow\n");
}
manager->m_callbacks_in_flight -= fired_labels.size();
manager->m_cond_var.SignalAll();
}
Common::Thread::SleepMicro(100);
}
}
Label* LabelManager::Create(LabelCallback callback_1, LabelCallback callback_2,
const uint64_t* args) {
Common::LockGuard lock(m_mutex);
auto* label = new Label;
label->status = LabelStatus::New;
label->device = GetRenderContext().GetGraphics().device;
label->callbacks.callback_1 = callback_1;
label->callbacks.callback_2 = callback_2;
if (args != nullptr) {
for (int i = 0; i < LABEL_ARGS_MAX; i++) {
label->callbacks.args[i] = args[i];
}
}
m_labels.push_back(label);
return label;
}
bool LabelManager::Remove(Label& label) {
EXIT_IF(label.device == nullptr);
Common::LockGuard lock(m_mutex);
const auto it = std::find(m_labels.begin(), m_labels.end(), &label);
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
EXIT_NOT_IMPLEMENTED(label.status != LabelStatus::NotActive &&
label.status != LabelStatus::Active &&
label.status != LabelStatus::ActiveDeleted);
if (!label.submissions.empty()) {
label.status = LabelStatus::ActiveDeleted;
return false;
}
m_labels.erase(it);
return true;
}
void LabelManager::Destroy(Label& label) {
EXIT_IF(label.device == nullptr);
EXIT_NOT_IMPLEMENTED(!label.submissions.empty());
delete &label;
}
void LabelManager::Delete(Label& label) {
if (Remove(label)) {
Destroy(label);
}
}
void LabelManager::Set(CommandBuffer& buffer, Label& label) {
EXIT_IF(buffer.IsInvalid());
EXIT_IF(label.device == nullptr);
Common::LockGuard lock(m_mutex);
const auto it = std::find(m_labels.begin(), m_labels.end(), &label);
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
EXIT_NOT_IMPLEMENTED(label.status != LabelStatus::New &&
label.status != LabelStatus::NotActive &&
label.status != LabelStatus::Active);
label.status = LabelStatus::Active;
LabelSubmission submission {};
submission.callbacks = label.callbacks;
submission.completion = std::make_shared<LabelEvent>();
submission.completion->device = label.device;
auto vk_buffer = buffer.Handle();
EXIT_NOT_IMPLEMENTED(vk_buffer == nullptr);
vk::EventCreateInfo create_info {};
create_info.sType = vk::StructureType::eEventCreateInfo;
create_info.pNext = nullptr;
create_info.flags = {};
const auto create_result =
label.device.createEvent(&create_info, nullptr, &submission.completion->event);
if (create_result != vk::Result::eSuccess || submission.completion->event == nullptr) {
EXIT("failed to create label event: %s (%d)\n", VulkanToString(create_result).c_str(),
static_cast<int>(create_result));
}
buffer.RetainResourceUntilFence(submission.completion);
// Labels can be reused before an earlier end-of-pipe event has been
// observed by the polling thread. Capture a separate Vulkan event and
// callback snapshot for each set so older writes are not lost.
const auto reset_result = label.device.resetEvent(submission.completion->event);
if (reset_result != vk::Result::eSuccess) {
EXIT("failed to reset label event: %s (%d)\n", VulkanToString(reset_result).c_str(),
static_cast<int>(reset_result));
}
vk_buffer.setEvent(submission.completion->event, vk::PipelineStageFlagBits::eBottomOfPipe);
label.submissions.push_back(submission);
m_cond_var.SignalAll();
}
void LabelManager::Drain() {
m_mutex.Lock();
for (;;) {
const bool pending = std::any_of(m_labels.begin(), m_labels.end(), [](const Label* label) {
return !label->submissions.empty();
});
if (!pending && m_callbacks_in_flight == 0) {
m_mutex.Unlock();
return;
}
m_cond_var.SignalAll();
m_cond_var.Wait(&m_mutex);
}
}
void LabelInit() {
EXIT_IF(g_label_manager != nullptr);
g_label_manager = new LabelManager;
}
Label* LabelCreate(LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args) {
EXIT_IF(g_label_manager == nullptr);
return g_label_manager->Create(callback_1, callback_2, args);
}
void LabelDelete(Label& label) {
EXIT_IF(g_label_manager == nullptr);
g_label_manager->Delete(label);
}
void LabelSet(CommandBuffer& buffer, Label& label) {
EXIT_IF(g_label_manager == nullptr);
g_label_manager->Set(buffer, label);
}
void LabelDrain() {
EXIT_IF(g_label_manager == nullptr);
g_label_manager->Drain();
}
bool LabelInCallback() noexcept {
return g_in_label_callback;
}
} // namespace Libs::Graphics
-26
View File
@@ -1,26 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
#include "common/abi.h"
#include "common/common.h"
namespace Libs::Graphics {
struct Label;
class CommandBuffer;
struct GraphicContext;
void LabelInit();
constexpr int LABEL_ARGS_MAX = 5;
using LabelCallback = bool (*)(const uint64_t* args);
Label* LabelCreate(LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
void LabelDelete(Label& label);
void LabelSet(CommandBuffer& buffer, Label& label);
void LabelDrain();
[[nodiscard]] bool LabelInCallback() noexcept;
} // namespace Libs::Graphics
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_ */
+73 -508
View File
@@ -1,17 +1,12 @@
#include "graphics/host_gpu/objects/textureCommon.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/renderer/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cinttypes>
#include <cstring>
@@ -159,6 +154,8 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_
raw_type, raw_order);
}
namespace {
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
}
@@ -193,28 +190,6 @@ uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint3
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
}
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
const std::vector<BufferImageCopy>& regions, uint64_t levels,
uint32_t depth) {
uint64_t size = 0;
for (const auto& r: regions) {
size = std::max<uint64_t>(size, static_cast<uint64_t>(r.offset) +
layout.level_sizes[r.dst_level].size);
}
for (uint32_t level = 0; level < levels; level++) {
const auto src_size = GetLevelSrcSize(layout.level_sizes[level]);
const auto mip_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
size = std::max<uint64_t>(size,
TextureUploadSliceSourceOffset(layout, level, z) + src_size);
}
}
return size;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
@@ -232,6 +207,8 @@ 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)));
@@ -241,94 +218,6 @@ vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
return components;
}
bool TextureCheckFormat(vk::ImageCreateInfo& image_info) {
auto& graphics = GetRenderContext().GetGraphics();
vk::ImageFormatProperties props {};
if (graphics.GetImageFormatProperties(image_info.format, image_info.imageType,
image_info.tiling, image_info.usage, image_info.flags,
&props) == vk::Result::eErrorFormatNotSupported) {
auto apply_fallback = [&](vk::Format replacement, const char* message) {
image_info.format = replacement;
const bool result = TextureCheckFormat(image_info);
LOGF("%s [%s]\n", message, (!result ? "FAIL" : "SUCCESS"));
return result;
};
if (image_info.format == vk::Format::eR8G8B8A8Srgb) {
// TODO() convert SRGB -> LINEAR in shader
return apply_fallback(
vk::Format::eR8G8B8A8Unorm,
"replace vk::Format::eR8G8B8A8Srgb => vk::Format::eR8G8B8A8Unorm");
}
if (image_info.format == vk::Format::eB8G8R8A8Srgb) {
// TODO() convert SRGB -> LINEAR in shader
return apply_fallback(
vk::Format::eB8G8R8A8Unorm,
"replace vk::Format::eB8G8R8A8Srgb => vk::Format::eB8G8R8A8Unorm");
}
return false;
}
return true;
}
static bool TextureCheckFormatExact(const vk::ImageCreateInfo& image_info) {
auto& graphics = GetRenderContext().GetGraphics();
vk::ImageFormatProperties props {};
return graphics.GetImageFormatProperties(image_info.format, image_info.imageType,
image_info.tiling, image_info.usage, image_info.flags,
&props) != vk::Result::eErrorFormatNotSupported;
}
bool TextureCheckStorageSwizzle(vk::ImageCreateInfo& image_info, vk::ComponentMapping& components) {
if (image_info.usage & vk::ImageUsageFlagBits::eStorage) {
if (components.r == vk::ComponentSwizzle::eR && components.g == vk::ComponentSwizzle::eG &&
components.b == vk::ComponentSwizzle::eB && components.a == vk::ComponentSwizzle::eA) {
return true;
}
if (components.r == vk::ComponentSwizzle::eB && components.g == vk::ComponentSwizzle::eG &&
components.b == vk::ComponentSwizzle::eR && components.a == vk::ComponentSwizzle::eA &&
image_info.format == vk::Format::eR8G8B8A8Srgb) {
LOGF("replace vk::Format::eR8G8B8A8Srgb => vk::Format::eB8G8R8A8Srgb\n");
components.r = vk::ComponentSwizzle::eR;
components.g = vk::ComponentSwizzle::eG;
components.b = vk::ComponentSwizzle::eB;
components.a = vk::ComponentSwizzle::eA;
image_info.format = vk::Format::eB8G8R8A8Srgb;
return true;
}
// TODO() swizzle channels in shader
return false;
}
return true;
}
vk::ImageUsageFlags TextureGetUsage(TextureFormatUsage usage) {
vk::ImageUsageFlags vk_usage =
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc;
if (TextureHasFormatUsage(usage, TextureFormatUsage::Sampled)) {
vk_usage |= vk::ImageUsageFlagBits::eSampled;
}
if (TextureHasFormatUsage(usage, TextureFormatUsage::Storage)) {
vk_usage |= vk::ImageUsageFlagBits::eStorage;
}
return vk_usage;
}
vk::ImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage) {
vk::ImageUsageFlags vk_usage = {};
if (TextureHasFormatUsage(usage, TextureFormatUsage::Sampled)) {
vk_usage |= vk::ImageUsageFlagBits::eSampled;
}
if (TextureHasFormatUsage(usage, TextureFormatUsage::Storage)) {
vk_usage |= vk::ImageUsageFlagBits::eStorage;
}
return vk_usage;
}
vk::Format TextureGetFormat(uint32_t fmt) {
const auto vk_format = VulkanFormat(fmt);
if (vk_format != vk::Format::eUndefined) {
@@ -338,261 +227,10 @@ vk::Format TextureGetFormat(uint32_t fmt) {
return vk::Format::eUndefined;
}
static uint32_t AlignUpU32(uint32_t value, uint32_t alignment) {
return (value + alignment - 1u) & ~(alignment - 1u);
}
namespace {
uint32_t TextureGetAtlasSliceYStride(vk::Format format, uint32_t mip_height, uint32_t depth,
uint64_t levels) {
return (depth > 1 && levels > 1 && Transfer::IsBlockCompressedFormat(format)
? AlignUpU32(mip_height, 4u)
: mip_height);
}
uint32_t TextureCalcStackedImageHeight(vk::Format format, uint32_t height, uint32_t depth,
uint64_t levels) {
auto image_height = height * depth;
if (depth <= 1 || levels <= 1 || !Transfer::IsBlockCompressedFormat(format)) {
return image_height;
}
uint32_t mip_height = height;
for (uint32_t level = 0; level < levels; level++) {
const auto mip_image_height =
TextureGetAtlasSliceYStride(format, mip_height, depth, levels) * depth;
image_height = std::max<uint32_t>(image_height, mip_image_height << level);
if (mip_height > 1) {
mip_height /= 2;
}
}
return image_height;
}
uint32_t TextureCalcMipmapAtlasImageHeight(vk::Format format, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels) {
auto image_height = height * depth;
if (levels <= 1) {
return image_height;
}
uint32_t mip_height = height;
for (uint32_t level = 0; level < levels; level++) {
const auto mipmap_offset = Transfer::MipmapAtlasOffset(level, width, height);
const auto mip_bottom =
static_cast<uint32_t>(mipmap_offset.second) +
TextureGetAtlasSliceYStride(format, mip_height, depth, levels) * depth;
image_height = std::max<uint32_t>(image_height, mip_bottom);
if (mip_height > 1) {
mip_height /= 2;
}
}
return image_height;
}
bool TextureIs3DTexture(uint64_t type) {
return static_cast<Prospero::ImageType>(type) == Prospero::ImageType::kColor3D;
}
bool TextureIsCubeTexture(uint64_t type) {
return static_cast<Prospero::ImageType>(type) == Prospero::ImageType::kCube;
}
bool TextureIsLayeredTexture(uint64_t type) {
const auto image_type = static_cast<Prospero::ImageType>(type);
return image_type == Prospero::ImageType::kCube ||
image_type == Prospero::ImageType::kColor1DArray ||
image_type == Prospero::ImageType::kColor2DArray ||
image_type == Prospero::ImageType::kColor2DMsaaArray;
}
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count) {
return TextureIsCubeTexture(type) && base_array % 6u == 0 && layer_count >= 6 &&
layer_count % 6u == 0;
}
vk::ComponentMapping TextureCreateImage(VulkanImage& vk_obj,
const TextureImageCreateParams& params) {
auto& graphics = GetRenderContext().GetGraphics();
EXIT_IF(params.owner == nullptr);
const bool array_texture = TextureIsLayeredTexture(params.type);
const bool volume_texture = TextureIs3DTexture(params.type);
auto pixel_format = TextureGetFormat(params.fmt);
EXIT_NOT_IMPLEMENTED(pixel_format == vk::Format::eUndefined);
EXIT_NOT_IMPLEMENTED(params.width == 0);
EXIT_NOT_IMPLEMENTED(params.height == 0);
EXIT_NOT_IMPLEMENTED(params.depth == 0);
EXIT_NOT_IMPLEMENTED(params.levels == 0 || params.levels > 16);
uint32_t image_height = 0;
uint32_t image_mips = 0;
if (params.image_layout == TextureUploadDestination::MipAtlas) {
EXIT_NOT_IMPLEMENTED(params.base_level != 0);
const auto atlas_depth = (array_texture || volume_texture ? 1u : params.depth);
image_height = TextureCalcMipmapAtlasImageHeight(
pixel_format, static_cast<uint32_t>(params.width), static_cast<uint32_t>(params.height),
atlas_depth, params.levels);
image_mips = 1;
} else {
image_height =
(array_texture || volume_texture
? static_cast<uint32_t>(params.height)
: TextureCalcStackedImageHeight(pixel_format, static_cast<uint32_t>(params.height),
params.depth, params.levels));
image_mips = static_cast<uint32_t>(params.levels);
}
vk::ComponentMapping components =
TextureGetComponentMapping(static_cast<uint32_t>(params.swizzle));
vk::ImageCreateInfo image_info {};
image_info.sType = vk::StructureType::eImageCreateInfo;
image_info.pNext = nullptr;
image_info.flags =
(params.allow_cube_view && TextureCanCreateCubeView(params.type, 0, params.depth)
? vk::ImageCreateFlagBits::eCubeCompatible
: vk::ImageCreateFlags {}) |
(volume_texture ? vk::ImageCreateFlagBits::e2DArrayCompatible : vk::ImageCreateFlags {}) |
(params.compatible_format_views ? vk::ImageCreateFlagBits::eMutableFormat
: vk::ImageCreateFlags {});
image_info.imageType = (volume_texture ? vk::ImageType::e3D : vk::ImageType::e2D);
image_info.extent.width = static_cast<uint32_t>(params.width);
image_info.extent.height = image_height;
image_info.extent.depth = (volume_texture ? params.depth : 1);
image_info.mipLevels = image_mips;
image_info.arrayLayers = (array_texture ? params.depth : 1);
image_info.format = pixel_format;
image_info.tiling = vk::ImageTiling::eOptimal;
image_info.initialLayout = vk::ImageLayout::eUndefined;
image_info.usage = TextureGetUsage(params.format_usage);
image_info.sharingMode = vk::SharingMode::eExclusive;
image_info.samples = vk::SampleCountFlagBits::e1;
const bool storage_view = TextureHasFormatUsage(params.view_usage, TextureFormatUsage::Storage);
if (storage_view && !TextureCheckStorageSwizzle(image_info, components)) {
if (!params.storage_swizzle_fallback) {
EXIT("swizzle is not supported");
}
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("\t %s swizzle 0x%08" PRIx64 " is not supported, using identity mapping\n",
params.owner, params.swizzle);
}
components.r = vk::ComponentSwizzle::eR;
components.g = vk::ComponentSwizzle::eG;
components.b = vk::ComponentSwizzle::eB;
components.a = vk::ComponentSwizzle::eA;
}
const auto view_checked_format = image_info.format;
const auto requested_usage = params.format_usage;
const auto required_usage = params.required_format_usage;
const bool has_optional_usage = static_cast<uint32_t>(requested_usage & ~required_usage) != 0;
if (!TextureCheckFormatExact(image_info) && has_optional_usage) {
auto required_info = image_info;
required_info.usage = TextureGetUsage(required_usage);
required_info.format = view_checked_format;
if (TextureCheckFormatExact(required_info)) {
static std::atomic_uint log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("\t %s usage 0x%08x is not supported for format %d, using required "
"usage 0x%08x\n",
params.owner, static_cast<uint32_t>(TextureGetUsage(requested_usage)),
static_cast<int>(image_info.format),
static_cast<uint32_t>(TextureGetUsage(required_usage)));
}
image_info = required_info;
}
}
if (!TextureCheckFormat(image_info)) {
if (has_optional_usage) {
image_info.format = view_checked_format;
image_info.usage = TextureGetUsage(required_usage);
}
if (!has_optional_usage || !TextureCheckFormat(image_info)) {
EXIT("format is not supported");
}
}
vk_obj.extent.width = image_info.extent.width;
vk_obj.extent.height = image_info.extent.height;
vk_obj.layers = image_info.arrayLayers;
vk_obj.mip_levels = image_info.mipLevels;
vk_obj.format = image_info.format;
vk_obj.image = nullptr;
vk_obj.layout = image_info.initialLayout;
vk_obj.memory.property = vk::MemoryPropertyFlagBits::eDeviceLocal;
bool created = graphics.CreateImage(image_info, vk_obj);
EXIT_NOT_IMPLEMENTED(!created);
return components;
}
void TextureCreateImageViews(VulkanImage& vk_obj,
vk::ComponentMapping components, uint64_t type, uint32_t base_array,
uint32_t base_level, uint32_t level_count, uint32_t depth,
bool allow_cube_view, TextureFormatUsage view_usage) {
auto& graphics = GetRenderContext().GetGraphics();
EXIT_IF(level_count == 0 || base_level + level_count > vk_obj.mip_levels);
const bool layered_texture = TextureIsLayeredTexture(type);
const bool volume_texture = TextureIs3DTexture(type);
const auto layer_count = (layered_texture && base_array < depth ? depth - base_array : 1u);
const auto volume_slices = std::max(depth >> base_level, 1u);
vk::ImageViewUsageCreateInfo usage_info {};
usage_info.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage_info.pNext = nullptr;
usage_info.usage = TextureGetViewUsage(view_usage);
vk::ImageViewCreateInfo create_info {};
create_info.sType = vk::StructureType::eImageViewCreateInfo;
create_info.pNext = (usage_info.usage ? &usage_info : nullptr);
create_info.flags = {};
create_info.image = vk_obj.image;
create_info.viewType = vk::ImageViewType::e2D;
create_info.format = vk_obj.format;
create_info.components = components;
create_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
create_info.subresourceRange.baseArrayLayer = (layered_texture ? base_array : 0);
create_info.subresourceRange.baseMipLevel = base_level;
create_info.subresourceRange.layerCount = layer_count;
create_info.subresourceRange.levelCount = level_count;
if (volume_texture) {
create_info.viewType = vk::ImageViewType::e3D;
create_info.subresourceRange.baseArrayLayer = 0;
create_info.subresourceRange.layerCount = 1;
} else if (allow_cube_view && TextureCanCreateCubeView(type, base_array, layer_count)) {
create_info.viewType =
(layer_count > 6 ? vk::ImageViewType::eCubeArray : vk::ImageViewType::eCube);
} else if (layered_texture) {
create_info.viewType = vk::ImageViewType::e2DArray;
}
auto create_view = [&](int index) {
const auto result =
graphics.device.createImageView(&create_info, nullptr, &vk_obj.image_view[index]);
if (result != vk::Result::eSuccess || vk_obj.image_view[index] == nullptr) {
EXIT("failed to create texture image view: result=%d format=%d index=%d\n",
static_cast<int>(result), static_cast<int>(create_info.format), index);
}
};
create_view(VulkanImage::VIEW_DEFAULT);
create_info.viewType = vk::ImageViewType::e2DArray;
create_info.subresourceRange.layerCount = volume_texture ? volume_slices : layer_count;
create_view(VulkanImage::VIEW_DEFAULT_ARRAY);
}
static uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels,
uint64_t total_size, uint32_t depth) {
uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride =
@@ -609,7 +247,7 @@ static uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64
return stride;
}
static uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
bytes_per_element != 0) {
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
@@ -625,8 +263,8 @@ static uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t
return 0;
}
static uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
uint64_t levels, uint32_t base_pitch) {
uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height,
uint64_t levels, uint32_t base_pitch) {
uint64_t offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
@@ -655,6 +293,8 @@ static uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt,
return offset;
}
} // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
@@ -663,6 +303,7 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
TextureUploadLayout layout {};
layout.tile = static_cast<uint32_t>(tile);
layout.pitch = static_cast<uint32_t>(pitch);
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u;
layout.volume_texture = volume_texture;
if (fmt != 0) {
@@ -752,12 +393,10 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64
return layout;
}
std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout& layout,
vk::Format image_format, uint32_t width,
uint32_t height, uint32_t depth,
uint64_t levels, bool array_texture,
bool volume_texture,
TextureUploadDestination destination) {
std::vector<vk::BufferImageCopy>
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels, bool array_texture,
bool volume_texture) {
uint32_t mip_width = width;
uint32_t mip_height = height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
@@ -765,53 +404,34 @@ std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout
? width
: layout.pitch;
std::vector<BufferImageCopy> regions;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture));
for (uint32_t i = 0; i < levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
const auto mipmap_offset = Transfer::MipmapAtlasOffset(i, width, height);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
BufferImageCopy region {};
region.offset = static_cast<uint32_t>(layout.level_sizes[i].offset + slice_offset);
region.width = mip_width;
region.height = mip_height;
region.copy_height =
(!array_texture && !volume_texture && depth > 1 && levels > 1 &&
Transfer::IsBlockCompressedFormat(image_format)
? TextureGetAtlasSliceYStride(image_format, mip_height, depth, levels)
: 0);
region.dst_layer = (array_texture ? z : 0);
region.dst_z = (volume_texture ? static_cast<int>(z) : 0);
if (!layout.volume_texture &&
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear &&
layout.padded_sizes[i].width != 0) {
region.pitch = layout.padded_sizes[i].width;
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset =
layout.level_sizes[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i,
array_texture ? z : 0, 1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear =
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.padded_sizes[i].width;
region.bufferImageHeight = layout.padded_sizes[i].height;
} else {
region.pitch = mip_pitch;
}
if (destination == TextureUploadDestination::MipLevels) {
region.dst_level = i;
region.dst_x = 0;
region.dst_y =
(array_texture || volume_texture
? 0
: static_cast<int>(z * TextureGetAtlasSliceYStride(
image_format, mip_height, depth, levels)));
} else {
region.dst_level = 0;
region.dst_x = mipmap_offset.first;
region.dst_y =
(array_texture || volume_texture
? mipmap_offset.second
: mipmap_offset.second +
static_cast<int>(z * TextureGetAtlasSliceYStride(
image_format, mip_height, depth, levels)));
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block;
};
const auto pitch = align(mip_pitch, layout.texel_block);
region.bufferRowLength =
pitch > align(mip_width, layout.texel_block) ? pitch : 0;
}
regions.push_back(region);
}
@@ -830,55 +450,6 @@ std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout
return regions;
}
std::vector<ImageBufferCopy>
TextureBuildDownloadRegions(const std::vector<BufferImageCopy>& upload_regions) {
std::vector<ImageBufferCopy> regions;
regions.reserve(upload_regions.size());
for (const auto& region: upload_regions) {
regions.push_back({region.offset, region.pitch, region.dst_level, region.width,
region.height, region.copy_height, region.dst_layer, region.dst_x,
region.dst_y, region.dst_z, region.aspect});
}
return regions;
}
static uint64_t FmaskRegionCopySize(const BufferImageCopy& region) {
const uint64_t row_length = (region.pitch != 0 ? region.pitch : region.width);
if (region.height == 0 || region.width == 0) {
return 0;
}
return ((static_cast<uint64_t>(region.height - 1u) * row_length) + region.width) *
sizeof(uint32_t);
}
static void UploadFmaskIdentity(VulkanImage& vk_obj,
const std::vector<BufferImageCopy>& regions,
vk::ImageLayout dst_layout, const char* owner) {
constexpr uint32_t kIdentityFmaskPattern = 0x76543210u;
std::vector<BufferImageCopy> upload_regions = regions;
uint64_t upload_size = 0;
for (auto& region: upload_regions) {
upload_size = (upload_size + 255u) & ~uint64_t {255};
EXIT_NOT_IMPLEMENTED(upload_size > UINT32_MAX);
region.offset = static_cast<uint32_t>(upload_size);
upload_size += FmaskRegionCopySize(region);
}
LOGF("%s: temporary: decoding PS5 FMASK8_S4_F4 as identity pattern 0x%08" PRIx32
", upload_size=%" PRIu64 " regions=%zu\n",
owner, kIdentityFmaskPattern, upload_size, upload_regions.size());
Transfer::ScratchBuffer temp_buf(upload_size);
auto* words = static_cast<uint32_t*>(temp_buf.Data());
for (uint64_t i = 0; i < upload_size / sizeof(uint32_t); i++) {
words[i] = kIdentityFmaskPattern;
}
Transfer::UploadImage(vk_obj, temp_buf.Data(), upload_size, upload_regions,
dst_layout);
}
struct GpuTileElementLayout {
uint32_t bytes = 0;
uint32_t wide = 1;
@@ -905,7 +476,8 @@ static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity,
return true;
}
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>& regions,
bool TextureBuildGpuTileInfos(uint64_t size,
const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
if (size == 0 || levels == 0 || levels > 16 || depth == 0 ||
@@ -929,7 +501,8 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
infos.reserve(regions.size());
if (layout.volume_texture) {
TileVolumeLayout volume {};
if (!TileGetTextureVolumeLayout(fmt, regions[0].width, regions[0].height, depth,
if (!TileGetTextureVolumeLayout(fmt, regions[0].imageExtent.width,
regions[0].imageExtent.height, depth,
static_cast<uint32_t>(levels), layout.tile, volume)) {
return false;
}
@@ -945,10 +518,16 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
const auto& region = regions[region_base + z];
const auto pitch = region.bufferRowLength != 0
? region.bufferRowLength
: region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.offset;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size +
volume.level_offsets[level];
@@ -961,11 +540,16 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
return false;
}
info.linear_slice_stride = linear_stride;
info.width = std::max((region.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max((region.height + element.tall - 1u) / element.tall, 1u);
info.width = std::max(
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max(
(logical_height + element.tall - 1u) / element.tall, 1u);
info.depth = copy_depth;
info.surface_z = block.block_depth == 1 ? static_cast<uint32_t>(region.dst_z) : 0;
info.pitch = std::max((region.pitch + element.wide - 1u) / element.wide, 1u);
info.surface_z = block.block_depth == 1
? static_cast<uint32_t>(region.imageOffset.z)
: 0;
info.pitch =
std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail_x = tail ? volume.tail_x[level] : 0;
info.tail_y = tail ? volume.tail_y[level] : 0;
info.tail = tail;
@@ -993,23 +577,32 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch = region.bufferRowLength != 0
? region.bufferRowLength
: region.imageExtent.width;
const auto logical_height = region.bufferImageHeight != 0
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.offset;
info.linear_offset = region.bufferOffset;
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
if (!SetGpuTileSize(info.linear_offset, level_size.size, size, info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), size,
info.tiled_size)) {
return false;
}
info.width = std::max((region.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max((region.height + element.tall - 1u) / element.tall, 1u);
info.width = std::max(
(region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max(
(logical_height + element.tall - 1u) / element.tall, 1u);
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
base_family == TileBlockFamily::Depth64KB
? region.dst_layer
? region.imageSubresource.baseArrayLayer
: 0;
info.pitch = std::max((region.pitch + element.wide - 1u) / element.wide, 1u);
info.pitch =
std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail = tail;
info.tail_x = tail ? level_size.x : 0;
info.tail_y = tail ? level_size.y : 0;
@@ -1037,32 +630,4 @@ bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>&
return true;
}
void TextureUploadGuestImage(VulkanImage& vk_obj, const void* src_data,
uint64_t size, const std::vector<BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
uint64_t height, uint32_t depth, uint64_t levels, const char* owner,
vk::ImageLayout dst_layout) {
if (fmt == 0) {
EXIT("%s: texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64 " extent=%" PRIu64
"x%" PRIu64 " depth=%u pitch=%u levels=%" PRIu64 "\n",
owner, layout.tile, size, width, height, depth, layout.pitch, levels);
}
if (static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear) {
Transfer::UploadImage(vk_obj, src_data, size, regions, dst_layout);
return;
}
if (layout.tile_family == TileBlockFamily::Depth64KB && Prospero::IsFmaskTextureFormat(fmt)) {
UploadFmaskIdentity(vk_obj, regions, dst_layout, owner);
return;
}
std::vector<GpuTileInfo> infos;
if (!TextureBuildGpuTileInfos(size, regions, layout, fmt, depth, levels, infos)) {
EXIT("%s: GPU tiled upload unsupported: fmt=%u tile=%u size=%" PRIu64 " extent=%" PRIu64
"x%" PRIu64 " depth=%u pitch=%u levels=%" PRIu64 "\n",
owner, fmt, layout.tile, size, width, height, depth, layout.pitch, levels);
}
Transfer::UploadTiledImage(vk_obj, src_data, size, size, infos, regions, dst_layout);
}
} // namespace Libs::Graphics
+8 -91
View File
@@ -5,44 +5,13 @@
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanImage;
enum class TextureFormatUsage : uint32_t {
None = 0,
Sampled = 1u << 0u,
Storage = 1u << 1u,
};
constexpr TextureFormatUsage operator|(TextureFormatUsage lhs, TextureFormatUsage rhs) {
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
}
constexpr TextureFormatUsage operator&(TextureFormatUsage lhs, TextureFormatUsage rhs) {
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
}
constexpr TextureFormatUsage operator~(TextureFormatUsage usage) {
return static_cast<TextureFormatUsage>(~static_cast<uint32_t>(usage));
}
constexpr TextureFormatUsage& operator|=(TextureFormatUsage& lhs, TextureFormatUsage rhs) {
lhs = lhs | rhs;
return lhs;
}
constexpr bool TextureHasFormatUsage(TextureFormatUsage usage, TextureFormatUsage flag) {
return (static_cast<uint32_t>(usage & flag) == static_cast<uint32_t>(flag));
}
enum class TextureUploadDestination { MipLevels, MipAtlas };
struct GpuTileInfo;
struct RenderTargetFormatInfo {
vk::Format format = vk::Format::eUndefined;
@@ -53,6 +22,7 @@ struct RenderTargetFormatInfo {
struct TextureUploadLayout {
uint32_t tile = 0;
uint32_t pitch = 0;
uint32_t texel_block = 1;
uint64_t slice_stride = 0;
uint64_t source_slice_stride = 0;
uint32_t first_tail_level = 16;
@@ -62,75 +32,22 @@ struct TextureUploadLayout {
TilePaddedSize padded_sizes[16] = {};
};
struct TextureImageCreateParams {
uint32_t fmt = 0;
uint64_t width = 0;
uint64_t height = 0;
uint32_t base_level = 0;
uint64_t levels = 1;
uint32_t depth = 1;
uint64_t type = 0;
uint64_t swizzle = 0;
TextureFormatUsage format_usage = TextureFormatUsage::Sampled;
TextureFormatUsage required_format_usage = TextureFormatUsage::Sampled;
TextureFormatUsage view_usage = TextureFormatUsage::Sampled;
TextureUploadDestination image_layout = TextureUploadDestination::MipLevels;
bool allow_cube_view = false;
bool compatible_format_views = false;
bool storage_swizzle_fallback = false;
const char* owner = nullptr;
};
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s);
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
bool TextureCheckFormat(vk::ImageCreateInfo& image_info);
bool TextureCheckStorageSwizzle(vk::ImageCreateInfo& image_info, vk::ComponentMapping& components);
vk::ImageUsageFlags TextureGetUsage(TextureFormatUsage usage);
vk::ImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage);
vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
uint32_t TextureGetAtlasSliceYStride(vk::Format format, uint32_t mip_height, uint32_t depth,
uint64_t levels);
uint32_t TextureCalcStackedImageHeight(vk::Format format, uint32_t height, uint32_t depth,
uint64_t levels);
uint32_t TextureCalcMipmapAtlasImageHeight(vk::Format format, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels);
bool TextureIs3DTexture(uint64_t type);
bool TextureIsCubeTexture(uint64_t type);
bool TextureIsLayeredTexture(uint64_t type);
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count);
vk::ComponentMapping TextureCreateImage(VulkanImage& image,
const TextureImageCreateParams& params);
void TextureCreateImageViews(VulkanImage& vk_obj,
vk::ComponentMapping components, uint64_t type, uint32_t base_array,
uint32_t base_level, uint32_t level_count, uint32_t depth,
bool allow_cube_view, TextureFormatUsage view_usage);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
uint32_t slice);
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
const std::vector<BufferImageCopy>& regions, uint64_t levels,
uint32_t depth);
std::vector<BufferImageCopy> TextureBuildUploadRegions(const TextureUploadLayout& layout,
vk::Format image_format, uint32_t width,
uint32_t height, uint32_t depth,
uint64_t levels, bool array_texture,
bool volume_texture,
TextureUploadDestination destination);
std::vector<ImageBufferCopy>
TextureBuildDownloadRegions(const std::vector<BufferImageCopy>& upload_regions);
bool TextureBuildGpuTileInfos(uint64_t size, const std::vector<BufferImageCopy>& regions,
std::vector<vk::BufferImageCopy>
TextureBuildImageCopies(const TextureUploadLayout& layout, uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels, bool array_texture,
bool volume_texture);
bool TextureBuildGpuTileInfos(uint64_t size,
const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& infos);
void TextureUploadGuestImage(VulkanImage& vk_obj, const void* src_data,
uint64_t size, const std::vector<BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
uint64_t height, uint32_t depth, uint64_t levels, const char* owner,
vk::ImageLayout dst_layout);
} // namespace Libs::Graphics
+32 -5
View File
@@ -297,8 +297,7 @@ bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
}
bool PageManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
size > ADDRESS_SIZE - vaddr) {
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
return false;
}
const auto end = PageStart(vaddr + size - 1) + PAGE_SIZE;
@@ -363,9 +362,6 @@ bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access)
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
PageWatchMode mode) {
if (g_in_fault_resolution) {
FailFast("page watchers changed during fault resolution");
}
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
Fatal("invalid watcher mode");
}
@@ -500,6 +496,37 @@ PageManager::BackingWrite::~BackingWrite() {
m_manager.EndBackingWrite(m_vaddr, m_size);
}
std::vector<std::unique_ptr<PageManager::BackingWrite>>
PageManager::ReserveBackingWrites(std::span<const RangeSet::Range> ranges) {
if (ranges.empty()) {
Fatal("cannot reserve empty backing-write ranges");
}
std::vector<std::unique_ptr<BackingWrite>> writes;
writes.reserve(ranges.size());
uint64_t begin = 0;
uint64_t end = 0;
for (const auto& range: ranges) {
if (range.address == 0 || range.size == 0 || range.size > UINT64_MAX - range.address ||
range.address + range.size > UINT64_MAX - (PAGE_SIZE - 1)) {
Fatal("invalid backing-write range");
}
const auto page_begin = PageStart(range.address);
const auto page_end = PageStart(range.address + range.size + PAGE_SIZE - 1);
if (begin != 0 && page_begin > end) {
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
begin = 0;
}
if (begin == 0) {
begin = page_begin;
end = page_end;
} else {
end = std::max(end, page_end);
}
}
writes.push_back(std::make_unique<BackingWrite>(*this, begin, end - begin));
return writes;
}
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
if (g_in_fault_resolution) {
FailFast("backing write began during fault resolution");
+5
View File
@@ -2,8 +2,11 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
#include "common/common.h"
#include "graphics/host_gpu/rangeSet.h"
#include <memory>
#include <span>
#include <vector>
namespace Libs::Graphics {
@@ -48,6 +51,8 @@ public:
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
[[nodiscard]] bool HandleWriteRange(uint64_t vaddr, uint64_t size) noexcept;
[[nodiscard]] std::vector<std::unique_ptr<BackingWrite>>
ReserveBackingWrites(std::span<const RangeSet::Range> ranges);
private:
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
@@ -0,0 +1,231 @@
#include "graphics/host_gpu/renderer/blitHelper.h"
#include "common/assert.h"
#include "gpu_blit_shaders/gpu_blit_color_to_ms_depth_spv.h"
#include "gpu_blit_shaders/gpu_blit_fs_triangle_spv.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include <algorithm>
#include <array>
#include <iterator>
namespace Libs::Graphics {
BlitHelper::BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler)
: m_graphics(graphics), m_scheduler(scheduler) {
vk::DescriptorSetLayoutBinding texture_binding {};
texture_binding.binding = 0;
texture_binding.descriptorType = vk::DescriptorType::eSampledImage;
texture_binding.descriptorCount = 1;
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;
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
&m_descriptor_layout),
"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(
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
"create BlitHelper pipeline layout");
m_vertex_shader = CreateShader(GPU_BLIT_FS_TRIANGLE_SPV, std::size(GPU_BLIT_FS_TRIANGLE_SPV));
m_fragment_shader =
CreateShader(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV, std::size(GPU_BLIT_COLOR_TO_MS_DEPTH_SPV));
}
BlitHelper::~BlitHelper() {
for (const auto& pipeline: m_pipelines) {
m_graphics.device.destroyPipeline(pipeline.handle, nullptr);
}
if (m_fragment_shader != nullptr) {
m_graphics.device.destroyShaderModule(m_fragment_shader, nullptr);
}
if (m_vertex_shader != nullptr) {
m_graphics.device.destroyShaderModule(m_vertex_shader, nullptr);
}
if (m_pipeline_layout != nullptr) {
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
}
if (m_descriptor_layout != nullptr) {
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
}
}
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;
RequireVulkanSuccess(m_graphics.device.createShaderModule(&create, nullptr, &module),
"create BlitHelper shader module");
return module;
}
vk::Pipeline BlitHelper::GetPipeline(PipelineKey key) {
const auto cached = std::ranges::find(m_pipelines, key, &Pipeline::key);
if (cached != m_pipelines.end()) {
return cached->handle;
}
const auto samples = vulkan_sample_count(key.samples);
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;
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();
create.pVertexInputState = &vertex_input;
create.pInputAssemblyState = &input_assembly;
create.pViewportState = &viewport;
create.pRasterizationState = &rasterization;
create.pMultisampleState = &multisample;
create.pDepthStencilState = &depth;
create.pColorBlendState = &color_blend;
create.pDynamicState = &dynamic;
create.layout = m_pipeline_layout;
vk::Pipeline pipeline = nullptr;
RequireVulkanSuccess(
m_graphics.device.createGraphicsPipelines(nullptr, 1, &create, nullptr, &pipeline),
"create color-to-MS-depth pipeline");
m_pipelines.push_back({key, pipeline});
return pipeline;
}
void BlitHelper::ReinterpretColorAsMsDepth(Image& source, Image& destination) {
const auto& source_info = source.info;
const auto& destination_info = destination.info;
EXIT_IF(DepthAspectTransferFormat(source_info.pixel_format) != vk::Format::eUndefined ||
DepthAspectTransferFormat(destination_info.pixel_format) == vk::Format::eUndefined ||
source_info.samples != 1 || destination_info.samples <= 1 ||
destination_info.samples > 4 || source.backing.image_type != vk::ImageType::e2D ||
destination.backing.image_type != vk::ImageType::e2D ||
source_info.extent.width != destination_info.extent.width ||
source_info.extent.height != destination_info.extent.height ||
source_info.extent.depth != 1 || destination_info.extent.depth != 1 ||
source.backing.image == nullptr || destination.backing.image == nullptr);
m_scheduler.EndRendering();
ImageViewInfo source_view_info {};
source_view_info.format = source_info.pixel_format;
source_view_info.type = vk::ImageViewType::e2D;
source_view_info.aspect = vk::ImageAspectFlagBits::eColor;
source_view_info.usage = vk::ImageUsageFlagBits::eSampled;
const auto source_view = source.FindView(source_view_info);
ImageViewInfo destination_view_info {};
destination_view_info.format = destination_info.pixel_format;
destination_view_info.type = vk::ImageViewType::e2D;
destination_view_info.aspect = vk::ImageAspectFlagBits::eDepth;
destination_view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
const auto destination_view = destination.FindView(destination_view_info);
auto& command_buffer = m_scheduler.Current();
auto command = command_buffer.Handle();
source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
command);
destination.Transit(ColorToMsDepthLayout,
vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}, 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;
depth_attachment.storeOp = vk::AttachmentStoreOp::eStore;
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;
command.beginRendering(&rendering);
vk::DescriptorImageInfo descriptor_image {};
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;
descriptor_write.pImageInfo = &descriptor_image;
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_pipeline_layout, 0, 1,
&descriptor_write);
command.bindPipeline(vk::PipelineBindPoint::eGraphics,
GetPipeline({destination_info.samples, destination_info.pixel_format}));
const vk::Viewport viewport {0.0f,
0.0f,
static_cast<float>(destination_info.extent.width),
static_cast<float>(destination_info.extent.height),
0.0f,
1.0f};
const vk::Rect2D scissor {{0, 0},
{destination_info.extent.width, destination_info.extent.height}};
command.setViewport(0, 1, &viewport);
command.setScissor(0, 1, &scissor);
command.draw(3, 1, 0, 0);
command.endRendering();
}
} // namespace Libs::Graphics
@@ -0,0 +1,54 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <compare>
#include <vector>
namespace Libs::Graphics {
class CommandScheduler;
class Image;
struct GraphicContext;
class BlitHelper final {
public:
inline static constexpr auto ColorToMsDepthLayout =
vk::ImageLayout::eDepthStencilAttachmentOptimal;
BlitHelper(GraphicContext& graphics, CommandScheduler& scheduler);
~BlitHelper();
KYTY_CLASS_NO_COPY(BlitHelper);
void ReinterpretColorAsMsDepth(Image& source, Image& destination);
private:
struct PipelineKey {
uint32_t samples = 1;
vk::Format format = vk::Format::eUndefined;
auto operator<=>(const PipelineKey&) const = default;
};
struct Pipeline {
PipelineKey key;
vk::Pipeline handle = nullptr;
};
[[nodiscard]] vk::ShaderModule CreateShader(const uint32_t* code, size_t words) const;
[[nodiscard]] vk::Pipeline GetPipeline(PipelineKey key);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
vk::PipelineLayout m_pipeline_layout = nullptr;
vk::ShaderModule m_vertex_shader = nullptr;
vk::ShaderModule m_fragment_shader = nullptr;
std::vector<Pipeline> m_pipelines;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BLITHELPER_H_
File diff suppressed because it is too large Load Diff
+87 -44
View File
@@ -6,44 +6,34 @@
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include <map>
#include <memory>
#include <mutex>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct GraphicContext;
struct VulkanBuffer;
class CommandBuffer;
class CommandScheduler;
class TextureCache;
class ResourceMutex;
struct BufferImageCopySource {
VulkanBuffer* buffer = nullptr;
uint64_t offset = 0;
uint64_t address = 0;
uint64_t size = 0;
bool cpu_current = false;
// True when the guest range was CPU-dirty before coherence resolution. ObtainBufferForImage
// may consume that tracker state while publishing the same bytes to a cached buffer.
bool cpu_dirty = false;
};
struct BufferCacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct BufferBinding {
std::shared_ptr<VulkanBuffer> buffer;
uint64_t offset = 0;
std::vector<uint8_t> host_data;
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t offset = 0;
};
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange& merged,
BufferCacheRange candidate) noexcept;
struct ImageBufferSource {
Buffer* buffer = nullptr;
uint64_t offset = 0;
bool gpu_owned = false;
};
class BufferCache {
public:
@@ -52,7 +42,8 @@ public:
return vaddr & (CACHING_PAGE_SIZE - 1);
}
BufferCache(GraphicContext& graphics, PageManager& page_manager, ResourceMutex& resource_mutex);
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache, ResourceMutex& resource_mutex);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
@@ -62,39 +53,91 @@ public:
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written = false, bool is_read = true,
bool is_formatted = false);
// Emulator-owned, CPU-current scratch only. Guest ranges must use ObtainBuffer so page
// ownership is resolved before any CPU access.
[[nodiscard]] bool UploadHostData(CommandBuffer& command, const void* src, uint64_t size,
uint64_t alignment, VulkanBuffer*& out_buffer,
uint64_t& out_offset, uint64_t& out_range);
[[nodiscard]] std::shared_ptr<VulkanBuffer> ObtainNullBuffer();
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(CommandBuffer* command, uint64_t vaddr, uint64_t size, uint32_t value);
void CopyBuffer(CommandBuffer* command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept;
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; }
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; }
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size,
uint64_t alignment);
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer();
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
[[nodiscard]] std::pair<std::shared_ptr<Buffer>, uint64_t>
ObtainBufferForImageWrite(uint64_t vaddr, uint64_t size);
void DiscardGpuDirtyBytes(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
bool src_gds = false);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
void PublishImageBacking(uint64_t vaddr, uint64_t size);
void InvalidateImageAliases(uint64_t vaddr, uint64_t size);
void BeginBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
void CompleteBackingPublication(uint64_t vaddr, uint64_t size, uint64_t tick);
[[nodiscard]] bool SynchronizeBacking(uint64_t vaddr, uint64_t size);
void PublishImageBuffer(uint64_t vaddr, uint64_t size);
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
void SetTextureCache(TextureCache& texture_cache);
void ResetNullBuffer();
void RunGarbageCollector();
private:
struct CachedBuffer;
struct ReadbackWorker;
friend struct BufferCacheTestAccess;
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::shared_ptr<VulkanBuffer> m_null_buffer;
// TODO: add LRU cache
struct CacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct CachedBuffer;
struct DownloadCopy;
struct DownloadRange;
struct RetiredBuffer;
struct FaultReadback;
struct PendingBackingPublication;
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
}
[[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept;
[[nodiscard]] static std::pair<uint64_t, uint64_t>
DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size);
[[nodiscard]] std::vector<DownloadRange>
RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire);
void RefreshInvalidatedRanges(CommandBuffer& command, CachedBuffer& cached, uint64_t vaddr,
uint64_t size, bool upload);
void DiscardGpuDirtyBytesLocked(uint64_t vaddr, uint64_t size, const char* operation);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
Buffer m_gds_buffer;
Common::Mutex m_mutex;
std::shared_ptr<Buffer> m_null_buffer;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
std::unique_ptr<ReadbackWorker> m_readback;
std::unique_ptr<FaultReadback> m_fault_readback;
RangeSet m_gpu_modified_ranges;
RangeSet m_image_invalidated_ranges;
std::mutex m_publication_mutex;
std::vector<PendingBackingPublication> m_pending_backing_publications;
MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer;
StreamBuffer m_stream_buffer;
StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer;
PageManager& m_page_manager;
TextureCache* m_texture_cache = nullptr;
TextureCache& m_texture_cache;
ResourceMutex& m_resource_mutex;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
};
} // namespace Libs::Graphics
@@ -10,12 +10,9 @@
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
@@ -25,9 +22,11 @@ namespace Libs::Graphics {
static std::atomic<uint32_t> g_render_color_log_count = 0;
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset, uint32_t render_target_slot,
bool ignore_target_mask, bool reuse_existing_render_texture) {
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo& r,
uint32_t render_target_slice_offset,
uint32_t render_target_slot, bool ignore_target_mask,
bool exact_format) {
KYTY_PROFILER_FUNCTION();
const auto& hw = buffer.GetRegisters();
@@ -57,14 +56,17 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
// No color output
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.vulkan_buffer = nullptr;
r.vulkan_view = nullptr;
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.color_clear_value = {};
return;
@@ -176,26 +178,33 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
pitch = width;
}
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
if (tile) {
TileSizeAlign layout {};
bool valid_layout = false;
if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, pitch, levels, rt.attrib3.tile_mode, &layout, nullptr,
nullptr);
height, pitch, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
mip_padded);
valid_layout = layout.size != 0 && layout.align == 65536;
} else {
valid_layout =
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
layout, rt.attrib.num_fragments)
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
levels, layout, nullptr, nullptr);
levels, layout, mip_sizes, mip_padded);
}
if (!valid_layout) {
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
height, pitch, bytes_per_element, levels);
}
size = layout.size;
EXIT_IF(size > UINT32_MAX);
if (levels == 1) {
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (rt.slice.slice_div64_minus1 != 0 &&
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
@@ -204,6 +213,11 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
}
} else {
size = static_cast<uint64_t>(pitch) * height * bytes_per_element * samples;
if (size > UINT32_MAX) {
EXIT("linear render-target slice exceeds the supported layout size\n");
}
mip_sizes[0] = {static_cast<uint32_t>(size), 0, 0, 0, 0, 0};
mip_padded[0] = {pitch, height};
}
if (size == 0 || size > UINT64_MAX / view.image_layers) {
EXIT("render-target memory footprint is invalid\n");
@@ -213,119 +227,69 @@ void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
EXIT("render-target backing range is invalid\n");
}
auto video_image = Presentation::DisplayBufferFind(rt.base.addr, true);
if (video_image.image != nullptr &&
!IsSupportedDisplayRenderTargetTileMode(rt.attrib3.tile_mode)) {
EXIT("unsupported display render-target tile mode: tile=%u expected=%u addr=0x%010" PRIx64
" backing_size=0x%016" PRIx64 " video_size=0x%016" PRIx64 "\n",
rt.attrib3.tile_mode, Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget),
rt.base.addr, backing_size, video_image.size);
}
bool render_to_texture = view.base_layer != 0 || video_image.image == nullptr;
if (!render_to_texture && (levels != 1 || rt.view.current_mip_level != 0)) {
EXIT("mipmapped display render targets are unsupported\n");
}
const vk::Extent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
std::max(height >> rt.view.current_mip_level, 1u)};
auto decision_log_id = g_render_color_log_count.fetch_add(1);
if (decision_log_id < 128 || !render_to_texture) {
if (decision_log_id < 128) {
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32
" samples=%u tile=%s target=%s video_size=0x%016" PRIx64 " video_pitch=%" PRIu64 "\n",
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
rt_slot, rt.base.addr, backing_size, width, height, rt.view.current_mip_level,
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear",
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
video_image.pitch);
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
}
if (render_to_texture) {
(void)reuse_existing_render_texture;
RenderTargetInfo target {};
target.address = rt.base.addr;
target.size = backing_size;
target.format = target_format.format;
target.width = width;
target.height = height;
target.pitch = pitch;
target.bytes_per_element = target_format.bytes_per_element;
target.tile_mode = rt.attrib3.tile_mode;
target.levels = levels;
target.layers = view.image_layers;
target.samples = samples;
auto& texture_cache = GetRenderContext().GetTextureCache();
auto& buffer_vulkan = texture_cache.FindRenderTarget(buffer, target);
r.type = RenderColorType::RenderTexture;
r.base_addr = rt.base.addr;
r.vulkan_buffer = &buffer_vulkan;
r.vulkan_view = texture_cache.GetRenderTargetAttachmentView(
buffer_vulkan, target.format, rt.view.current_mip_level, view.base_layer,
view.layer_count);
r.format = target.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 = buffer_vulkan.initial_clear_pending;
r.color_clear_value = {};
} else {
if (samples != 1) {
EXIT("multisampled display render targets are unsupported\n");
}
const auto layout = static_cast<Prospero::ChannelLayout>(rt.info.format);
const auto type = static_cast<Prospero::ChannelType>(rt.info.channel_type);
const auto order = static_cast<Prospero::ChannelOrder>(rt.info.channel_order);
bool supported_display_format =
(layout == Prospero::ChannelLayout::k8_8_8_8 &&
(type == Prospero::ChannelType::kSrgb || type == Prospero::ChannelType::kUNorm) &&
(order == Prospero::ChannelOrder::kStandard ||
order == Prospero::ChannelOrder::kAlt)) ||
(layout == Prospero::ChannelLayout::k10_10_10_2 &&
type == Prospero::ChannelType::kUNorm &&
(order == Prospero::ChannelOrder::kStandard ||
order == Prospero::ChannelOrder::kAlt)) ||
(layout == Prospero::ChannelLayout::k16_16_16_16 &&
type == Prospero::ChannelType::kFloat &&
(order == Prospero::ChannelOrder::kStandard || order == Prospero::ChannelOrder::kAlt));
EXIT_NOT_IMPLEMENTED(!supported_display_format);
// Display buffer
if (video_image.size != size) {
LOGF("RenderColorTarget: display buffer size differs from render target span, "
"video_size=0x%016" PRIx64 " render_size=0x%016" PRIx64 "\n",
video_image.size, size);
}
EXIT_NOT_IMPLEMENTED(video_image.size < size);
EXIT_NOT_IMPLEMENTED(video_image.pitch != pitch);
r.type = RenderColorType::DisplayBuffer;
r.base_addr = rt.base.addr;
r.vulkan_buffer = video_image.image;
r.vulkan_view = video_image.image->image_view[VulkanImage::VIEW_DEFAULT];
r.format = video_image.image->format;
r.extent = video_image.image->extent;
r.base_mip_level = 0;
r.buffer_size = video_image.size;
r.samples = 1;
r.export_mapping = target_format.export_mapping;
}
}
void MarkRenderTargetGpuWritten(const RenderColorInfo& target) {
const bool with_color = target.vulkan_buffer != nullptr;
if (with_color) {
if (target.type == RenderColorType::RenderTexture ||
target.type == RenderColorType::DisplayBuffer) {
GetRenderContext().GetTextureCache().MarkGpuWritten(*target.vulkan_buffer);
} else {
EXIT("unknown writable render-color resource type\n");
}
TextureCache::ImageDesc desc {};
desc.type = TextureCache::BindingType::RenderTarget;
desc.info.data = {rt.base.addr, backing_size};
desc.info.pixel_format = target_format.format;
desc.info.guest_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {levels, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes_per_element;
desc.info.samples = samples;
desc.info.tile_mode = rt.attrib3.tile_mode;
for (uint32_t level = 0; level < levels; level++) {
const auto level_offset =
mip_sizes[level].src_size != 0 ? mip_sizes[level].src_offset : mip_sizes[level].offset;
const auto level_size =
static_cast<uint64_t>(mip_sizes[level].src_size != 0 ? mip_sizes[level].src_size
: mip_sizes[level].size) *
view.image_layers;
desc.info.mip_layout[level] = {
level_offset,
level_size,
mip_padded[level].width,
mip_padded[level].height,
};
}
desc.view_info.format = target_format.format;
desc.view_info.type =
view.layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray;
desc.view_info.aspect = vk::ImageAspectFlagBits::eColor;
desc.view_info.base_level = rt.view.current_mip_level;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
auto& texture_cache = m_context.GetTextureCache();
r.desc = std::move(desc);
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;
r.color_clear_value = {};
BindRenderTarget(r.image_id);
}
} // namespace Libs::Graphics
@@ -3,6 +3,7 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
@@ -10,18 +11,17 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
struct VulkanImage;
enum class RenderColorType {
NoColorOutput,
DisplayBuffer,
RenderTexture,
};
struct RenderColorInfo {
RenderColorType type = RenderColorType::NoColorOutput;
VulkanImage* vulkan_buffer = nullptr;
vk::ImageView vulkan_view = nullptr;
RenderColorType type = RenderColorType::NoColorOutput;
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;
@@ -35,13 +35,6 @@ struct RenderColorInfo {
vk::ClearColorValue color_clear_value {};
};
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderColorInfo& r,
uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false,
bool reuse_existing_render_texture = false);
void MarkRenderTargetGpuWritten(const RenderColorInfo& target);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COLORRENDERTARGET_H_
@@ -1,18 +1,161 @@
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include <algorithm>
namespace Libs::Graphics {
static thread_local CommandScheduler* g_deferred_callback_scheduler = nullptr;
void CommandSlot::Reset() {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
}
CommandScheduler::CommandPool::~CommandPool() {
Destroy();
}
void CommandScheduler::CommandPool::Create(GraphicContext& graphics) {
EXIT_IF(m_pool != nullptr || m_graphics != nullptr ||
graphics.queue_family == static_cast<uint32_t>(-1));
m_graphics = &graphics;
vk::CommandPoolCreateInfo create {};
create.sType = vk::StructureType::eCommandPoolCreateInfo;
create.queueFamilyIndex = graphics.queue_family;
create.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&create, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* CommandScheduler::CommandPool::CreateSlot() {
EXIT_IF(m_graphics == nullptr);
auto& graphics = *m_graphics;
vk::CommandBufferAllocateInfo allocate {};
allocate.sType = vk::StructureType::eCommandBufferAllocateInfo;
allocate.commandPool = m_pool;
allocate.level = vk::CommandBufferLevel::ePrimary;
allocate.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
EXIT_IF(graphics.device.allocateCommandBuffers(&allocate, &buffer) != vk::Result::eSuccess);
vk::FenceCreateInfo fence_create {};
fence_create.sType = vk::StructureType::eFenceCreateInfo;
fence_create.flags = vk::FenceCreateFlagBits::eSignaled;
vk::Fence fence = nullptr;
if (graphics.device.createFence(&fence_create, nullptr, &fence) != vk::Result::eSuccess) {
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
EXIT("failed to create command-buffer fence\n");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
}
CommandSlot* CommandScheduler::CommandPool::Allocate(GraphicContext& graphics) {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
Create(graphics);
}
EXIT_IF(m_graphics != &graphics);
auto found = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
auto* slot = found != m_slots.end() ? &*found : CreateSlot();
slot->busy = true;
slot->Reset();
return slot;
}
void CommandScheduler::CommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
EXIT_IF(m_graphics == nullptr);
for (const auto& slot: m_slots) {
m_graphics->device.destroyFence(slot.fence, nullptr);
}
m_graphics->device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
m_graphics = nullptr;
}
bool CommandScheduler::InDeferredOperation() noexcept {
return g_deferred_callback_scheduler != nullptr;
}
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
: m_master(graphics), m_context(context), m_graphics(graphics),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {}
CommandScheduler::~CommandScheduler() {
Shutdown();
}
void CommandScheduler::Shutdown() {
{
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Closed) {
return;
}
if (g_deferred_callback_scheduler == this) {
EXIT_IF(m_operation_state == OperationState::Open);
// A priority callback cannot join its own runner, while a normal callback can be
// executing inside the shutdown owner's final PopPendingOperations. The owning
// thread will finish shutdown after this callback returns.
return;
}
if (m_operation_state == OperationState::Draining) {
m_operation_available.wait(
lock, [this] { return m_operation_state == OperationState::Closed; });
return;
}
m_operation_state = OperationState::Draining;
}
if (Active() && m_recording) {
Finish();
}
DrainPriorityOperations();
m_priority_thread.request_stop();
m_operation_available.notify_all();
if (m_priority_thread.joinable()) {
m_priority_thread.join();
}
{
std::lock_guard lock(m_operation_mutex);
EXIT_IF(!m_pending_operations.empty() || !m_priority_operations.empty() ||
m_priority_active);
m_operation_state = OperationState::Closed;
}
m_operation_available.notify_all();
}
void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config,
HW::Shader& shaders) {
{
std::lock_guard lock(m_operation_mutex);
EXIT_IF(m_operation_state != OperationState::Open);
}
m_registers = &registers;
m_user_config = &user_config;
m_shaders = &shaders;
if (!Active()) {
for (auto& buffer: m_buffers) {
buffer = std::make_unique<RenderCommandBuffer>();
buffer = std::make_unique<RenderCommandBuffer>(*this);
}
m_current = 0;
}
@@ -24,37 +167,201 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
}
}
void CommandScheduler::BeginRendering(const RenderState& state) {
Current().BeginRendering(state);
}
void CommandScheduler::EndRendering() {
if (Active() && m_recording) {
Current().EndRendering();
}
}
void CommandScheduler::Flush() {
SubmitCurrent();
SubmitInfo submit;
Flush(submit);
}
void CommandScheduler::Flush(SubmitInfo& submit) {
SubmitCurrent(submit);
BeginNext();
}
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
auto& submitted = SubmitCurrent();
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
BeginNext();
return submitted;
}
void CommandScheduler::Finish() {
CheckActive();
const auto tick = CurrentTick();
if (m_recording) {
SubmitCurrent();
SubmitInfo submit;
SubmitCurrent(submit);
}
for (auto& buffer: m_buffers) {
buffer->WaitForFenceAndReset();
}
m_master.Wait(tick);
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
}
void CommandScheduler::FinishCurrent() {
auto& submitted = SubmitCurrent();
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
PopPendingOperations();
submitted.Begin();
m_recording = true;
}
void CommandScheduler::Wait(uint64_t tick) {
CheckActive();
EXIT_IF(tick > CurrentTick());
if (tick >= CurrentTick()) {
// A stream-buffer wrap can wait while a draw is being prepared through a reference to
// Current(). Recycle the same command object so that reference remains valid.
FinishCurrent();
return;
}
m_master.Wait(tick);
PopPendingOperations();
}
void CommandScheduler::PopPendingOperations() {
m_master.Refresh();
for (;;) {
Common::UniqueFunction<void> callback;
{
std::lock_guard lock(m_operation_mutex);
if (m_pending_operations.empty() ||
!m_master.IsFree(m_pending_operations.front().tick)) {
return;
}
callback = std::move(m_pending_operations.front().callback);
m_pending_operations.pop();
}
RunOperation(std::move(callback));
}
}
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
CheckActive();
EXIT_IF(!operation);
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Open) {
m_pending_operations.push({std::move(operation), CurrentTick()});
return;
}
if (g_deferred_callback_scheduler == this) {
lock.unlock();
operation();
return;
}
m_operation_available.wait(lock,
[this] { return m_operation_state == OperationState::Closed; });
lock.unlock();
operation();
}
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
CheckActive();
EXIT_IF(!operation);
std::unique_lock lock(m_operation_mutex);
if (m_operation_state == OperationState::Open) {
m_priority_operations.push({std::move(operation), CurrentTick()});
lock.unlock();
m_operation_available.notify_one();
return;
}
if (g_deferred_callback_scheduler == this) {
lock.unlock();
operation();
return;
}
m_operation_available.wait(lock,
[this] { return m_operation_state == OperationState::Closed; });
lock.unlock();
operation();
}
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
while (!stop.stop_requested()) {
PendingOperation operation;
{
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, &stop] {
return stop.stop_requested() || !m_priority_operations.empty();
});
if (stop.stop_requested()) {
return;
}
operation = std::move(m_priority_operations.front());
m_priority_operations.pop();
m_priority_active = true;
m_priority_active_tick = operation.tick;
}
m_master.Wait(operation.tick);
if (!stop.stop_requested()) {
RunOperation(std::move(operation.callback));
}
{
std::lock_guard lock(m_operation_mutex);
m_priority_active = false;
m_priority_active_tick = 0;
}
m_operation_available.notify_all();
}
}
void CommandScheduler::DrainPriorityOperations() {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(
lock, [this] { return m_priority_operations.empty() && !m_priority_active; });
}
void CommandScheduler::WaitPriorityOperations(uint64_t tick) {
EXIT_IF(g_deferred_callback_scheduler == this);
std::unique_lock lock(m_operation_mutex);
m_operation_available.wait(lock, [this, tick] {
const bool active_before_or_at =
m_priority_active && m_priority_active_tick <= tick;
const bool queued_before_or_at =
!m_priority_operations.empty() && m_priority_operations.front().tick <= tick;
return !active_before_or_at && !queued_before_or_at;
});
}
void CommandScheduler::RunOperation(Common::UniqueFunction<void>&& operation) {
auto* previous = g_deferred_callback_scheduler;
g_deferred_callback_scheduler = this;
operation();
g_deferred_callback_scheduler = previous;
}
bool CommandScheduler::IsFree(uint64_t tick) {
if (m_master.IsFree(tick)) {
return true;
}
m_master.Refresh();
return m_master.IsFree(tick);
}
CommandSlot* CommandScheduler::AllocateCommandBuffer() {
return m_command_pool.Allocate(m_graphics);
}
uint64_t CommandScheduler::NextSubmitSequence() noexcept {
return m_submit_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
}
void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || m_current >= BufferCount);
}
@@ -70,12 +377,14 @@ void CommandScheduler::BindCurrent() const {
Current().Bind(*m_registers, *m_user_config, *m_shaders);
}
CommandBuffer& CommandScheduler::SubmitCurrent() {
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
CheckActive();
EXIT_IF(!m_recording);
auto& submitted = Current();
submitted.End();
submitted.Execute();
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_recording = false;
return submitted;
}
@@ -84,6 +393,7 @@ void CommandScheduler::BeginNext() {
EXIT_IF(m_recording);
m_current = (m_current + 1) % BufferCount;
Current().WaitForFenceAndReset();
PopPendingOperations();
BindCurrent();
Current().Begin();
m_recording = true;
@@ -2,42 +2,124 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
#include "common/common.h"
#include "common/uniqueFunction.h"
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "graphics/host_gpu/renderer/render.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
namespace Libs::Graphics {
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
void Reset();
};
class CommandScheduler {
public:
static constexpr int BufferCount = 8;
CommandScheduler() = default;
~CommandScheduler() = default;
CommandScheduler(RenderContext& context, GraphicContext& graphics);
~CommandScheduler();
KYTY_CLASS_NO_COPY(CommandScheduler);
void Begin(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders);
void BeginRendering(const RenderState& state);
void EndRendering();
void Flush();
void Flush(SubmitInfo& submit);
CommandBuffer& FlushAndGetSubmitted();
void Finish();
void FinishCurrent();
// Deferred callbacks can observe an externally owned drain, but cannot initiate shutdown:
// the priority runner cannot join itself.
void Shutdown();
void Wait(uint64_t tick);
void PopPendingOperations();
void DrainPriorityOperations();
void WaitPriorityOperations(uint64_t tick);
void DeferOperation(Common::UniqueFunction<void>&& operation);
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] static bool InDeferredOperation() noexcept;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] bool Active() const noexcept { return m_current >= 0; }
void CheckActive() const;
RenderCommandBuffer& Current() const;
[[nodiscard]] uint64_t CurrentTick() const noexcept { return m_master.CurrentTick(); }
[[nodiscard]] bool IsFree(uint64_t tick);
[[nodiscard]] RenderContext& Context() const noexcept { return m_context; }
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
void BindCurrent() const;
CommandBuffer& SubmitCurrent();
void BeginNext();
class CommandPool {
public:
CommandPool() = default;
~CommandPool();
KYTY_CLASS_NO_COPY(CommandPool);
CommandSlot* Allocate(GraphicContext& graphics);
private:
void Create(GraphicContext& graphics);
CommandSlot* CreateSlot();
void Destroy();
GraphicContext* m_graphics = nullptr;
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
};
enum class OperationState { Open, Draining, Closed };
struct PendingOperation {
Common::UniqueFunction<void> callback;
uint64_t tick = 0;
};
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext();
void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer;
};
} // namespace Libs::Graphics
+87 -291
View File
@@ -9,63 +9,17 @@
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cstring>
#include <deque>
#include <memory>
namespace Libs::Graphics {
static std::atomic<uint64_t> g_command_buffer_submit_seq = 0;
static void ResetNativeCommandBuffer(vk::CommandBuffer buffer) {
EXIT_IF(buffer == nullptr);
const auto result = buffer.reset(vk::CommandBufferResetFlagBits::eReleaseResources);
if (result != vk::Result::eSuccess) {
EXIT("failed to reset Vulkan command buffer: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
}
}
struct CommandSlot {
Common::Mutex* pool_mutex = nullptr;
uint32_t id = 0;
vk::CommandBuffer buffer = nullptr;
vk::Fence fence = nullptr;
bool busy = false;
};
class ThreadCommandPool {
public:
ThreadCommandPool() = default;
KYTY_CLASS_NO_COPY(ThreadCommandPool);
CommandSlot* Allocate();
void Destroy();
private:
void Create();
CommandSlot* CreateSlot();
Common::Mutex m_mutex;
vk::CommandPool m_pool = nullptr;
std::deque<CommandSlot> m_slots;
};
static RenderContext* g_render_ctx = nullptr;
static thread_local ThreadCommandPool g_command_pool;
RenderContext& GetRenderContext() noexcept {
return *g_render_ctx;
}
FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) {
@@ -88,93 +42,14 @@ void FenceResourceRetainer::ReleaseAfterFence() noexcept {
m_resources.clear();
}
void GraphicsRenderInit(GraphicContext& graphics) {
g_render_ctx = new RenderContext(graphics);
}
void GraphicsRenderReleaseThreadCommandPool() {
g_command_pool.Destroy();
}
CommandBuffer::CommandBuffer()
: m_graphics(GetRenderContext().GetGraphics()), m_slot(g_command_pool.Allocate()),
m_host_stream(m_graphics) {}
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
m_slot(scheduler.AllocateCommandBuffer()) {}
CommandBuffer::~CommandBuffer() {
Release();
}
void ThreadCommandPool::Create() {
auto& graphics = GetRenderContext().GetGraphics();
EXIT_IF(m_pool != nullptr || graphics.queue_family == static_cast<uint32_t>(-1));
vk::CommandPoolCreateInfo pool_info {};
pool_info.sType = vk::StructureType::eCommandPoolCreateInfo;
pool_info.pNext = nullptr;
pool_info.queueFamilyIndex = graphics.queue_family;
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
const auto result = graphics.device.createCommandPool(&pool_info, nullptr, &m_pool);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_pool == nullptr);
}
CommandSlot* ThreadCommandPool::CreateSlot() {
auto& graphics = GetRenderContext().GetGraphics();
vk::CommandBufferAllocateInfo alloc_info {};
alloc_info.sType = vk::StructureType::eCommandBufferAllocateInfo;
alloc_info.commandPool = m_pool;
alloc_info.level = vk::CommandBufferLevel::ePrimary;
alloc_info.commandBufferCount = 1;
vk::CommandBuffer buffer = nullptr;
if (graphics.device.allocateCommandBuffers(&alloc_info, &buffer) != vk::Result::eSuccess) {
EXIT("Can't allocate command buffers");
}
vk::FenceCreateInfo fence_info {};
fence_info.sType = vk::StructureType::eFenceCreateInfo;
fence_info.flags = vk::FenceCreateFlagBits::eSignaled;
vk::Fence fence = nullptr;
if (graphics.device.createFence(&fence_info, nullptr, &fence) != vk::Result::eSuccess) {
graphics.device.freeCommandBuffers(m_pool, 1, &buffer);
EXIT("Can't create fence");
}
auto& slot = m_slots.emplace_back();
slot.pool_mutex = &m_mutex;
slot.id = static_cast<uint32_t>(m_slots.size() - 1);
slot.buffer = buffer;
slot.fence = fence;
return &slot;
}
CommandSlot* ThreadCommandPool::Allocate() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
Create();
}
auto it = std::ranges::find_if(m_slots, [](const auto& slot) { return !slot.busy; });
auto* slot = it != m_slots.end() ? &*it : CreateSlot();
slot->busy = true;
ResetNativeCommandBuffer(slot->buffer);
return slot;
}
void ThreadCommandPool::Destroy() {
Common::LockGuard lock(m_mutex);
if (m_pool == nullptr) {
return;
}
EXIT_IF(std::ranges::any_of(m_slots, [](const auto& slot) { return slot.busy; }));
auto& graphics = GetRenderContext().GetGraphics();
for (const auto& slot: m_slots) {
graphics.device.destroyFence(slot.fence, nullptr);
}
graphics.device.destroyCommandPool(m_pool, nullptr);
m_slots.clear();
m_pool = nullptr;
}
bool CommandBuffer::IsInvalid() const {
return m_slot == nullptr;
}
@@ -194,10 +69,8 @@ void CommandBuffer::Release() {
WaitForFence();
m_host_stream.Release();
m_slot->busy = false;
ResetNativeCommandBuffer(m_slot->buffer);
m_slot->Reset();
ReleaseResourcesAfterFence();
m_slot = nullptr;
@@ -224,12 +97,13 @@ void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
void CommandBuffer::RecycleDescriptorsAfterFence() {
for (auto* set: m_descriptor_sets_after_fence) {
GetRenderContext().GetDescriptorCache().Recycle(*set);
m_context.GetDescriptorCache().Recycle(*set);
}
m_descriptor_sets_after_fence.clear();
}
void CommandBuffer::Begin() const {
EXIT_IF(m_rendering);
auto buffer = Handle();
vk::CommandBufferBeginInfo begin_info {};
@@ -244,6 +118,7 @@ void CommandBuffer::Begin() const {
}
void CommandBuffer::End() const {
EndRendering();
auto buffer = Handle();
auto result = buffer.end();
@@ -262,39 +137,34 @@ void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0,
m_debug_arg4 = arg4;
}
void CommandBuffer::Execute() {
Submit(nullptr, {}, nullptr);
}
void CommandBuffer::ExecuteWithSemaphore(vk::Semaphore wait_semaphore,
vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore) {
EXIT_IF(wait_semaphore == nullptr || signal_semaphore == nullptr);
Submit(wait_semaphore, wait_stage, signal_semaphore);
}
void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore) {
void CommandBuffer::Execute(const SubmitInfo& submit) {
EXIT_IF(IsInvalid());
EXIT_IF(m_execute);
EXIT_IF(submit.num_wait_semaphores > SubmitInfo::MaxSemaphores ||
submit.num_signal_semaphores > SubmitInfo::MaxSemaphores);
const bool has_wait = wait_semaphore != nullptr;
const bool has_signal = signal_semaphore != nullptr;
auto buffer = Handle();
auto fence = m_slot->fence;
auto buffer = Handle();
auto fence = m_slot->fence;
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 = nullptr;
submit_info.waitSemaphoreCount = has_wait ? 1u : 0u;
submit_info.pWaitSemaphores = has_wait ? &wait_semaphore : nullptr;
submit_info.pWaitDstStageMask = has_wait ? &wait_stage : nullptr;
submit_info.pNext = &timeline_info;
submit_info.waitSemaphoreCount = submit.num_wait_semaphores;
submit_info.pWaitSemaphores = submit.wait_semaphores.data();
submit_info.pWaitDstStageMask = submit.wait_stages.data();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &buffer;
submit_info.signalSemaphoreCount = has_signal ? 1u : 0u;
submit_info.pSignalSemaphores = has_signal ? &signal_semaphore : nullptr;
submit_info.signalSemaphoreCount = submit.num_signal_semaphores;
submit_info.pSignalSemaphores = submit.signal_semaphores.data();
auto& graphics = GetRenderContext().GetGraphics();
auto& graphics = m_graphics;
EXIT_IF(graphics.queue == nullptr);
auto result = graphics.device.resetFences(1, &fence);
@@ -305,16 +175,16 @@ void CommandBuffer::Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
if (Config::GraphicsDebugDumpEnabled()) {
LOGF("vkQueueSubmit begin: slot=%u wait_semaphore=%p signal_semaphore=%p"
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_slot->id, static_cast<void*>(wait_semaphore), static_cast<void*>(signal_semaphore),
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
LOGF("vkQueueSubmit begin: slot=%u waits=%u signals=%u debug_op=%u debug_submit=%" PRIu64
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
m_slot->id, submit.num_wait_semaphores, submit.num_signal_semaphores, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
{
Common::LockGuard lock(graphics.queue_mutex);
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
m_submit_seq = m_scheduler.NextSubmitSequence();
result = graphics.queue.submit(1, &submit_info, fence);
}
@@ -340,7 +210,7 @@ void CommandBuffer::WaitForFenceOnly() {
if (!m_execute || m_fence_waited) {
return;
}
auto device = GetRenderContext().GetGraphics().device;
auto device = m_graphics.device;
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) {
LOGF("vkWaitForFences failed: %s (%d), slot=%u submit_seq=%" PRIu64
@@ -364,12 +234,10 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
m_execute = false;
m_fence_waited = false;
if (reset_recording) {
ResetNativeCommandBuffer(m_slot->buffer);
Common::LockGuard lock(*m_slot->pool_mutex);
m_slot->Reset();
}
}
if (reset_recording) {
m_host_stream.Reset();
}
if (was_executed) {
ReleaseResourcesAfterFence();
}
@@ -383,142 +251,70 @@ void CommandBuffer::ReleaseResourcesAfterFence() {
void CommandBuffer::DeleteBuffersAfterFence() {
for (const auto& buffer: m_retired_buffers) {
GetRenderContext().GetGraphics().DeleteBuffer(*buffer);
m_graphics.DeleteBuffer(*buffer);
}
m_retired_buffers.clear();
}
void CommandBuffer::BeginRenderPass(VulkanFramebuffer& framebuffer, RenderColorInfo* colors,
uint32_t requested_color_count, RenderDepthInfo& depth) const {
auto buffer = Handle();
EXIT_IF(colors == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
uint32_t color_count = 0;
for (uint32_t i = 0; i < requested_color_count; i++) {
if (colors[i].vulkan_buffer == nullptr) {
break;
}
color_count++;
void CommandBuffer::BeginRendering(const RenderState& state) const {
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.num_color_attachments > RENDER_COLOR_ATTACHMENTS_MAX);
if (m_rendering && m_render_state == state) {
return;
}
bool with_color = (color_count != 0);
EndRendering();
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color);
vk::ClearValue clears[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
clears[i].color = colors[i].color_clear_value;
}
clears[color_count].depthStencil = {depth.depth_clear_value, depth.stencil_clear_value};
vk::Extent2D extent = (with_color ? colors[0].extent : depth.vulkan_buffer->extent);
vk::RenderPassBeginInfo render_pass_info {};
render_pass_info.sType = vk::StructureType::eRenderPassBeginInfo;
render_pass_info.pNext = nullptr;
render_pass_info.renderPass = framebuffer.render_pass;
render_pass_info.framebuffer = framebuffer.framebuffer;
render_pass_info.renderArea.offset = {0, 0};
render_pass_info.renderArea.extent = extent;
render_pass_info.clearValueCount = color_count + (with_depth ? 1u : 0u);
render_pass_info.pClearValues = clears;
for (uint32_t i = 0; i < color_count; i++) {
const auto color_initial_layout = framebuffer.color_layout[i];
if (colors[i].vulkan_buffer->layout != color_initial_layout) {
if (graphics_debug_dump_enabled()) {
LOGF("BeginRenderPass: color%u initial barrier image=%p mem=%" PRIu64 " %s -> %s\n",
i, VulkanHandleToPointer(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
VulkanToString(colors[i].vulkan_buffer->layout).c_str(),
VulkanToString(color_initial_layout).c_str());
}
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = {};
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eColorAttachmentWrite;
image_memory_barrier.oldLayout = colors[i].vulkan_buffer->layout;
image_memory_barrier.newLayout = color_initial_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = colors[i].vulkan_buffer->image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = colors[i].vulkan_buffer->layers;
buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
colors[i].vulkan_buffer->layout = image_memory_barrier.newLayout;
} else if (graphics_debug_dump_enabled()) {
LOGF("BeginRenderPass: color%u initial image=%p mem=%" PRIu64 " layout=%s\n", i,
VulkanHandleToPointer(colors[i].vulkan_buffer->image),
colors[i].vulkan_buffer->memory.unique_id,
VulkanToString(colors[i].vulkan_buffer->layout).c_str());
}
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 = attachment.is_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
colors[i].storeOp = vk::AttachmentStoreOp::eStore;
colors[i].clearValue.color.uint32 = attachment.clear_value;
}
const auto depth_layout =
with_depth ? framebuffer.depth_layout : vk::ImageLayout::eDepthStencilAttachmentOptimal;
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 = depth_stencil.depth_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
depth.storeOp = vk::AttachmentStoreOp::eStore;
depth.clearValue.depthStencil.depth = std::bit_cast<float>(depth_stencil.clear_value[0]);
if (with_depth && depth.vulkan_buffer->layout != depth_layout) {
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
image_memory_barrier.dstAccessMask =
(depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal
? vk::AccessFlagBits::eMemoryRead
: vk::AccessFlagBits::eMemoryWrite);
image_memory_barrier.oldLayout = depth.vulkan_buffer->layout;
image_memory_barrier.newLayout = depth_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = depth.vulkan_buffer->image;
image_memory_barrier.subresourceRange.aspectMask =
ImageViewOps::DepthAspectMask(depth.vulkan_buffer->format);
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = 1;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = depth.vulkan_buffer->layers;
vk::RenderingAttachmentInfo stencil {};
stencil.sType = vk::StructureType::eRenderingAttachmentInfo;
stencil.imageView = depth_stencil.image_view;
stencil.imageLayout = depth_stencil.image_layout;
stencil.loadOp = depth_stencil.stencil_clear ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad;
stencil.storeOp = vk::AttachmentStoreOp::eStore;
stencil.clearValue.depthStencil.stencil = depth_stencil.clear_value[1];
buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
depth.vulkan_buffer->layout = image_memory_barrier.newLayout;
}
buffer.beginRenderPass(&render_pass_info, vk::SubpassContents::eInline);
for (uint32_t i = 0; i < color_count; i++) {
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
if (colors[i].vulkan_buffer->type == VulkanImageType::RenderTexture) {
static_cast<RenderTextureVulkanImage*>(colors[i].vulkan_buffer)->initial_clear_pending =
false;
}
}
if (with_depth) {
depth.vulkan_buffer->initial_depth_clear_pending = false;
depth.vulkan_buffer->initial_stencil_clear_pending = false;
}
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;
rendering.pColorAttachments = colors.data();
rendering.pDepthAttachment = depth_stencil.has_depth ? &depth : nullptr;
rendering.pStencilAttachment = depth_stencil.has_stencil ? &stencil : nullptr;
Handle().beginRendering(rendering);
m_render_state = state;
m_rendering = true;
}
void CommandBuffer::EndRenderPass() const {
auto buffer = Handle();
buffer.endRenderPass();
void CommandBuffer::EndRendering() const {
if (!m_rendering) {
return;
}
Handle().endRendering();
m_rendering = false;
m_render_state = {};
}
} // namespace Libs::Graphics
@@ -13,12 +13,10 @@
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
@@ -88,7 +86,8 @@ static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderDepthInfo& r) {
void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderDepthInfo& r) {
KYTY_PROFILER_FUNCTION();
(void)submit_id;
const auto& hw = buffer.GetRegisters();
@@ -282,18 +281,9 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
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;
auto& cache = GetRenderContext().GetTextureCache();
if (has_htile) {
cache.RegisterMeta(r.htile_buffer_vaddr, r.htile_buffer_size, view.image_layers);
}
if (has_htile && rc.depth_clear_enable && !cache.ClearMeta(z.htile_data_base_addr)) {
DepthFatal("failed to acquire HTile metadata for a depth clear");
}
const bool meta_clear =
has_htile && cache.IsMetaCleared(z.htile_data_base_addr, z.depth_view.slice_start);
r.depth_clear_enable = rc.depth_clear_enable;
r.depth_meta_clear_enable = meta_clear;
r.depth_load_clear_enable = r.depth_clear_enable || r.depth_meta_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;
@@ -343,55 +333,94 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
r.vaddr[1] = r.stencil_buffer_vaddr;
r.size[1] = r.stencil_buffer_size;
}
DepthTargetInfo info {};
info.address = r.depth_buffer_vaddr;
info.size = r.depth_buffer_size;
info.stencil_address = r.stencil_buffer_vaddr;
info.stencil_size = r.stencil_buffer_size;
info.htile_address = r.htile_buffer_vaddr;
info.htile_size = r.htile_buffer_size;
info.format = r.format;
info.guest_format = guest_format;
info.width = width;
info.height = height;
info.pitch = pitch;
info.bytes_per_element = bytes;
info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
info.layers = view.image_layers;
info.samples = samples;
info.depth_load_clear = r.depth_load_clear_enable;
info.depth_access = depth_active;
info.stencil_load_clear = rc.stencil_clear_enable;
info.stencil_access =
r.stencil_clear_enable ||
(r.stencil_test_enable &&
(stencil_face_accesses_attachment(r.stencil_static_front, r.stencil_dynamic_front) ||
stencil_face_accesses_attachment(r.stencil_static_back, r.stencil_dynamic_back)));
info.stencil_htile_compressed =
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.guest_format = guest_format;
desc.info.type = Prospero::ImageType::kColor2D;
desc.info.extent = {width, height, 1};
desc.info.resources = {1, view.image_layers};
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::GpuEnumValue(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.metadata.kind = has_htile ? ImageMetadataKind::Htile : ImageMetadataKind::None;
desc.info.metadata.stencil_compressed =
has_stencil && has_htile && !z.stencil_info.tile_stencil_disable;
r.vulkan_buffer = &cache.FindDepthTarget(buffer, info);
r.vulkan_view =
cache.GetDepthTargetAttachmentView(*r.vulkan_buffer, view.base_layer, view.layer_count);
if (r.vulkan_buffer->initial_depth_clear_pending) {
r.depth_load_clear_enable = true;
r.depth_clear_value = 0.0f;
}
if (r.vulkan_buffer->initial_stencil_clear_pending) {
r.stencil_clear_enable = true;
r.stencil_clear_value = 0;
}
if (meta_clear && !cache.TouchMeta(z.htile_data_base_addr, z.depth_view.slice_start, false)) {
DepthFatal("failed to consume HTile clear state");
}
desc.view_info.format = r.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.base_level = 0;
desc.view_info.level_count = 1;
desc.view_info.base_layer = view.base_layer;
desc.view_info.layer_count = view.layer_count;
desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
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);
}
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target) {
const bool with_depth =
target.format != vk::Format::eUndefined && target.vulkan_buffer != nullptr;
if (with_depth && !depth_attachment_read_only(target)) {
GetRenderContext().GetTextureCache().MarkGpuWritten(*target.vulkan_buffer);
vk::ImageAspectFlags RenderDepthInfo::AttachmentWriteAspects() const {
if (format == vk::Format::eUndefined) {
return {};
}
const auto available = ImageViewOps::DepthAspectMask(format);
vk::ImageAspectFlags writes {};
if ((available & vk::ImageAspectFlagBits::eDepth) &&
(depth_load_clear_enable || (depth_test_enable && depth_write_enable))) {
writes |= vk::ImageAspectFlagBits::eDepth;
}
if (!(available & vk::ImageAspectFlagBits::eStencil)) {
return writes;
}
const auto face_writes = [&](const PipelineStencilStaticState& state,
const PipelineStencilDynamicState& dynamic) {
if (dynamic.writeMask == 0) {
return false;
}
bool can_pass = state.compareOp != vk::CompareOp::eNever;
bool can_fail = state.compareOp != vk::CompareOp::eAlways;
if (dynamic.compareMask == 0) {
switch (state.compareOp) {
case vk::CompareOp::eEqual:
case vk::CompareOp::eLessOrEqual:
case vk::CompareOp::eGreaterOrEqual:
case vk::CompareOp::eAlways:
can_pass = true;
can_fail = false;
break;
case vk::CompareOp::eNever:
case vk::CompareOp::eLess:
case vk::CompareOp::eGreater:
case vk::CompareOp::eNotEqual:
can_pass = false;
can_fail = true;
break;
default: break;
}
}
const bool depth_pass = !depth_test_enable || depth_compare_op != vk::CompareOp::eNever;
const bool depth_fail = depth_test_enable && depth_compare_op != vk::CompareOp::eAlways;
return (can_fail && state.failOp != vk::StencilOp::eKeep) ||
(can_pass && depth_pass && state.passOp != vk::StencilOp::eKeep) ||
(can_pass && depth_fail && state.depthFailOp != vk::StencilOp::eKeep);
};
if (stencil_clear_enable ||
(stencil_test_enable &&
(face_writes(stencil_static_front, stencil_dynamic_front) ||
face_writes(stencil_static_back, stencil_dynamic_back)))) {
writes |= vk::ImageAspectFlagBits::eStencil;
}
return writes;
}
} // namespace Libs::Graphics
@@ -2,7 +2,9 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
@@ -10,7 +12,6 @@
namespace Libs::Graphics {
class RenderCommandBuffer;
struct DepthStencilVulkanImage;
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
bool acceleration_disabled) {
@@ -18,6 +19,7 @@ inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stenc
}
struct RenderDepthInfo {
TextureCache::ImageDesc desc;
vk::Format format = vk::Format::eUndefined;
uint32_t width = 0;
uint32_t height = 0;
@@ -49,29 +51,46 @@ struct RenderDepthInfo {
PipelineStencilStaticState stencil_static_back;
PipelineStencilDynamicState stencil_dynamic_front;
PipelineStencilDynamicState stencil_dynamic_back;
DepthStencilVulkanImage* vulkan_buffer = nullptr;
vk::ImageView vulkan_view = nullptr;
uint64_t vaddr[3] = {};
uint64_t size[3] = {};
int vaddr_num = 0;
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) {
const bool stencil_write =
depth.stencil_test_enable &&
(depth.stencil_dynamic_front.writeMask != 0 || depth.stencil_dynamic_back.writeMask != 0);
return !depth.depth_load_clear_enable && !depth.stencil_clear_enable &&
!depth.depth_write_enable && !stencil_write;
return !depth.AttachmentWriteAspects();
}
inline vk::ImageLayout depth_attachment_layout(const RenderDepthInfo& depth) {
return depth_attachment_read_only(depth) ? vk::ImageLayout::eDepthStencilReadOnlyOptimal
: vk::ImageLayout::eDepthStencilAttachmentOptimal;
const auto available = ImageViewOps::DepthAspectMask(depth.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);
const bool stencil_write = static_cast<bool>(writes & vk::ImageAspectFlagBits::eStencil);
if (!has_stencil) {
return depth_write ? vk::ImageLayout::eDepthAttachmentOptimal
: vk::ImageLayout::eDepthReadOnlyOptimal;
}
if (!has_depth) {
return stencil_write ? vk::ImageLayout::eStencilAttachmentOptimal
: vk::ImageLayout::eStencilReadOnlyOptimal;
}
if (depth_write && stencil_write) {
return vk::ImageLayout::eDepthStencilAttachmentOptimal;
}
if (depth_write) {
return vk::ImageLayout::eDepthAttachmentStencilReadOnlyOptimal;
}
if (stencil_write) {
return vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal;
}
return vk::ImageLayout::eDepthStencilReadOnlyOptimal;
}
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, RenderDepthInfo& r);
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DEPTHRENDERTARGET_H_
@@ -2,7 +2,6 @@
#include "common/assert.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include <array>
@@ -76,31 +75,35 @@ std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
return key;
}
vk::ImageLayout SampledLayout(const VulkanImage& image) {
switch (image.layout) {
case vk::ImageLayout::eTransferDstOptimal:
case vk::ImageLayout::eTransferSrcOptimal:
case vk::ImageLayout::eColorAttachmentOptimal: return vk::ImageLayout::eGeneral;
default: return image.layout;
}
}
vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
EXIT_IF(view.buffer == nullptr || view.buffer->buffer == nullptr);
return {view.buffer->buffer, view.offset, view.range};
}
vk::DescriptorImageInfo MakeDescriptorImageInfo(const DescriptorCache::TextureBinding& texture,
vk::ImageLayout layout) {
EXIT_IF(texture.image == nullptr || texture.view < 0 || texture.view >= VulkanImage::VIEW_MAX);
const auto view = texture.image_view != nullptr ? texture.image_view
: texture.image->image_view[texture.view];
EXIT_IF(view == nullptr);
return {nullptr, view, layout};
EXIT_IF(view.buffer == nullptr);
return {view.buffer, view.offset, view.range};
}
} // namespace
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture) {
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, texture.image_view, texture.layout};
}
DescriptorCache::~DescriptorCache() {
for (auto& [layout, sets]: m_free_sets_by_layout) {
(void)layout;
for (auto* set: sets) {
delete set;
}
}
for (const auto& [key, layout]: m_descriptor_set_layouts) {
(void)key;
m_graphics.device.destroyDescriptorSetLayout(layout, nullptr);
}
for (const auto& pool: m_pools) {
m_graphics.device.destroyDescriptorPool(pool.pool, nullptr);
}
}
vk::DescriptorSetLayout
DescriptorCache::GetDescriptorSetLayoutInternal(Stage stage,
const ShaderRecompiler::IR::Program& program) {
@@ -256,13 +259,9 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
}
break;
default: {
const auto layout = IsStorageImage(binding.kind) ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eUndefined;
for (const auto resource: binding.resources) {
const auto& texture = data.images.at(resource);
image_infos.push_back(MakeDescriptorImageInfo(
texture,
IsSampledImage(binding.kind) ? SampledLayout(*texture.image) : layout));
image_infos.push_back(MakeImageInfo(texture));
}
break;
}
@@ -6,6 +6,7 @@
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shaderBindings.h"
@@ -18,11 +19,14 @@
namespace Libs::Graphics {
namespace ShaderRecompiler::IR {
struct DescriptorValue;
struct ImageResource;
struct Program;
struct ResourceSnapshot;
}
} // namespace ShaderRecompiler::IR
class CommandBuffer;
struct DescriptorCacheTestAccess;
struct ShaderStageRuntime;
struct VulkanDescriptorSet {
@@ -32,11 +36,10 @@ struct VulkanDescriptorSet {
};
struct BufferView {
std::shared_ptr<VulkanBuffer> owner;
VulkanBuffer* buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
std::vector<uint8_t> host_data;
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE;
};
class DescriptorCache {
@@ -44,19 +47,10 @@ public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
VulkanImage* image = nullptr;
int view = VulkanImage::VIEW_DEFAULT;
vk::ImageView image_view = nullptr;
std::shared_ptr<void> owner;
};
enum class TextureVariant : int {
Float2D = 0,
Uint2D,
FloatArray,
UintArray,
Float3D,
Uint3D,
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
};
struct NativeDescriptors {
@@ -70,20 +64,20 @@ public:
};
struct PreparedBindings {
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
NativeDescriptors resources;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage;
Stage stage = Stage::Unknown;
bool committed = false;
NativeDescriptors resources;
std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage;
Stage stage = Stage::Unknown;
bool committed = false;
};
explicit DescriptorCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~DescriptorCache() { KYTY_NOT_IMPLEMENTED; }
~DescriptorCache();
KYTY_CLASS_NO_COPY(DescriptorCache);
vk::DescriptorSetLayout GetDescriptorSetLayout(Stage stage,
@@ -93,11 +87,14 @@ public:
const NativeDescriptors& descriptors);
private:
friend struct DescriptorCacheTestAccess;
struct Pool {
vk::DescriptorPool pool = nullptr;
int next_free_pool = -1;
};
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
void CreatePool();
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
vk::DescriptorSetLayout
@@ -112,15 +109,6 @@ private:
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
};
[[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void ActivateImageWrites(std::span<DescriptorCache::PreparedBindings*> bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
File diff suppressed because it is too large Load Diff
+3 -12
View File
@@ -2,6 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shaderBindings.h"
@@ -12,14 +13,6 @@
namespace Libs::Graphics {
struct VulkanImage;
enum class VulkanImageType;
enum class ImageWriteActivation { Unsupported, Implicit, Explicit };
[[nodiscard]] ImageWriteActivation
ClassifyImageWriteActivation(VulkanImageType type) noexcept;
template <typename T>
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
static_assert(std::is_trivially_copyable_v<T>);
@@ -41,13 +34,11 @@ ResolveTargetTextureView(const ShaderRecompiler::IR::ImageResource& resource,
Prospero::ImageType type, uint32_t base_layer, uint32_t image_layers);
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
const VulkanImage& image);
const Image& image);
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor);
[[nodiscard]] bool
IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, const VulkanImage& image);
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
const ShaderTextureResource& descriptor, const Image& image);
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor, uint64_t size);
@@ -1,50 +0,0 @@
#include "graphics/host_gpu/renderer/dummyTextureCache.h"
#include "common/assert.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/transfer.h"
#include <algorithm>
namespace Libs::Graphics {
namespace {
[[nodiscard]] constexpr size_t DummyTextureIndex(bool uint_format, bool image_3d) noexcept {
return (image_3d ? 2u : 0u) + (uint_format ? 1u : 0u);
}
} // namespace
DummyTextureCache::~DummyTextureCache() {
Common::LockGuard lock(m_mutex);
const auto populated = [](const auto& slots) {
return std::ranges::any_of(slots, [](const auto& slot) { return slot.image != nullptr; });
};
if (!populated(m_sampled) && !populated(m_storage)) {
return;
}
Transfer::WaitForQueueIdle();
const auto destroy = [](auto& slots) {
for (auto& slot: slots) {
if (slot.image != nullptr) {
ImageOps::Destroy(*slot.image);
slot.image = nullptr;
}
}
};
destroy(m_sampled);
destroy(m_storage);
}
VulkanImage& DummyTextureCache::Get(Usage usage, bool uint_format, bool image_3d) {
Common::LockGuard lock(m_mutex);
auto& slots = usage == Usage::Storage ? m_storage : m_sampled;
auto& slot = slots[DummyTextureIndex(uint_format, image_3d)];
if (slot.image == nullptr) {
slot.image = ImageOps::CreateDummyTexture(uint_format, image_3d, usage == Usage::Storage);
}
return *slot.image;
}
} // namespace Libs::Graphics
@@ -1,35 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
#include "common/abi.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include <array>
#include <cstdint>
namespace Libs::Graphics {
class DummyTextureCache final {
public:
enum class Usage : uint8_t { Sampled, Storage };
DummyTextureCache() = default;
~DummyTextureCache();
KYTY_CLASS_NO_COPY(DummyTextureCache);
[[nodiscard]] VulkanImage& Get(Usage usage, bool uint_format, bool image_3d);
private:
struct Slot {
GpuTextureVulkanImage* image = nullptr;
};
Common::Mutex m_mutex;
std::array<Slot, 4> m_sampled {};
std::array<Slot, 4> m_storage {};
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DUMMYTEXTURECACHE_H_
@@ -1,357 +0,0 @@
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <algorithm>
#include <atomic>
namespace Libs::Graphics {
VulkanFramebuffer* FramebufferCache::CreateFramebuffer(RenderColorInfo* colors,
uint32_t requested_color_count,
RenderDepthInfo& depth) {
KYTY_PROFILER_FUNCTION();
Common::LockGuard lock(m_mutex);
EXIT_IF(colors == nullptr);
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
bool with_depth = (depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
bool with_color[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
VulkanImage* first_color = nullptr;
vk::Extent2D first_color_extent = {};
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < requested_color_count; i++) {
with_color[i] = (colors[i].vulkan_buffer != nullptr);
if (!with_color[i]) {
break;
}
if (first_color == nullptr) {
first_color = colors[i].vulkan_buffer;
first_color_extent = colors[i].extent;
attachment_samples = colors[i].samples;
} else if (colors[i].extent.width != first_color_extent.width ||
colors[i].extent.height != first_color_extent.height) {
LOGF("Framebuffer: temporary: dropping mismatched MRT%u attachment color0=%ux%u "
"color%u=%ux%u\n",
i, first_color_extent.width, first_color_extent.height, i, colors[i].extent.width,
colors[i].extent.height);
with_color[i] = false;
break;
}
if (colors[i].samples != attachment_samples ||
colors[i].vulkan_buffer->samples != colors[i].samples) {
EXIT("Framebuffer: mismatched color attachment samples at slot %u, expected=%u "
"requested=%u image=%u\n",
i, attachment_samples, colors[i].samples, colors[i].vulkan_buffer->samples);
}
color_count++;
}
if (with_depth) {
if (depth.samples != depth.vulkan_buffer->samples) {
EXIT("Framebuffer: depth attachment sample identity mismatch, requested=%u image=%u\n",
depth.samples, depth.vulkan_buffer->samples);
}
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT(
"Framebuffer: mixed color/depth sample counts are unsupported, color=%u depth=%u\n",
attachment_samples, depth.samples);
}
}
if (!with_depth && color_count == 0) {
LOGF("Framebuffer: warning: no color or depth attachment\n");
return nullptr;
}
if (vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("Framebuffer: invalid attachment sample count %u\n", attachment_samples);
}
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (auto& layout: color_layout) {
layout = RENDER_COLOR_IMAGE_LAYOUT;
}
auto depth_layout = (with_depth ? depth_attachment_layout(depth)
: vk::ImageLayout::eDepthStencilAttachmentOptimal);
auto depth_read_only =
(with_depth && depth_layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal);
if (with_depth && first_color != nullptr &&
(first_color_extent.width != depth.vulkan_buffer->extent.width ||
first_color_extent.height != depth.vulkan_buffer->extent.height)) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF("Framebuffer: temporary: dropping mismatched PS5 depth attachment color=%ux%u "
"depth=%ux%u format=%s\n",
first_color_extent.width, first_color_extent.height,
depth.vulkan_buffer->extent.width, depth.vulkan_buffer->extent.height,
VulkanToString(depth.format).c_str());
}
depth.format = vk::Format::eUndefined;
depth.vulkan_buffer = nullptr;
depth.vulkan_view = nullptr;
depth.depth_test_enable = false;
depth.depth_write_enable = false;
depth.depth_bounds_test_enable = false;
depth.stencil_test_enable = false;
depth.depth_clear_enable = false;
depth.depth_load_clear_enable = false;
depth.stencil_clear_enable = false;
with_depth = false;
depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
depth_read_only = false;
}
for (auto& f: m_framebuffers) {
bool color_match = (f.framebuffer != nullptr);
for (uint32_t i = 0; color_match && i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
const uint64_t image_id =
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
color_match =
color_match && f.image_id[i] == image_id &&
f.color_view[i] ==
(i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr) &&
f.color_clear_enable[i] ==
(i < color_count && with_color[i] && colors[i].color_clear_enable) &&
f.color_layout[i] == color_layout[i];
}
if (color_match && f.depth_id == (with_depth ? depth.vulkan_buffer->memory.unique_id : 0) &&
f.depth_view == (with_depth ? depth.vulkan_view : nullptr) &&
f.depth_clear_enable == depth.depth_load_clear_enable &&
f.stencil_clear_enable == depth.stencil_clear_enable &&
f.depth_read_only == depth_read_only) {
return f.framebuffer;
}
}
EXIT_NOT_IMPLEMENTED(with_depth && first_color != nullptr &&
(first_color_extent.width != depth.vulkan_buffer->extent.width ||
first_color_extent.height != depth.vulkan_buffer->extent.height));
if (first_color == nullptr) {
first_color_extent = depth.vulkan_buffer->extent;
}
auto* framebuffer = new VulkanFramebuffer;
framebuffer->render_pass = nullptr;
framebuffer->framebuffer = nullptr;
framebuffer->samples = attachment_samples;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
framebuffer->color_layout[i] = color_layout[i];
}
framebuffer->depth_layout = depth_layout;
vk::AttachmentDescription attachments[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
attachments[i].flags = {};
attachments[i].format = colors[i].format;
attachments[i].samples = vulkan_sample_count(attachment_samples);
attachments[i].loadOp = (colors[i].color_clear_enable ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
attachments[i].storeOp = vk::AttachmentStoreOp::eStore;
attachments[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
attachments[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
attachments[i].initialLayout = color_layout[i];
attachments[i].finalLayout = RENDER_COLOR_IMAGE_LAYOUT;
}
const uint32_t depth_attachment = color_count;
attachments[depth_attachment].flags = {};
attachments[depth_attachment].format = depth.format;
attachments[depth_attachment].samples = vulkan_sample_count(attachment_samples);
attachments[depth_attachment].loadOp =
(depth.depth_load_clear_enable ? vk::AttachmentLoadOp::eClear
: vk::AttachmentLoadOp::eLoad);
attachments[depth_attachment].storeOp = vk::AttachmentStoreOp::eStore;
attachments[depth_attachment].stencilLoadOp =
(depth.stencil_clear_enable ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad);
attachments[depth_attachment].stencilStoreOp = vk::AttachmentStoreOp::eStore;
attachments[depth_attachment].initialLayout = depth_layout;
attachments[depth_attachment].finalLayout = depth_layout;
vk::AttachmentReference color_attachment_ref[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
color_attachment_ref[i].attachment = (i < color_count ? i : VK_ATTACHMENT_UNUSED);
color_attachment_ref[i].layout = RENDER_COLOR_IMAGE_LAYOUT;
}
vk::AttachmentReference depth_attachment_ref {};
depth_attachment_ref.attachment = depth_attachment;
depth_attachment_ref.layout = depth_layout;
vk::SubpassDescription subpass {};
subpass.flags = {};
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = nullptr;
subpass.colorAttachmentCount = color_count;
subpass.pColorAttachments = (color_count > 0 ? color_attachment_ref : nullptr);
subpass.pResolveAttachments = nullptr;
subpass.pDepthStencilAttachment = (with_depth ? &depth_attachment_ref : nullptr);
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = nullptr;
const auto attachment_stage_mask =
static_cast<vk::PipelineStageFlags>(vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests);
const auto attachment_access_mask = static_cast<vk::AccessFlags>(
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eDepthStencilAttachmentWrite);
vk::SubpassDependency dependencies[2] = {};
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eAllCommands;
dependencies[0].dstStageMask = attachment_stage_mask;
dependencies[0].srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
dependencies[0].dstAccessMask = attachment_access_mask;
dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = attachment_stage_mask;
dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eAllCommands;
dependencies[1].srcAccessMask = attachment_access_mask;
dependencies[1].dstAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;
vk::RenderPassCreateInfo render_pass_info {};
render_pass_info.sType = vk::StructureType::eRenderPassCreateInfo;
render_pass_info.pNext = nullptr;
render_pass_info.flags = {};
render_pass_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
render_pass_info.pAttachments = attachments;
render_pass_info.subpassCount = 1;
render_pass_info.pSubpasses = &subpass;
render_pass_info.dependencyCount = 2;
render_pass_info.pDependencies = dependencies;
auto result =
m_graphics.device.createRenderPass(&render_pass_info, nullptr, &framebuffer->render_pass);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
vk::Format color_formats[RENDER_COLOR_ATTACHMENTS_MAX] = {};
for (uint32_t i = 0; i < color_count; i++) {
color_formats[i] = colors[i].format;
}
framebuffer->render_pass_id = render_pass_compat_id(
color_count, color_formats, with_depth, depth.format, depth_layout, attachment_samples);
EXIT_NOT_IMPLEMENTED(framebuffer->render_pass == nullptr);
vk::ImageView views[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
for (uint32_t i = 0; i < color_count; i++) {
if (colors[i].vulkan_view == nullptr) {
EXIT("Framebuffer: color attachment view is missing at slot %u\n", i);
}
views[i] = colors[i].vulkan_view;
}
if (with_depth) {
if (depth.vulkan_view == nullptr) {
EXIT("Framebuffer: depth attachment view is missing\n");
}
views[color_count] = depth.vulkan_view;
}
vk::FramebufferCreateInfo framebuffer_info {};
framebuffer_info.sType = vk::StructureType::eFramebufferCreateInfo;
framebuffer_info.pNext = nullptr;
framebuffer_info.flags = {};
framebuffer_info.renderPass = framebuffer->render_pass;
framebuffer_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
framebuffer_info.pAttachments = views;
framebuffer_info.width = first_color_extent.width;
framebuffer_info.height = first_color_extent.height;
framebuffer_info.layers = 1;
result =
m_graphics.device.createFramebuffer(&framebuffer_info, nullptr, &framebuffer->framebuffer);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
EXIT_NOT_IMPLEMENTED(framebuffer->framebuffer == nullptr);
Framebuffer fnew;
fnew.framebuffer = framebuffer;
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
fnew.image_id[i] =
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
fnew.color_view[i] = (i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr);
fnew.color_clear_enable[i] =
(i < color_count && with_color[i] && colors[i].color_clear_enable);
fnew.color_layout[i] = color_layout[i];
}
fnew.depth_id = (with_depth ? depth.vulkan_buffer->memory.unique_id : 0);
fnew.depth_view = (with_depth ? depth.vulkan_view : nullptr);
fnew.depth_clear_enable = depth.depth_load_clear_enable;
fnew.stencil_clear_enable = depth.stencil_clear_enable;
fnew.depth_read_only = depth_read_only;
bool updated = false;
for (auto& f: m_framebuffers) {
if (f.framebuffer == nullptr) {
f = fnew;
updated = true;
break;
}
}
if (!updated) {
m_framebuffers.push_back(fnew);
}
return framebuffer;
}
void FramebufferCache::FreeFramebufferByColor(VulkanImage& image) {
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
bool uses_image = false;
for (auto image_id: f.image_id) {
if (image_id == image.memory.unique_id) {
uses_image = true;
break;
}
}
if (f.framebuffer != nullptr && uses_image) {
m_graphics.device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
m_graphics.device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
void FramebufferCache::FreeFramebufferByDepth(DepthStencilVulkanImage& image) {
Common::LockGuard lock(m_mutex);
for (auto& f: m_framebuffers) {
if (f.framebuffer != nullptr && f.depth_id == image.memory.unique_id) {
m_graphics.device.destroyFramebuffer(f.framebuffer->framebuffer, nullptr);
m_graphics.device.destroyRenderPass(f.framebuffer->render_pass, nullptr);
delete f.framebuffer;
f.framebuffer = nullptr;
}
}
}
} // namespace Libs::Graphics
@@ -1,85 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <vector>
namespace Libs::Graphics {
struct RenderColorInfo;
struct RenderDepthInfo;
static constexpr vk::ImageLayout RENDER_COLOR_IMAGE_LAYOUT = vk::ImageLayout::eGeneral;
struct VulkanFramebuffer {
vk::RenderPass render_pass = nullptr;
uint64_t render_pass_id = 0;
vk::Framebuffer framebuffer = nullptr;
uint32_t samples = 1;
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageLayout depth_layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
};
inline uint64_t render_pass_compat_id(uint32_t color_count, const vk::Format* color_formats,
bool with_depth, vk::Format depth_format,
vk::ImageLayout depth_layout, uint32_t samples) {
uint64_t id = 0xcbf29ce484222325ull;
auto mix = [&id](uint64_t v) {
id ^= v;
id *= 0x100000001b3ull;
};
mix(color_count);
for (uint32_t i = 0; i < color_count; i++) {
mix(static_cast<uint32_t>(color_formats[i]));
}
mix(with_depth ? 1u : 0u);
mix(static_cast<uint32_t>(depth_format));
mix(static_cast<uint32_t>(depth_layout));
mix(samples);
return id;
}
class FramebufferCache {
public:
explicit FramebufferCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~FramebufferCache() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(FramebufferCache);
VulkanFramebuffer* CreateFramebuffer(RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo& depth);
void FreeFramebufferByColor(VulkanImage& image);
void FreeFramebufferByDepth(DepthStencilVulkanImage& image);
private:
struct Framebuffer {
VulkanFramebuffer* framebuffer = nullptr;
uint64_t image_id[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageView color_view[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint64_t depth_id = 0;
vk::ImageView depth_view = nullptr;
bool color_clear_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
vk::ImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
bool depth_clear_enable = false;
bool stencil_clear_enable = false;
bool depth_read_only = false;
};
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::vector<Framebuffer> m_framebuffers;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
@@ -1,67 +0,0 @@
#include "graphics/host_gpu/renderer/gdsBuffer.h"
#include "common/assert.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
namespace Libs::Graphics {
void GdsBuffer::Init() {
if (m_buffer == nullptr) {
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer;
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent |
vk::MemoryPropertyFlagBits::eHostCached;
m_graphics.CreateBuffer(DW_SIZE * 4, *m_buffer);
}
}
void GdsBuffer::Clear(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value) {
Common::LockGuard lock(m_mutex);
Init();
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_num > DW_SIZE);
void* data = nullptr;
m_graphics.MapMemory(m_buffer->memory, data);
for (uint32_t i = 0; i < dw_num; i++) {
static_cast<uint32_t*>(data)[dw_offset + i] = clear_value;
}
m_graphics.UnmapMemory(m_buffer->memory);
}
void GdsBuffer::Read(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
EXIT_IF(dst == nullptr);
Common::LockGuard lock(m_mutex);
Init();
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
EXIT_NOT_IMPLEMENTED(dw_offset + dw_size > DW_SIZE);
void* data = nullptr;
m_graphics.MapMemory(m_buffer->memory, data);
for (uint32_t i = 0; i < dw_size; i++) {
dst[i] = static_cast<uint32_t*>(data)[dw_offset + i];
}
m_graphics.UnmapMemory(m_buffer->memory);
}
VulkanBuffer& GdsBuffer::GetBuffer() {
Common::LockGuard lock(m_mutex);
Init();
return *m_buffer;
}
} // namespace Libs::Graphics
@@ -1,39 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include <memory>
namespace Libs::Graphics {
class GdsBuffer {
public:
explicit GdsBuffer(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~GdsBuffer() { KYTY_NOT_IMPLEMENTED; }
KYTY_CLASS_NO_COPY(GdsBuffer);
void Clear(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
void Read(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
VulkanBuffer& GetBuffer();
private:
static constexpr uint64_t DW_SIZE = 0x3000;
void Init();
GraphicContext& m_graphics;
Common::Mutex m_mutex;
std::unique_ptr<VulkanBuffer> m_buffer;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
@@ -3,17 +3,14 @@
#include "common/assert.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics)
: m_page_manager(FaultThunk, this), m_buffer_cache(graphics, m_page_manager, m_resource_mutex),
m_texture_cache(graphics, m_page_manager, m_buffer_cache, m_resource_mutex) {
m_buffer_cache.SetTextureCache(m_texture_cache);
}
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_page_manager(FaultThunk, this),
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
GpuResourceManager::~GpuResourceManager() = default;
@@ -24,6 +21,15 @@ bool GpuResourceManager::FaultThunk(void* context, PageFaultAccess access, uint6
bool GpuResourceManager::InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept {
// Let the authoritative image materialize first. A clean overlapping buffer marks a write
// fault CPU-dirty when it begins ownership transfer; doing that before image preflight would
// make the image appear to race a real CPU write. Completion and release retain buffer-first
// ordering so its pending fault is gone before TextureCache publishes the downloaded backing.
if (phase == PageFaultPhase::Invalidate) {
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
}
const bool buffer_handled = m_buffer_cache.InvalidateMemory(access, vaddr, size, phase);
const bool image_handled = m_texture_cache.InvalidateMemory(access, vaddr, size, phase);
return buffer_handled || image_handled;
@@ -33,19 +39,23 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
if (!m_page_manager.IsMapped(fault_vaddr, 1)) {
return false;
}
if (LabelInCallback()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU label callback, "
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported guest-memory fault from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
cp->BeginReadbackTransaction();
bool handled = false;
bool handled = false;
const auto resolve = [this, access, fault_vaddr, &handled](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
(void)m_buffer_cache.SynchronizeBacking(fault_vaddr, 1);
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handled = m_page_manager.HandleFault(access, fault_vaddr);
}
cp->EndReadbackTransaction();
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return handled;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
@@ -53,36 +63,36 @@ bool GpuResourceManager::HandleFault(PageFaultAccess access, uint64_t fault_vadd
" access=%u\n",
fault_vaddr, static_cast<uint32_t>(access));
}
// Stop command-processor jobs before taking the shared cache transaction. External readback
// workers inherit this paused state and therefore never form resource -> submission lock
// inversion.
GraphicsRunSubmissionLock submissions;
ResourceMutex::FaultScope fault(m_resource_mutex);
return m_page_manager.HandleFault(access, fault_vaddr);
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
return handled;
}
void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
if (!m_page_manager.HasAnyMapping(vaddr, size)) {
return;
}
if (LabelInCallback()) {
EXIT("unsupported host write from an asynchronous GPU label callback, addr=0x%016" PRIx64
if (CommandScheduler::InDeferredOperation()) {
EXIT("unsupported host write from an asynchronous GPU completion, addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto handle_range = [this, vaddr, size]() {
const auto handle_range = [this, vaddr, size] {
if (!m_page_manager.HandleWriteRange(vaddr, size)) {
EXIT("failed to prepare host write, addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
};
if (auto* cp = GraphicsRunCurrentCommandProcessor(); cp != nullptr) {
cp->BeginReadbackTransaction();
const auto resolve = [this, &handle_range](CommandProcessor& cp) {
cp.BeginReadbackTransaction();
{
ResourceMutex::FaultScope fault(m_resource_mutex);
handle_range();
}
cp->EndReadbackTransaction();
cp.EndReadbackTransaction();
};
if (auto* cp = Gpu::CurrentCommandProcessor(); cp != nullptr) {
resolve(*cp);
return;
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
@@ -90,9 +100,8 @@ void GpuResourceManager::PrepareHostWrite(uint64_t vaddr, uint64_t size) {
" size=0x%016" PRIx64 "\n",
vaddr, size);
}
GraphicsRunSubmissionLock submissions;
ResourceMutex::FaultScope fault(m_resource_mutex);
handle_range();
EXIT_IF(m_gpu == nullptr);
m_gpu->SendCommandSyncWithProcessor(resolve);
}
bool GpuResourceManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
@@ -107,27 +116,26 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess ac
if (!IsMapped(vaddr, size)) {
EXIT("cannot unmap an unmapped GPU resource range\n");
}
m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access);
const auto unmap = [this, vaddr, size, access] {
m_texture_cache.UnmapMemory(vaddr, size);
m_buffer_cache.UnmapMemory(vaddr, size);
m_page_manager.OnGpuUnmap(vaddr, size, access);
};
if (m_gpu == nullptr) {
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("cannot synchronously unmap from a resource transaction\n");
}
unmap();
return;
}
Gpu::SubmissionLock submissions(*m_gpu);
m_gpu->SendCommandSync(unmap);
}
void GpuResourceManager::FillBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
uint32_t value) {
if (command.IsInvalid()) {
EXIT("cannot fill a buffer without a valid render command context\n");
}
Common::LockGuard lock(GetRenderContext().GetMutex());
m_buffer_cache.FillBuffer(&command, vaddr, size, value);
}
void GpuResourceManager::CopyBuffer(CommandBuffer& command, uint64_t dst_vaddr, uint64_t src_vaddr,
uint64_t size) {
if (command.IsInvalid()) {
EXIT("cannot copy a buffer without a valid render command context\n");
}
Common::LockGuard lock(GetRenderContext().GetMutex());
m_buffer_cache.CopyBuffer(&command, dst_vaddr, src_vaddr, size);
void GpuResourceManager::RunGarbageCollector() {
m_texture_cache.ProcessDownloadImages();
m_texture_cache.RunGarbageCollector();
m_buffer_cache.RunGarbageCollector();
}
} // namespace Libs::Graphics
@@ -12,24 +12,25 @@
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
class Gpu;
class GpuResourceManager {
public:
explicit GpuResourceManager(GraphicContext& graphics);
GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler);
~GpuResourceManager();
KYTY_CLASS_NO_COPY(GpuResourceManager);
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
void MapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void UnmapMemory(uint64_t vaddr, uint64_t size, GpuAccess access);
void FillBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size, uint32_t value);
void CopyBuffer(CommandBuffer& command, uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size);
void RunGarbageCollector();
private:
static bool FaultThunk(void* context, PageFaultAccess access, uint64_t vaddr, uint64_t size,
@@ -41,6 +42,7 @@ private:
ResourceMutex m_resource_mutex;
BufferCache m_buffer_cache;
TextureCache m_texture_cache;
Gpu* m_gpu = nullptr;
};
} // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
+141 -121
View File
@@ -2,49 +2,93 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include <algorithm>
#include <cstddef>
#include <compare>
#include <limits>
#include <mutex>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
struct RenderTextureVulkanImage;
struct VideoOutVulkanImage;
struct VulkanImage;
class Buffer;
class CommandScheduler;
struct ImageTestAccess;
struct Image final: ImageInfo {
Image& operator=(const ImageInfo& value) {
if (IsCpuDirty()) {
EXIT("dirty sampled image cannot be reassigned\n");
}
static_cast<ImageInfo&>(*this) = value;
m_track_begin = address;
m_track_end = address + size;
m_maybe_cpu_hash_valid = false;
return *this;
struct ImageId {
uint32_t index = std::numeric_limits<uint32_t>::max();
uint32_t generation = 0;
[[nodiscard]] explicit operator bool() const noexcept {
return index != std::numeric_limits<uint32_t>::max();
}
auto operator<=>(const ImageId&) const = default;
};
struct CachedImageView {
ImageViewInfo info;
vk::ImageView view = nullptr;
};
struct ImageViewCache {
ImageViewCache() = default;
KYTY_CLASS_NO_COPY(ImageViewCache);
std::mutex mutex;
std::vector<CachedImageView> views;
};
struct ImageUsage {
bool texture = false;
bool storage = false;
bool render_target = false;
bool depth_target = false;
bool video_out = false;
};
struct ImageBinding {
bool is_bound = false;
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
};
class Image final {
public:
Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageInfo& info);
~Image();
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,
vk::PipelineStageFlags2 destination_stage,
std::optional<ImageSubresourceRange> range);
void Transit(vk::ImageLayout destination_layout, vk::AccessFlags2 destination_access,
std::optional<ImageSubresourceRange> range, vk::CommandBuffer command_buffer);
void Upload(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size);
void Download(std::span<const vk::BufferImageCopy> copies, vk::Buffer buffer,
uint64_t offset, uint64_t size);
void CopyImage(Image& source);
void Resolve(Image& source, const ImageSubresourceRange& source_range,
const ImageSubresourceRange& destination_range);
void CopyImageWithBuffer(Image& source, Buffer& buffer);
void CopyMip(Image& source, uint32_t mip, uint32_t layer);
void InvalidateCpuWrite(uint64_t vaddr, uint64_t size) {
if (ImageRangeOverlaps(address, this->size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_track_begin = m_track_end;
} else if (ImagePageRangesOverlap(address, this->size, vaddr, size)) {
constexpr uint64_t page_mask = 4096 - 1;
if (vaddr + size <= address) {
const auto next_page = (address + page_mask) & ~page_mask;
m_track_begin = std::min(m_track_end, std::max(m_track_begin, next_page));
} else if (vaddr >= address + this->size) {
const auto page = (address + this->size) & ~page_mask;
m_track_end = std::max(m_track_begin, std::min(m_track_end, page));
}
m_maybe_cpu_dirty = m_track_begin == m_track_end;
if (ImageRangeOverlaps(info.data.address, info.data.size, vaddr, size)) {
m_cpu_dirty = true;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
} else if (ImagePageRangesOverlap(info.data.address, info.data.size, vaddr, size)) {
m_maybe_cpu_dirty = true;
}
}
@@ -52,117 +96,93 @@ struct Image final: ImageInfo {
[[nodiscard]] bool IsDefinitelyCpuDirty() const { return m_cpu_dirty; }
[[nodiscard]] bool IsMaybeCpuDirty() const { return m_maybe_cpu_dirty; }
[[nodiscard]] bool NeedsMaybeCpuHash() const {
return m_maybe_cpu_dirty && !m_maybe_cpu_hash_valid;
}
[[nodiscard]] bool IsCpuTrackingComplete() const {
return m_track_begin == address && m_track_end == address + size;
return m_maybe_cpu_dirty && !m_maybe_hash_valid;
}
void SetMaybeCpuHash(uint64_t hash) {
if (!NeedsMaybeCpuHash()) {
EXIT("sampled image cannot initialize maybe-dirty hash\n");
EXIT("image cannot initialize maybe-dirty hash\n");
}
m_maybe_cpu_hash = hash;
m_maybe_cpu_hash_valid = true;
m_maybe_cpu_hash = hash;
m_maybe_hash_valid = true;
}
[[nodiscard]] bool ResolveMaybeCpuHash(uint64_t hash) {
if (!m_maybe_cpu_dirty || !m_maybe_cpu_hash_valid || m_cpu_dirty) {
EXIT("sampled image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_cpu_dirty = hash != m_maybe_cpu_hash;
if (!m_cpu_dirty) {
m_track_begin = address;
m_track_end = address + size;
if (!m_maybe_cpu_dirty || !m_maybe_hash_valid || m_cpu_dirty) {
EXIT("image cannot resolve maybe-dirty hash\n");
}
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
m_cpu_dirty |= hash != m_maybe_cpu_hash;
return m_cpu_dirty;
}
void RefreshComplete() {
if (!IsCpuDirty()) {
EXIT("clean sampled image cannot complete a refresh\n");
EXIT("clean image cannot complete a refresh\n");
}
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_cpu_hash_valid = false;
m_track_begin = address;
m_track_end = address + size;
m_cpu_dirty = false;
m_maybe_cpu_dirty = false;
m_maybe_hash_valid = false;
}
[[nodiscard]] bool IsGpuModified() const noexcept { return m_gpu_modified; }
void MarkGpuModified() noexcept { m_gpu_modified = true; }
void ClearGpuModified() noexcept { m_gpu_modified = false; }
[[nodiscard]] bool IsBufferModified() const noexcept { return m_buffer_modified; }
void MarkBufferModified() noexcept { m_buffer_modified = true; }
void ClearBufferModified() noexcept { m_buffer_modified = false; }
[[nodiscard]] bool Overlaps(uint64_t address, uint64_t size, bool pages = false) const noexcept {
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();
}
[[nodiscard]] uint64_t AccountedSize() const noexcept {
return backing.image == nullptr ? 0 : (info.data.size + 1023) & ~uint64_t {1023};
}
[[nodiscard]] uint64_t HashGuestEdges() const;
ImageInfo info;
VulkanImage backing;
ImageViewCache views;
ImageUsage usage;
ImageBinding binding;
bool registered = false;
ImageId depth_id {};
uint64_t tick_accessed_last = 0;
size_t lru_id = 0;
private:
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_cpu_hash_valid = false;
uint64_t m_track_begin = 0;
uint64_t m_track_end = 0;
uint64_t m_maybe_cpu_hash = 0;
friend struct ImageTestAccess;
[[nodiscard]] static vk::ImageAspectFlags FullAspectMask(vk::Format format) noexcept;
[[nodiscard]] static uint32_t CopyRows(uint64_t row_size, uint32_t rows,
uint64_t capacity) noexcept;
[[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;
uint64_t m_maybe_cpu_hash = 0;
bool m_cpu_dirty = false;
bool m_maybe_cpu_dirty = false;
bool m_maybe_hash_valid = false;
bool m_gpu_modified = false;
bool m_buffer_modified = false;
};
namespace ImageOps {
[[nodiscard]] GpuTextureVulkanImage* CreateTexture(const ImageInfo& info,
bool storage, vk::ComponentMapping& components);
void CreateTextureViews(GpuTextureVulkanImage& image,
const ImageInfo& info, bool storage, vk::ComponentMapping components);
[[nodiscard]] RenderTextureVulkanImage* CreateRenderTarget(
const RenderTargetInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
void UploadRenderTargetLayers(RenderTextureVulkanImage& image,
const RenderTargetInfo& info, uint32_t base_layer,
uint32_t layer_count, bool refresh);
void UploadRenderTarget(RenderTextureVulkanImage& image,
const RenderTargetInfo& info, bool refresh);
[[nodiscard]] DepthStencilVulkanImage* CreateDepthTarget(
const DepthTargetInfo& info);
void ValidateVideoOut(const VideoOutInfo& info);
[[nodiscard]] VideoOutVulkanImage* CreateVideoOut(
const VideoOutInfo& info);
void SwapVideoOutBgra16(void* data, uint64_t size);
void UploadVideoOut(VideoOutVulkanImage& image, const VideoOutInfo& info,
bool refresh);
[[nodiscard]] GpuTextureVulkanImage* CreateDummyTexture(bool uint_format,
bool image_3d, bool storage);
void Destroy(VulkanImage& image);
void Validate(const ImageInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
} // namespace ImageOps
struct ImageRetirementRange {
uint64_t address = 0;
uint64_t size = 0;
bool retire = false;
};
struct ImageRetirementConflict {
size_t retired = SIZE_MAX;
size_t retained = SIZE_MAX;
[[nodiscard]] bool Exists() const { return retired != SIZE_MAX; }
};
[[nodiscard]] inline ImageRetirementConflict
FindImageRetirementConflict(std::span<const ImageRetirementRange> ranges) {
for (size_t retired = 0; retired < ranges.size(); retired++) {
if (!ranges[retired].retire) {
continue;
}
for (size_t retained = 0; retained < ranges.size(); retained++) {
if (ranges[retained].retire) {
continue;
}
if (ImageRangeOverlaps(ranges[retired].address, ranges[retired].size,
ranges[retained].address, ranges[retained].size)) {
return {retired, retained};
}
}
}
return {};
}
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
File diff suppressed because it is too large Load Diff
+343 -337
View File
@@ -1,9 +1,8 @@
#include "graphics/host_gpu/renderer/imageView.h"
#include "common/assert.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image.h"
#include <mutex>
@@ -11,62 +10,286 @@ namespace Libs::Graphics {
namespace {
void CreateView(VulkanImage& image, int view_index,
vk::ImageViewType view_type, vk::ImageAspectFlags aspect_mask,
vk::ComponentMapping components, uint32_t base_array_layer, uint32_t base_mip_level,
uint32_t layer_count, uint32_t level_count,
vk::Format view_format = vk::Format::eUndefined,
vk::ImageUsageFlags view_usage = {}) {
auto& graphics = GetRenderContext().GetGraphics();
if (view_index < 0 || view_index >= VulkanImage::VIEW_MAX ||
image.image_view[view_index] != nullptr) {
EXIT("invalid image-view creation target: image=%p index=%d current_view=%d\n",
static_cast<const void*>(&image), view_index,
view_index >= 0 && view_index < VulkanImage::VIEW_MAX &&
image.image_view[view_index] != nullptr);
}
vk::ImageViewUsageCreateInfo usage_info {};
usage_info.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage_info.usage = view_usage;
vk::ImageViewCreateInfo create_info {};
create_info.sType = vk::StructureType::eImageViewCreateInfo;
create_info.pNext = view_usage ? &usage_info : nullptr;
create_info.image = image.image;
create_info.viewType = view_type;
create_info.format = view_format != vk::Format::eUndefined ? view_format : image.format;
create_info.components = components;
create_info.subresourceRange.aspectMask = aspect_mask;
create_info.subresourceRange.baseArrayLayer = base_array_layer;
create_info.subresourceRange.baseMipLevel = base_mip_level;
create_info.subresourceRange.layerCount = layer_count;
create_info.subresourceRange.levelCount = level_count;
const auto result =
graphics.device.createImageView(&create_info, nullptr, &image.image_view[view_index]);
if (result != vk::Result::eSuccess || image.image_view[view_index] == nullptr) {
EXIT("failed to create image view: result=%d image_format=%d view_format=%d index=%d\n",
static_cast<int>(result), static_cast<int>(image.format),
static_cast<int>(create_info.format), view_index);
[[nodiscard]] bool IsComponentSwizzle(vk::ComponentSwizzle swizzle) {
switch (swizzle) {
case vk::ComponentSwizzle::eIdentity:
case vk::ComponentSwizzle::eZero:
case vk::ComponentSwizzle::eOne:
case vk::ComponentSwizzle::eR:
case vk::ComponentSwizzle::eG:
case vk::ComponentSwizzle::eB:
case vk::ComponentSwizzle::eA: return true;
default: return false;
}
}
void CreateRenderTargetView(VulkanImage& image, int index,
vk::ComponentSwizzle r, vk::ComponentSwizzle g, vk::ComponentSwizzle b,
vk::ComponentSwizzle a, vk::ImageViewType type = vk::ImageViewType::e2D,
vk::Format view_format = vk::Format::eUndefined,
vk::ImageUsageFlags view_usage = {}, uint32_t level_count = 0) {
const auto layer_count = type == vk::ImageViewType::e2DArray ? image.layers : 1u;
CreateView(image, index, type, vk::ImageAspectFlagBits::eColor, {r, g, b, a}, 0, 0,
layer_count, level_count == 0 ? image.mip_levels : level_count, view_format,
view_usage);
[[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:
case vk::Format::eR8Uint:
case vk::Format::eR8Unorm: return true;
default: return false;
}
}
[[nodiscard]] bool IsDepthViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eR16Unorm:
case vk::Format::eD32Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Uint: return true;
default: return false;
}
}
[[nodiscard]] bool IsValidViewType(const VulkanImage& image, const ImageViewInfo& info) {
switch (image.image_type) {
case vk::ImageType::e1D:
if (info.type != vk::ImageViewType::e1D && info.type != vk::ImageViewType::e1DArray) {
return false;
}
return info.type != vk::ImageViewType::e1D || info.layer_count == 1;
case vk::ImageType::e2D:
switch (info.type) {
case vk::ImageViewType::e2D: return info.layer_count == 1;
case vk::ImageViewType::e2DArray: return true;
case vk::ImageViewType::eCube:
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::eCubeCompatible) &&
info.base_layer % 6 == 0 && info.layer_count == 6;
case vk::ImageViewType::eCubeArray:
return static_cast<bool>(image.flags &
vk::ImageCreateFlagBits::eCubeCompatible) &&
info.base_layer % 6 == 0 && info.layer_count % 6 == 0;
default: return false;
}
case vk::ImageType::e3D:
return info.type == vk::ImageViewType::e3D && info.base_layer == 0 &&
info.layer_count == 1;
default: return false;
}
}
[[nodiscard]] bool IsValidAspect(const VulkanImage& image, vk::ImageAspectFlags aspect) {
const auto depth_format = DepthAspectTransferFormat(image.format);
if (depth_format == vk::Format::eUndefined) {
return aspect == vk::ImageAspectFlagBits::eColor;
}
const auto supported = ImageViewOps::DepthAspectMask(image.format);
return static_cast<bool>(aspect) && !(aspect & ~supported);
}
} // namespace
namespace ImageViewOps {
namespace {
enum CompatibilityClass : uint32_t {
None = 0,
Bit8 = 1u << 0,
Bit16 = 1u << 1,
Bit24 = 1u << 2,
Bit32 = 1u << 3,
Bit48 = 1u << 4,
Bit64 = 1u << 5,
Bit96 = 1u << 6,
Bit128 = 1u << 7,
Bit192 = 1u << 8,
Bit256 = 1u << 9,
Bc1Rgb = 1u << 10,
Bc1Rgba = 1u << 11,
Bc2 = 1u << 12,
Bc3 = 1u << 13,
Bc4 = 1u << 14,
Bc5 = 1u << 15,
Bc6h = 1u << 16,
Bc7 = 1u << 17,
D16 = 1u << 18,
D16S8 = 1u << 19,
D24 = 1u << 20,
D24S8 = 1u << 21,
D32 = 1u << 22,
D32S8 = 1u << 23,
S8 = 1u << 24,
};
[[nodiscard]] uint32_t FormatClass(vk::Format format) noexcept {
switch (format) {
case vk::Format::eR4G4UnormPack8:
case vk::Format::eR8Sint:
case vk::Format::eR8Snorm:
case vk::Format::eR8Srgb:
case vk::Format::eR8Sscaled:
case vk::Format::eR8Uint:
case vk::Format::eR8Unorm:
case vk::Format::eR8Uscaled: return Bit8;
case vk::Format::eA1R5G5B5UnormPack16:
case vk::Format::eA4B4G4R4UnormPack16:
case vk::Format::eA4R4G4B4UnormPack16:
case vk::Format::eB4G4R4A4UnormPack16:
case vk::Format::eB5G5R5A1UnormPack16:
case vk::Format::eB5G6R5UnormPack16:
case vk::Format::eR10X6UnormPack16:
case vk::Format::eR12X4UnormPack16:
case vk::Format::eR16Sfloat:
case vk::Format::eR16Sint:
case vk::Format::eR16Snorm:
case vk::Format::eR16Sscaled:
case vk::Format::eR16Uint:
case vk::Format::eR16Unorm:
case vk::Format::eR16Uscaled:
case vk::Format::eR4G4B4A4UnormPack16:
case vk::Format::eR5G5B5A1UnormPack16:
case vk::Format::eR5G6B5UnormPack16:
case vk::Format::eR8G8Sint:
case vk::Format::eR8G8Snorm:
case vk::Format::eR8G8Srgb:
case vk::Format::eR8G8Sscaled:
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8Uscaled: return Bit16;
case vk::Format::eB8G8R8Sint:
case vk::Format::eB8G8R8Snorm:
case vk::Format::eB8G8R8Srgb:
case vk::Format::eB8G8R8Sscaled:
case vk::Format::eB8G8R8Uint:
case vk::Format::eB8G8R8Unorm:
case vk::Format::eB8G8R8Uscaled:
case vk::Format::eR8G8B8Sint:
case vk::Format::eR8G8B8Snorm:
case vk::Format::eR8G8B8Srgb:
case vk::Format::eR8G8B8Sscaled:
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Uscaled: return Bit24;
case vk::Format::eA2B10G10R10SintPack32:
case vk::Format::eA2B10G10R10SnormPack32:
case vk::Format::eA2B10G10R10SscaledPack32:
case vk::Format::eA2B10G10R10UintPack32:
case vk::Format::eA2B10G10R10UnormPack32:
case vk::Format::eA2B10G10R10UscaledPack32:
case vk::Format::eA2R10G10B10SintPack32:
case vk::Format::eA2R10G10B10SnormPack32:
case vk::Format::eA2R10G10B10SscaledPack32:
case vk::Format::eA2R10G10B10UintPack32:
case vk::Format::eA2R10G10B10UnormPack32:
case vk::Format::eA2R10G10B10UscaledPack32:
case vk::Format::eA8B8G8R8SintPack32:
case vk::Format::eA8B8G8R8SnormPack32:
case vk::Format::eA8B8G8R8SrgbPack32:
case vk::Format::eA8B8G8R8SscaledPack32:
case vk::Format::eA8B8G8R8UintPack32:
case vk::Format::eA8B8G8R8UnormPack32:
case vk::Format::eA8B8G8R8UscaledPack32:
case vk::Format::eB10G11R11UfloatPack32:
case vk::Format::eB8G8R8A8Sint:
case vk::Format::eB8G8R8A8Snorm:
case vk::Format::eB8G8R8A8Srgb:
case vk::Format::eB8G8R8A8Sscaled:
case vk::Format::eB8G8R8A8Uint:
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Uscaled:
case vk::Format::eE5B9G9R9UfloatPack32:
case vk::Format::eR10X6G10X6Unorm2Pack16:
case vk::Format::eR12X4G12X4Unorm2Pack16:
case vk::Format::eR16G16Sfloat:
case vk::Format::eR16G16Sint:
case vk::Format::eR16G16Snorm:
case vk::Format::eR16G16Sscaled:
case vk::Format::eR16G16Uint:
case vk::Format::eR16G16Unorm:
case vk::Format::eR16G16Uscaled:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Sint:
case vk::Format::eR32Uint:
case vk::Format::eR8G8B8A8Sint:
case vk::Format::eR8G8B8A8Snorm:
case vk::Format::eR8G8B8A8Srgb:
case vk::Format::eR8G8B8A8Sscaled:
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Uscaled: return Bit32;
case vk::Format::eR16G16B16Sfloat:
case vk::Format::eR16G16B16Sint:
case vk::Format::eR16G16B16Snorm:
case vk::Format::eR16G16B16Sscaled:
case vk::Format::eR16G16B16Uint:
case vk::Format::eR16G16B16Unorm:
case vk::Format::eR16G16B16Uscaled: return Bit48;
case vk::Format::eR16G16B16A16Sfloat:
case vk::Format::eR16G16B16A16Sint:
case vk::Format::eR16G16B16A16Snorm:
case vk::Format::eR16G16B16A16Sscaled:
case vk::Format::eR16G16B16A16Uint:
case vk::Format::eR16G16B16A16Unorm:
case vk::Format::eR16G16B16A16Uscaled:
case vk::Format::eR32G32Sfloat:
case vk::Format::eR32G32Sint:
case vk::Format::eR32G32Uint:
case vk::Format::eR64Sfloat:
case vk::Format::eR64Sint:
case vk::Format::eR64Uint: return Bit64;
case vk::Format::eR32G32B32Sfloat:
case vk::Format::eR32G32B32Sint:
case vk::Format::eR32G32B32Uint: return Bit96;
case vk::Format::eR32G32B32A32Sfloat:
case vk::Format::eR32G32B32A32Sint:
case vk::Format::eR32G32B32A32Uint:
case vk::Format::eR64G64Sfloat:
case vk::Format::eR64G64Sint:
case vk::Format::eR64G64Uint: return Bit128;
case vk::Format::eR64G64B64Sfloat:
case vk::Format::eR64G64B64Sint:
case vk::Format::eR64G64B64Uint: return Bit192;
case vk::Format::eR64G64B64A64Sfloat:
case vk::Format::eR64G64B64A64Sint:
case vk::Format::eR64G64B64A64Uint: return Bit256;
case vk::Format::eBc1RgbSrgbBlock:
case vk::Format::eBc1RgbUnormBlock: return Bc1Rgb | Bit64;
case vk::Format::eBc1RgbaSrgbBlock:
case vk::Format::eBc1RgbaUnormBlock: return Bc1Rgba | Bit64;
case vk::Format::eBc2SrgbBlock:
case vk::Format::eBc2UnormBlock: return Bc2 | Bit128;
case vk::Format::eBc3SrgbBlock:
case vk::Format::eBc3UnormBlock: return Bc3 | Bit128;
case vk::Format::eBc4SnormBlock:
case vk::Format::eBc4UnormBlock: return Bc4 | Bit64;
case vk::Format::eBc5SnormBlock:
case vk::Format::eBc5UnormBlock: return Bc5 | Bit128;
case vk::Format::eBc6HSfloatBlock:
case vk::Format::eBc6HUfloatBlock: return Bc6h | Bit128;
case vk::Format::eBc7SrgbBlock:
case vk::Format::eBc7UnormBlock: return Bc7 | Bit128;
case vk::Format::eD16Unorm: return D16;
case vk::Format::eD16UnormS8Uint: return D16S8;
case vk::Format::eX8D24UnormPack32: return D24;
case vk::Format::eD24UnormS8Uint: return D24S8;
case vk::Format::eD32Sfloat: return D32;
case vk::Format::eD32SfloatS8Uint: return D32S8;
case vk::Format::eS8Uint: return S8;
default: return None;
}
}
} // namespace
vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
switch (format) {
case vk::Format::eD16Unorm:
@@ -79,314 +302,97 @@ vk::ImageAspectFlags DepthAspectMask(vk::Format format) {
}
}
bool FormatSupportsStorage(vk::Format format) {
auto& graphics = GetRenderContext().GetGraphics();
const auto properties = graphics.GetFormatProperties(format);
return static_cast<bool>(properties.optimalTilingFeatures &
vk::FormatFeatureFlagBits::eStorageImage);
}
void CreateRenderTargetViews(RenderTextureVulkanImage& image) {
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity);
if (image.layers > 1) {
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT_ARRAY,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2DArray);
}
if (image.samples == 1 && FormatSupportsStorage(image.format)) {
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2D, vk::Format::eUndefined,
vk::ImageUsageFlags {}, 1);
if (image.layers > 1) {
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE_ARRAY,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2DArray, vk::Format::eUndefined,
vk::ImageUsageFlags {}, 1);
}
}
}
void CreateDepthViews(DepthStencilVulkanImage& image) {
CreateView(image, VulkanImage::VIEW_DEFAULT, vk::ImageViewType::e2D,
DepthAspectMask(image.format),
{vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity},
0, 0, 1, 1);
}
void CreateVideoOutViews(VideoOutVulkanImage& image) {
CreateRenderTargetView(image, VulkanImage::VIEW_DEFAULT,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity);
if ((image.format == vk::Format::eR8G8B8A8Srgb || image.format == vk::Format::eB8G8R8A8Srgb) &&
FormatSupportsStorage(vk::Format::eR8G8B8A8Uint)) {
CreateRenderTargetView(image, VulkanImage::VIEW_STORAGE,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ComponentSwizzle::eIdentity, vk::ComponentSwizzle::eIdentity,
vk::ImageViewType::e2D, vk::Format::eR8G8B8A8Uint,
vk::ImageUsageFlagBits::eStorage, 1);
}
}
void DestroyViews(VulkanImage& image) {
auto& graphics = GetRenderContext().GetGraphics();
for (auto& cached: image.view_cache.views) {
if (cached.view != nullptr) {
graphics.device.destroyImageView(cached.view, nullptr);
cached.view = nullptr;
}
}
image.view_cache.views.clear();
for (auto& view: image.image_view) {
if (view != nullptr) {
graphics.device.destroyImageView(view, nullptr);
view = nullptr;
}
bool FormatsCompatible(vk::Format base, vk::Format view) noexcept {
if (base == view) {
return true;
}
const auto base_class = FormatClass(base);
const auto view_class = FormatClass(view);
return view_class != None && (base_class & view_class) == view_class;
}
} // namespace ImageViewOps
vk::ImageView TextureCache::GetRenderTargetAttachmentView(RenderTextureVulkanImage& image,
vk::Format format, uint32_t level,
uint32_t base_layer,
uint32_t layer_count) {
if (image.format == vk::Format::eUndefined || level >= image.mip_levels || level >= 16 ||
layer_count == 0 || base_layer >= image.layers || layer_count > image.layers - base_layer) {
EXIT("TextureCache: invalid render-target attachment view, image=%p format=%d"
" level=%u image_levels=%u base_layer=%u layer_count=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(format), level, image.mip_levels,
base_layer, layer_count, image.layers);
vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
const auto& image = backing;
auto normalized = view_info;
const bool is_storage =
static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
normalized.aspect = FullAspectMask(image.format);
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
IsDepthViewFormat(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eDepth;
}
if (format != image.format && !IsRgba8SrgbReinterpretation(image.format, format)) {
EXIT("TextureCache: incompatible render-target attachment view, image_format=%d"
" view_format=%d level=%u\n",
static_cast<int>(image.format), static_cast<int>(format), level);
if (normalized.aspect & vk::ImageAspectFlagBits::eStencil &&
IsStencilViewFormat(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
}
normalized.usage =
is_storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlags {};
const bool format_compatible = normalized.format != vk::Format::eUndefined &&
IsCompatibleViewFormat(image.format, normalized.format);
const bool ranges_valid = normalized.level_count != 0 &&
normalized.base_level < image.mip_levels &&
normalized.level_count <= image.mip_levels - normalized.base_level &&
normalized.layer_count != 0 && normalized.base_layer < image.layers &&
normalized.layer_count <= image.layers - normalized.base_layer;
const bool mapping_valid =
IsComponentSwizzle(normalized.mapping.r) && IsComponentSwizzle(normalized.mapping.g) &&
IsComponentSwizzle(normalized.mapping.b) && IsComponentSwizzle(normalized.mapping.a);
if (image.image == nullptr || !format_compatible || !ranges_valid || !mapping_valid ||
!IsValidViewType(image, normalized) ||
!IsValidAspect(image, normalized.aspect)) {
EXIT("invalid image view: image_format=%d view_format=%d type=%d aspect=0x%x "
"mip=%u+%u layer=%u+%u usage=0x%x image_levels=%u image_layers=%u\n",
static_cast<int>(image.format), static_cast<int>(normalized.format),
static_cast<int>(normalized.type),
static_cast<vk::ImageAspectFlags::MaskType>(normalized.aspect), normalized.base_level,
normalized.level_count, normalized.base_layer, normalized.layer_count,
static_cast<vk::ImageUsageFlags::MaskType>(normalized.usage), image.mip_levels,
image.layers);
}
return GetImageView(
image, {format, layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray,
vk::ImageAspectFlagBits::eColor, level, 1, base_layer, layer_count,
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eColorAttachment});
}
vk::ImageView TextureCache::GetDepthTargetAttachmentView(DepthStencilVulkanImage& image,
uint32_t base_layer,
uint32_t layer_count) {
if (layer_count == 0 || base_layer >= image.layers || layer_count > image.layers - base_layer) {
EXIT("TextureCache: invalid depth-target attachment view, image=%p base_layer=%u "
"layer_count=%u image_layers=%u\n",
static_cast<const void*>(&image), base_layer, layer_count, image.layers);
}
return GetImageView(image,
{image.format,
layer_count == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray,
ImageViewOps::DepthAspectMask(image.format), 0, 1, base_layer, layer_count,
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eDepthStencilAttachment});
}
vk::ImageView TextureCache::GetImageView(VulkanImage& image, const ImageViewInfo& info) {
const bool supported_type = info.type == vk::ImageViewType::e2D ||
info.type == vk::ImageViewType::e2DArray ||
info.type == vk::ImageViewType::e3D;
const bool supported_usage = info.usage == vk::ImageUsageFlagBits::eSampled ||
info.usage == vk::ImageUsageFlagBits::eStorage ||
info.usage == vk::ImageUsageFlagBits::eColorAttachment ||
info.usage == vk::ImageUsageFlagBits::eDepthStencilAttachment;
const bool valid_shape =
(info.type == vk::ImageViewType::e2D && info.layer_count == 1) ||
info.type == vk::ImageViewType::e2DArray ||
(info.type == vk::ImageViewType::e3D && info.base_layer == 0 && info.layer_count == 1);
if (info.format == vk::Format::eUndefined || !info.aspect || info.level_count == 0 ||
info.base_level >= (image.mip_levels) ||
info.level_count > image.mip_levels - info.base_level || info.layer_count == 0 ||
info.base_layer >= image.layers || info.layer_count > image.layers - info.base_layer ||
!supported_type || !valid_shape || !supported_usage) {
EXIT("TextureCache: invalid dynamic image view, image=%p format=%d aspect=0x%x"
" swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x"
" image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(info.format),
static_cast<vk::ImageAspectFlags::MaskType>(info.aspect), info.swizzle,
info.base_level, info.level_count, info.base_layer, info.layer_count,
static_cast<int>(info.type), static_cast<vk::ImageUsageFlags::MaskType>(info.usage),
image.mip_levels, image.layers);
}
auto& cache = image.view_cache;
std::lock_guard lock(cache.mutex);
for (const auto& cached: cache.views) {
if (cached.info == info) {
std::lock_guard lock(views.mutex);
for (const auto& cached: views.views) {
if (cached.info == normalized) {
return cached.view;
}
}
vk::ImageViewUsageCreateInfo usage {};
usage.sType = vk::StructureType::eImageViewUsageCreateInfo;
usage.usage = info.usage;
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 = info.type;
create.format = info.format;
create.components = info.usage == vk::ImageUsageFlagBits::eSampled
? TextureGetComponentMapping(info.swizzle)
: vk::ComponentMapping {};
create.subresourceRange.aspectMask = info.aspect;
create.subresourceRange.baseMipLevel = info.base_level;
create.subresourceRange.levelCount = info.level_count;
create.subresourceRange.baseArrayLayer = info.base_layer;
create.subresourceRange.layerCount = info.layer_count;
vk::ImageView view = nullptr;
const auto result = m_graphics.device.createImageView(&create, nullptr, &view);
create.viewType = normalized.type;
create.format = normalized.format;
create.components = normalized.mapping;
create.subresourceRange.aspectMask = normalized.aspect;
create.subresourceRange.baseMipLevel = normalized.base_level;
create.subresourceRange.levelCount = normalized.level_count;
create.subresourceRange.baseArrayLayer = normalized.base_layer;
create.subresourceRange.layerCount = normalized.layer_count;
vk::ImageView view = nullptr;
const auto result = m_graphics->device.createImageView(&create, nullptr, &view);
if (result != vk::Result::eSuccess || view == nullptr) {
EXIT("TextureCache: failed to create dynamic image view, result=%d format=%d"
" aspect=0x%x swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d usage=0x%x\n",
static_cast<int>(result), static_cast<int>(info.format),
static_cast<vk::ImageAspectFlags::MaskType>(info.aspect), info.swizzle,
info.base_level, info.level_count, info.base_layer, info.layer_count,
static_cast<int>(info.type), static_cast<vk::ImageUsageFlags::MaskType>(info.usage));
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",
static_cast<int>(result), static_cast<int>(image.format),
static_cast<int>(view_info.format), static_cast<int>(view_info.type),
static_cast<vk::ImageAspectFlags::MaskType>(view_info.aspect), view_info.base_level,
view_info.level_count, view_info.base_layer, view_info.layer_count,
static_cast<vk::ImageUsageFlags::MaskType>(view_info.usage));
}
cache.views.push_back({info, view});
views.views.push_back({normalized, view});
return view;
}
vk::ImageView TextureCache::GetDepthTargetSampledView(DepthStencilVulkanImage& image,
vk::Format view_format, uint32_t swizzle,
uint32_t base_level, uint32_t level_count,
vk::ImageViewType type, uint32_t base_layer,
uint32_t layer_count) {
if (view_format == vk::Format::eUndefined ||
!IsSupportedSampledDepthView(image.format, view_format, swizzle)) {
EXIT("TextureCache: invalid sampled depth-target view, image=%p image_format=%d"
" view_format=%d swizzle=0x%03x mip=%u+%u layer=%u+%u type=%d"
" image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(image.format),
static_cast<int>(view_format), swizzle, base_level, level_count, base_layer,
layer_count, static_cast<int>(type), image.mip_levels, image.layers);
}
return GetImageView(image, {image.format, type, vk::ImageAspectFlagBits::eDepth, base_level,
level_count, base_layer, layer_count, swizzle});
}
vk::ImageView TextureCache::GetSampledColorView(VulkanImage& image, vk::Format view_format,
uint32_t swizzle, uint32_t base_level,
uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count) {
if (view_format == vk::Format::eUndefined || base_level >= 16 ||
(type != vk::ImageViewType::e2D && type != vk::ImageViewType::e2DArray) ||
!IsSupportedSampledColorView(image.format, view_format, swizzle)) {
EXIT("TextureCache: invalid sampled color view, image=%p swizzle=0x%03x"
" view_format=%d mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), swizzle, static_cast<int>(view_format), base_level,
level_count, base_layer, layer_count, static_cast<int>(type), image.mip_levels,
image.layers);
}
const auto precreated_view = type == vk::ImageViewType::e2DArray
? VulkanImage::VIEW_DEFAULT_ARRAY
: VulkanImage::VIEW_DEFAULT;
const bool full_view = base_level == 0 && level_count == image.mip_levels && base_layer == 0 &&
layer_count == (type == vk::ImageViewType::e2DArray ? image.layers : 1u);
if (view_format == image.format && swizzle == DstSel(4, 5, 6, 7) && full_view &&
image.image_view[precreated_view] != nullptr) {
return image.image_view[precreated_view];
}
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, base_level,
level_count, base_layer, layer_count, swizzle});
}
vk::ImageView TextureCache::GetRenderTargetStorageView(RenderTextureVulkanImage& image,
vk::Format view_format, uint32_t base_level,
uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count) {
if (view_format == vk::Format::eUndefined ||
(type != vk::ImageViewType::e2D && type != vk::ImageViewType::e2DArray)) {
EXIT("TextureCache: invalid render-target storage view, image=%p view_format=%d"
" mip=%u+%u layer=%u+%u type=%d image_levels=%u image_layers=%u\n",
static_cast<const void*>(&image), static_cast<int>(view_format), base_level,
level_count, base_layer, layer_count, static_cast<int>(type), image.mip_levels,
image.layers);
}
const bool exact = view_format == image.format;
const bool compatible = view_format == BgraSrgbStorageViewFormat(image.format);
if (!exact && !compatible) {
EXIT("TextureCache: incompatible render-target storage view, image_format=%d"
" view_format=%d base=%u count=%u\n",
static_cast<int>(image.format), static_cast<int>(view_format), base_level,
level_count);
}
if (exact) {
const auto index = type == vk::ImageViewType::e2DArray ? VulkanImage::VIEW_STORAGE_ARRAY
: VulkanImage::VIEW_STORAGE;
const bool full_view =
base_level == 0 && level_count == 1 && base_layer == 0 &&
layer_count == (type == vk::ImageViewType::e2DArray ? image.layers : 1u);
if (full_view && image.image_view[index] != nullptr) {
return image.image_view[index];
}
}
if (compatible && !ImageViewOps::FormatSupportsStorage(view_format)) {
EXIT("TextureCache: compatible render-target storage format lacks storage support,"
" image_format=%d view_format=%d base=%u count=%u\n",
static_cast<int>(image.format), static_cast<int>(view_format), base_level,
level_count);
}
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, base_level,
level_count, base_layer, layer_count, DstSel(4, 5, 6, 7),
vk::ImageUsageFlagBits::eStorage});
}
vk::ImageView TextureCache::GetStorageTextureSampledView(StorageTextureVulkanImage& image,
const ImageInfo& info) {
const auto shape = SelectStorageSampledViewShape(info.type, info.depth, image.layers);
if (image.image == nullptr || shape == StorageSampledViewShape::Unsupported ||
info.base_array != 0 || info.levels != image.mip_levels || info.base_level >= info.levels ||
info.view_levels == 0 || info.base_level + info.view_levels > info.levels) {
EXIT("TextureCache: invalid sampled view of storage texture, image=%p type=%u depth=%u"
" base=%u levels=%u view_levels=%u image_levels=%u base_array=%u\n",
static_cast<const void*>(&image), info.type, info.depth, info.base_level, info.levels,
info.view_levels, image.mip_levels, info.base_array);
}
const auto view_format = TextureGetFormat(info.format);
if (view_format != image.format && !IsRgba8SrgbReinterpretation(image.format, view_format) &&
!IsR32UintFloatReinterpretation(image.format, view_format)) {
EXIT("TextureCache: incompatible sampled view of storage texture, image_format=%d"
" view_format=%d swizzle=0x%03x\n",
static_cast<int>(image.format), static_cast<int>(view_format), info.swizzle);
}
vk::ImageViewType type = static_cast<vk::ImageViewType>(VK_IMAGE_VIEW_TYPE_MAX_ENUM);
switch (shape) {
case StorageSampledViewShape::Image2D: type = vk::ImageViewType::e2D; break;
case StorageSampledViewShape::Image2DArray: type = vk::ImageViewType::e2DArray; break;
case StorageSampledViewShape::Image3D: type = vk::ImageViewType::e3D; break;
case StorageSampledViewShape::Unsupported:
EXIT("TextureCache: unsupported sampled storage-image view shape\n");
}
const auto layer_count = shape == StorageSampledViewShape::Image2DArray ? info.depth : 1u;
return GetImageView(image, {view_format, type, vk::ImageAspectFlagBits::eColor, info.base_level,
info.view_levels, 0, layer_count, info.swizzle});
}
vk::ImageView TextureCache::GetStorageTextureStorageView(StorageTextureVulkanImage& image,
uint32_t base_level) {
if (image.image == nullptr || base_level >= (image.mip_levels)) {
EXIT("TextureCache: invalid storage-texture mip view, image=%p level=%u levels=%u\n",
static_cast<const void*>(&image), base_level, image.mip_levels);
}
if (base_level == 0) {
return image.image_view[VulkanImage::VIEW_DEFAULT];
}
return GetImageView(image, {image.format, vk::ImageViewType::e2D,
vk::ImageAspectFlagBits::eColor, base_level, 1, 0, 1,
DstSel(4, 5, 6, 7), vk::ImageUsageFlagBits::eStorage});
}
} // namespace Libs::Graphics
+23 -77
View File
@@ -2,42 +2,28 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/shader.h"
namespace Libs::Graphics {
[[nodiscard]] inline bool IsSupportedStorageSwizzle(uint32_t format, uint32_t swizzle) noexcept {
const bool single_channel =
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16Float) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
return swizzle == DstSel(4, 5, 6, 7) ||
(single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1) ||
swizzle == DstSel(4, 4, 4, 4))) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32UInt) &&
swizzle == DstSel(4, 5, 0, 1)) ||
((format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm) ||
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UInt)) &&
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float) &&
swizzle == DstSel(5, 6, 7, 4));
}
[[nodiscard]] inline bool IsSupportedStorageDepthTile(uint32_t format, uint32_t type,
uint32_t width, uint32_t height,
uint32_t depth) noexcept {
return (format == Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt) &&
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) && width != 0 &&
height != 0 && depth == 1) ||
(format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) &&
type == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) && width != 0 &&
height != 0 && depth == 1);
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
}
for (uint32_t channel = 0; channel < 4; channel++) {
switch (GetDstSel(swizzle, channel)) {
case 0:
case 1:
case 4:
case 5:
case 6:
case 7: break;
default: return false;
}
}
return true;
}
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
@@ -60,11 +46,8 @@ namespace Libs::Graphics {
switch (image_format) {
case vk::Format::eB8G8R8A8Unorm:
case vk::Format::eB8G8R8A8Srgb:
switch (view_format) {
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb: return true;
default: return false;
}
return view_format == vk::Format::eR8G8B8A8Unorm ||
view_format == vk::Format::eR8G8B8A8Srgb;
case vk::Format::eA2R10G10B10UnormPack32:
return view_format == vk::Format::eA2B10G10R10UnormPack32;
default: return false;
@@ -86,28 +69,10 @@ namespace Libs::Graphics {
return view_format == BgraSrgbStorageViewFormat(image_format) && swizzle == DstSel(6, 5, 4, 7);
}
[[nodiscard]] inline bool IsValidSampledColorSwizzle(uint32_t swizzle) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
}
for (uint32_t channel = 0; channel < 4; channel++) {
switch (GetDstSel(swizzle, channel)) {
case 0:
case 1:
case 4:
case 5:
case 6:
case 7: break;
default: return false;
}
}
return true;
}
[[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format,
vk::Format view_format,
uint32_t swizzle) noexcept {
if (!IsValidSampledColorSwizzle(swizzle)) {
if (!IsValidImageSwizzle(swizzle)) {
return false;
}
if (image_format == view_format || IsRgba8SrgbReinterpretation(image_format, view_format)) {
@@ -169,26 +134,13 @@ IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& r
!resource.written && !resource.atomic && !resource.depth_compare;
}
[[nodiscard]] inline int SelectStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
const bool single_channel =
view_format == vk::Format::eR8Unorm || view_format == vk::Format::eR8Uint ||
view_format == vk::Format::eR16Uint || view_format == vk::Format::eR32Uint ||
view_format == vk::Format::eR16Sfloat || view_format == vk::Format::eR32Sfloat;
const bool swizzle_ok =
swizzle == DstSel(4, 5, 6, 7) ||
(single_channel && (swizzle == DstSel(4, 0, 0, 0) || swizzle == DstSel(4, 0, 0, 1) ||
swizzle == DstSel(4, 4, 4, 4))) ||
(view_format == vk::Format::eR32G32Uint && swizzle == DstSel(4, 5, 0, 1)) ||
((view_format == vk::Format::eR8G8B8A8Unorm || view_format == vk::Format::eR8G8B8A8Uint) &&
(swizzle == DstSel(4, 5, 6, 1) || swizzle == DstSel(6, 5, 4, 7))) ||
(view_format == vk::Format::eR32G32B32A32Sfloat && swizzle == DstSel(5, 6, 7, 4));
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
if ((image_format != view_format &&
!IsBgraSrgbStorageView(image_format, view_format, swizzle)) ||
!swizzle_ok) {
!IsValidImageSwizzle(swizzle)) {
UnsupportedColorView("storage", image_format, view_format, swizzle);
}
return VulkanImage::VIEW_STORAGE;
}
[[nodiscard]] inline bool
@@ -216,13 +168,7 @@ ValidateStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource
namespace ImageViewOps {
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
[[nodiscard]] bool FormatSupportsStorage(vk::Format format);
void CreateRenderTargetViews(RenderTextureVulkanImage& image);
void CreateDepthViews(DepthStencilVulkanImage& image);
void CreateVideoOutViews(VideoOutVulkanImage& image);
void DestroyViews(VulkanImage& image);
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
} // namespace ImageViewOps
} // namespace Libs::Graphics
@@ -0,0 +1,60 @@
#include "graphics/host_gpu/renderer/masterSemaphore.h"
#include "common/assert.h"
#include "graphics/host_gpu/graphicContext.h"
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);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess || m_semaphore == nullptr);
}
MasterSemaphore::~MasterSemaphore() {
if (m_semaphore != nullptr) {
m_graphics.device.destroySemaphore(m_semaphore, nullptr);
}
}
void MasterSemaphore::Refresh() {
uint64_t counter = 0;
const auto result = m_graphics.device.getSemaphoreCounterValue(m_semaphore, &counter);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
auto known = m_gpu_tick.load(std::memory_order_acquire);
while (known < counter &&
!m_gpu_tick.compare_exchange_weak(known, counter, std::memory_order_release,
std::memory_order_relaxed)) {
}
}
void MasterSemaphore::Wait(uint64_t tick) {
if (IsFree(tick)) {
return;
}
Refresh();
if (IsFree(tick)) {
return;
}
vk::SemaphoreWaitInfo wait_info {};
wait_info.sType = vk::StructureType::eSemaphoreWaitInfo;
wait_info.semaphoreCount = 1;
wait_info.pSemaphores = &m_semaphore;
wait_info.pValues = &tick;
const auto result = m_graphics.device.waitSemaphores(&wait_info, UINT64_MAX);
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
Refresh();
}
} // namespace Libs::Graphics
@@ -0,0 +1,43 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <atomic>
namespace Libs::Graphics {
struct GraphicContext;
class MasterSemaphore {
public:
explicit MasterSemaphore(GraphicContext& graphics);
~MasterSemaphore();
KYTY_CLASS_NO_COPY(MasterSemaphore);
[[nodiscard]] uint64_t CurrentTick() const noexcept {
return m_current_tick.load(std::memory_order_acquire);
}
[[nodiscard]] uint64_t KnownGpuTick() const noexcept {
return m_gpu_tick.load(std::memory_order_acquire);
}
[[nodiscard]] bool IsFree(uint64_t tick) const noexcept { return KnownGpuTick() >= tick; }
[[nodiscard]] uint64_t NextTick() noexcept {
return m_current_tick.fetch_add(1, std::memory_order_release);
}
[[nodiscard]] vk::Semaphore Handle() const noexcept { return m_semaphore; }
void Refresh();
void Wait(uint64_t tick);
private:
GraphicContext& m_graphics;
vk::Semaphore m_semaphore = nullptr;
std::atomic<uint64_t> m_gpu_tick {0};
std::atomic<uint64_t> m_current_tick {1};
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MASTERSEMAPHORE_H_
@@ -7,7 +7,7 @@
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
@@ -36,13 +36,25 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
} // namespace
PipelineCache::~PipelineCache() {
auto destroy = [this](const auto& pipelines) {
for (const auto& [key, pipeline]: pipelines) {
(void)key;
m_graphics.device.destroyPipeline(pipeline->pipeline, nullptr);
m_graphics.device.destroyPipelineLayout(pipeline->pipeline_layout, nullptr);
}
};
destroy(m_graphics_pipelines);
destroy(m_compute_pipelines);
}
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
return std::memcmp(this, &other, sizeof(*this)) == 0;
}
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
VulkanFramebuffer& framebuffer, RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo& depth, ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
@@ -61,10 +73,10 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
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].vulkan_buffer != nullptr
? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
ctx.GetRenderTargetMask(), colors[i].target_slot))
: 0);
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();
@@ -76,11 +88,40 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
PipelineStaticParameters static_params {};
GraphicsPipeline p {};
p.render_pass_id = framebuffer.render_pass_id;
p.ps_shader_id = ps_id;
p.vs_shader_id = vs_id;
static_params.color_count = color_count;
PipelineRenderingState 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;
if (attachment_samples == 0) {
attachment_samples = colors[i].samples;
} else if (attachment_samples != colors[i].samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, colors[i].samples);
}
}
const bool with_depth =
depth.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;
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
attachment_samples, depth.samples);
}
}
EXIT_IF(attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {});
if (ps_active && depth.depth_test_enable && ps_input_info->ps_execute_on_noop) {
static std::atomic<uint32_t> log_count {0};
@@ -94,14 +135,13 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.samples = framebuffer.samples;
static_params.samples = attachment_samples;
static_params.sample_shading_enable =
ps_active && framebuffer.samples > 1 && ps_input_info->ps_sample_shading;
ps_active && attachment_samples > 1 && ps_input_info->ps_sample_shading;
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 =
(depth.format != vk::Format::eUndefined && depth.vulkan_buffer != nullptr);
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;
@@ -139,7 +179,7 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
NormalizeStaticParamsForDynamicState(static_params);
GraphicsPipelineKey key {};
key.render_pass_id = p.render_pass_id;
key.rendering = rendering;
key.vs_shader_id = p.vs_shader_id;
key.ps_shader_id = p.ps_shader_id;
key.static_params = static_params;
@@ -162,7 +202,8 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
auto cached = std::make_unique<GraphicsPipeline>(p);
LogPipelineTrace("CreatePipelineInternal begin", vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32);
CreatePipelineInternal(*cached, framebuffer.render_pass, vs_input_info, vs_spirv, ps_input_info,
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, rendering, vs_input_info,
vs_spirv, ps_input_info,
ps_spirv, static_params, vs_id.hash0, vs_id.crc32, ps_id.hash0,
ps_id.crc32, ps_active);
LogPipelineTrace("CreatePipelineInternal done", vs_id.hash0, vs_id.crc32, ps_id.hash0,
@@ -204,7 +245,7 @@ PipelineCache::CreateComputePipeline(ShaderComputeInputInfo& input_info,
}
auto cached = std::make_unique<ComputePipeline>(p);
CreatePipelineInternal(*cached, input_info, cs_spirv);
CreatePipelineInternal(m_graphics, m_descriptor_cache, *cached, input_info, cs_spirv);
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
EXIT_NOT_IMPLEMENTED(cached->pipeline_layout == nullptr);
+33 -11
View File
@@ -20,8 +20,8 @@ namespace Libs::Graphics {
struct GraphicContext;
struct RenderColorInfo;
struct RenderDepthInfo;
struct VulkanFramebuffer;
class RenderCommandBuffer;
class DescriptorCache;
namespace HW {
class Context;
@@ -86,12 +86,22 @@ static_assert(sizeof(PipelineStaticParameters) ==
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
struct PipelineRenderingState {
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
vk::Format depth_format = vk::Format::eUndefined;
vk::Format stencil_format = vk::Format::eUndefined;
uint32_t color_count = 0;
bool operator==(const PipelineRenderingState&) const = default;
};
class PipelineCache {
public:
explicit PipelineCache(GraphicContext& graphics): m_graphics(graphics) {
PipelineCache(GraphicContext& graphics, DescriptorCache& descriptor_cache)
: m_graphics(graphics), m_descriptor_cache(descriptor_cache) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~PipelineCache() { KYTY_NOT_IMPLEMENTED; }
~PipelineCache();
KYTY_CLASS_NO_COPY(PipelineCache);
struct Pipeline {
@@ -100,7 +110,6 @@ public:
};
struct GraphicsPipeline: Pipeline {
uint64_t render_pass_id = 0;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
};
@@ -110,8 +119,8 @@ public:
};
GraphicsPipeline& CreateGraphicsPipeline(
VulkanFramebuffer& framebuffer, RenderColorInfo* colors, uint32_t color_count,
RenderDepthInfo& depth, ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
@@ -120,13 +129,13 @@ public:
private:
struct GraphicsPipelineKey {
uint64_t render_pass_id = 0;
PipelineRenderingState rendering;
ShaderId vs_shader_id;
ShaderId ps_shader_id;
PipelineStaticParameters static_params;
bool operator==(const GraphicsPipelineKey& other) const {
return render_pass_id == other.render_pass_id && vs_shader_id == other.vs_shader_id &&
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
}
};
@@ -160,12 +169,21 @@ private:
Mix(hash, bytes[i]);
}
}
static void MixRendering(std::size_t& hash, const PipelineRenderingState& rendering) {
Mix(hash, rendering.color_count);
for (uint32_t i = 0; i < rendering.color_count; i++) {
Mix(hash, static_cast<uint32_t>(rendering.color_formats[i]));
}
Mix(hash, static_cast<uint32_t>(rendering.depth_format));
Mix(hash, static_cast<uint32_t>(rendering.stencil_format));
}
};
struct GraphicsPipelineKeyHash {
std::size_t operator()(const GraphicsPipelineKey& key) const {
std::size_t hash = 0;
PipelineKeyHash::Mix(hash, key.render_pass_id);
PipelineKeyHash::MixRendering(hash, key.rendering);
PipelineKeyHash::MixShaderId(hash, key.vs_shader_id);
PipelineKeyHash::MixShaderId(hash, key.ps_shader_id);
PipelineKeyHash::MixStaticParams(hash, key.static_params);
@@ -182,6 +200,7 @@ private:
};
GraphicContext& m_graphics;
DescriptorCache& m_descriptor_cache;
std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>,
GraphicsPipelineKeyHash>
m_graphics_pipelines;
@@ -192,7 +211,9 @@ private:
void LogPipelineTrace(const char* phase, uint32_t vs_hash0, uint32_t vs_crc32, uint32_t ps_hash0,
uint32_t ps_crc32);
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info,
std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info,
@@ -200,7 +221,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
bool ps_active);
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader);
+114 -45
View File
@@ -2,10 +2,13 @@
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRENDER_H_
#include "common/abi.h"
#include "common/assert.h"
#include "common/common.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/renderTarget.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <memory>
#include <vector>
@@ -15,29 +18,23 @@ namespace HW {
class Context;
class UserConfig;
class Shader;
struct DepthRenderTarget;
} // namespace HW
struct GraphicContext;
struct ShaderBufferResource;
struct ShaderComputeInputInfo;
struct VideoOutVulkanImage;
struct DepthStencilVulkanImage;
struct TextureVulkanImage;
struct StorageTextureVulkanImage;
struct RenderTextureVulkanImage;
struct CommandSlot;
struct VulkanBuffer;
struct VulkanDescriptorSet;
struct VulkanFramebuffer;
struct RenderDepthInfo;
struct RenderColorInfo;
class BufferCache;
struct HtileClearTarget {
uint64_t address = 0;
uint64_t size = 0;
};
struct DrawCallInfo;
struct DrawEmitInfo;
struct DrawIndexBufferSource;
struct DrawRenderState;
class RenderContext;
class CommandScheduler;
struct RenderExecutorTestAccess;
enum class CommandBufferDebugOp : uint32_t {
DispatchDirect,
@@ -66,9 +63,35 @@ private:
std::vector<std::shared_ptr<void>> m_resources;
};
struct SubmitInfo {
static constexpr uint32_t MaxSemaphores = 3;
std::array<vk::Semaphore, MaxSemaphores> wait_semaphores {};
std::array<uint64_t, MaxSemaphores> wait_ticks {};
std::array<vk::PipelineStageFlags, MaxSemaphores> wait_stages {};
std::array<vk::Semaphore, MaxSemaphores> signal_semaphores {};
std::array<uint64_t, MaxSemaphores> signal_ticks {};
uint32_t num_wait_semaphores = 0;
uint32_t num_signal_semaphores = 0;
void AddWait(vk::Semaphore semaphore, uint64_t tick = 1,
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eAllCommands) {
EXIT_IF(semaphore == nullptr || num_wait_semaphores >= MaxSemaphores);
wait_semaphores[num_wait_semaphores] = semaphore;
wait_ticks[num_wait_semaphores] = tick;
wait_stages[num_wait_semaphores++] = stage;
}
void AddSignal(vk::Semaphore semaphore, uint64_t tick = 1) {
EXIT_IF(semaphore == nullptr || num_signal_semaphores >= MaxSemaphores);
signal_semaphores[num_signal_semaphores] = semaphore;
signal_ticks[num_signal_semaphores++] = tick;
}
};
class CommandBuffer {
public:
CommandBuffer();
explicit CommandBuffer(CommandScheduler& scheduler);
~CommandBuffer();
KYTY_CLASS_NO_COPY(CommandBuffer);
@@ -77,14 +100,11 @@ public:
void Begin() const;
void End() const;
void Execute();
void ExecuteWithSemaphore(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore);
void Execute(const SubmitInfo& submit = {});
void SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0 = 0, uint32_t arg1 = 0,
uint32_t arg2 = 0, uint32_t arg3 = 0, uint64_t arg4 = 0);
void BeginRenderPass(VulkanFramebuffer& framebuffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) const;
void EndRenderPass() const;
void BeginRendering(const RenderState& state) const;
void EndRendering() const;
void WaitForFenceOnly();
void WaitForFence();
void WaitForFenceAndReset();
@@ -94,19 +114,18 @@ public:
[[nodiscard]] vk::CommandBuffer Handle() const;
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
[[nodiscard]] RenderContext& GetContext() const noexcept { return m_context; }
[[nodiscard]] bool IsExecute() const { return m_execute; }
private:
friend class BufferCache;
void Submit(vk::Semaphore wait_semaphore, vk::PipelineStageFlags wait_stage,
vk::Semaphore signal_semaphore);
void Release();
void FinalizeFence(bool reset_recording);
void ReleaseResourcesAfterFence();
void DeleteBuffersAfterFence();
void RecycleDescriptorsAfterFence();
RenderContext& m_context;
CommandScheduler& m_scheduler;
GraphicContext& m_graphics;
CommandSlot* m_slot = nullptr;
bool m_execute = false;
@@ -122,12 +141,13 @@ private:
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
FenceResourceRetainer m_fence_resources;
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
HostStreamBuffer m_host_stream;
mutable RenderState m_render_state;
mutable bool m_rendering = false;
};
class RenderCommandBuffer final: public CommandBuffer {
public:
RenderCommandBuffer() = default;
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
m_registers = &registers;
@@ -145,30 +165,79 @@ private:
HW::Shader* m_shaders = nullptr;
};
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& 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 RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& 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 RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
class RenderExecutor {
public:
explicit RenderExecutor(RenderContext& context): m_context(context) {}
KYTY_CLASS_NO_COPY(RenderExecutor);
void GraphicsRenderInit(GraphicContext& graphics);
void GraphicsRenderReleaseThreadCommandPool();
void DrawIndex(uint64_t submit_id, RenderCommandBuffer& 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, RenderCommandBuffer& 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, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
[[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage);
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
private:
struct GraphicsBindings {
DescriptorCache::PreparedBindings vertex;
std::optional<DescriptorCache::PreparedBindings> pixel;
};
[[nodiscard]] DescriptorCache::TextureBinding
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(CommandBuffer& buffer,
const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel,
bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo& target, uint32_t render_target_slice_offset = 0,
uint32_t render_target_slot = UINT32_MAX,
bool ignore_target_mask = false, bool exact_format = false);
void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderDepthInfo& target);
[[nodiscard]] bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, 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, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset);
void BindImage(ImageId id, bool storage);
void BindRenderTarget(ImageId id);
void TrackImageBinding(ImageId id);
void ResetBindings();
[[nodiscard]] bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer);
RenderContext& m_context;
std::vector<std::shared_ptr<Image>> m_bound_images;
friend struct RenderExecutorTestAccess;
};
[[nodiscard]] bool ResolveComputeImageClear(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);
[[nodiscard]] bool ResolveHtileClearTarget(const HW::DepthRenderTarget& target,
uint64_t descriptor_size, HtileClearTarget& resolved);
void GraphicsRenderMemoryBarrier(CommandBuffer& buffer);
void GraphicsRenderTextureBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size);
void GraphicsRenderDepthStencilBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size);
} // namespace Libs::Graphics
+35 -223
View File
@@ -9,20 +9,15 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/descriptors.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
@@ -50,217 +45,35 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
return stride == 0 ? records : records * stride;
}
bool ResolveHtileClearTarget(const HW::DepthRenderTarget& z, uint64_t descriptor_size,
HtileClearTarget& resolved) {
resolved = {};
const bool has_stencil =
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const auto* depth_policy = FindDepthFormatPolicy(z.z_info.format);
const bool supported_depth_state =
z.z_info.tile_surface_enable && depth_policy != nullptr && z.z_info.tile_mode_index == 0 &&
z.z_info.num_samples <= 3 && z.z_info.zrange_precision <= 1 && !z.z_info.expclear_enabled &&
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident &&
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 &&
z.depth_view.current_mip_level == 0 && z.depth_view.slice_start == 0 &&
z.depth_view.slice_max == 0 && z.depth_info.addr5_swizzle_mask == 0 &&
z.depth_info.array_mode == 0 && z.depth_info.pipe_config == 0 &&
z.depth_info.bank_width == 0 && z.depth_info.bank_height == 0 &&
z.depth_info.macro_tile_aspect == 0 && z.depth_info.num_banks == 0;
const bool supported_stencil_state =
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
!z.stencil_info.expclear_enabled &&
depth_htile_stencil_acceleration_compatible(has_stencil, true,
z.stencil_info.tile_stencil_disable) &&
!z.stencil_info.texture_compatible_stencil && !z.stencil_info.partially_resident &&
(!has_stencil ||
(z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) &&
!z.depth_view.stencil_write_disable));
const bool supported_htile_state =
z.htile_surface.linear == 0 && z.htile_surface.full_cache == 0 &&
z.htile_surface.htile_uses_preload_win == 0 && z.htile_surface.preload == 0 &&
z.htile_surface.prefetch_width == 0 && z.htile_surface.prefetch_height == 0 &&
z.htile_surface.dst_outside_zero_to_one == 0;
const bool supported_addresses =
z.z_read_base_addr != 0 && z.z_write_base_addr == z.z_read_base_addr &&
(z.z_read_base_addr & 0xffffu) == 0 &&
(has_stencil ? z.stencil_read_base_addr != 0 &&
z.stencil_write_base_addr == z.stencil_read_base_addr &&
(z.stencil_read_base_addr & 0xffffu) == 0
: z.stencil_read_base_addr == 0 && z.stencil_write_base_addr == 0) &&
z.htile_data_base_addr != 0 && (z.htile_data_base_addr & 0x7fffu) == 0 &&
descriptor_size != 0 && (descriptor_size & 0x7fffu) == 0 &&
z.htile_data_base_addr < TRACKER_ADDRESS_SIZE &&
descriptor_size <= TRACKER_ADDRESS_SIZE - z.htile_data_base_addr;
if (!supported_depth_state || !supported_stencil_state || !supported_htile_state ||
!supported_addresses) {
return false;
}
const bool size_xy_valid = z.size.valid;
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
if (!size_xy_valid && !wh_valid) {
// Prospero emits an exact full-surface metadata descriptor even
// when the compute context omits depth extent registers. Admit only that wholly absent
// layout state; partially programmed layouts remain unsupported.
const bool descriptor_backed_state =
!z.size.valid && z.size.x_max == 0 && z.size.y_max == 0 && !z.width_height_valid &&
z.width == 0 && z.height == 0 && !z.pitch_height_valid && z.pitch_div8_minus1 == 0 &&
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0;
if (!descriptor_backed_state) {
return false;
}
resolved = {.address = z.htile_data_base_addr, .size = descriptor_size};
return true;
}
const uint32_t width = size_xy_valid ? static_cast<uint32_t>(z.size.x_max) + 1u : z.width;
const uint32_t height = size_xy_valid ? static_cast<uint32_t>(z.size.y_max) + 1u : z.height;
if (width > 16384 || height > 16384 ||
(size_xy_valid && wh_valid && (width != z.width || height != z.height)) ||
(!z.pitch_height_valid &&
(z.pitch_div8_minus1 != 0 || z.height_div8_minus1 != 0 || z.slice_div64_minus1 != 0))) {
return false;
}
TileSizeAlign htile_size {};
if (!TileGetHtileSize(width, height, htile_size) || htile_size.size != descriptor_size) {
return false;
}
resolved = {.address = z.htile_data_base_addr, .size = htile_size.size};
return true;
}
static void ValidateFullHtileClearDispatch(const ShaderComputeInputInfo& input,
const ShaderBufferResource& metadata, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const bool thread_dimensions = input.dispatch_thread_dimensions;
const bool supported_shape =
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.group_id[0] && !input.group_id[1] &&
!input.group_id[2] && input.thread_ids_num == 1 && input.wave_size == 32 &&
!input.tg_size_en && mode == (thread_dimensions ? 0x61u : 0x41u);
const bool dimensions_match =
thread_dimensions
? input.dispatch_threads_num[0] == group_x && input.dispatch_threads_num[1] == 1 &&
input.dispatch_threads_num[2] == 1 && group_x % input.threads_num[0] == 0
: input.dispatch_threads_num[0] == 0 && input.dispatch_threads_num[1] == 0 &&
input.dispatch_threads_num[2] == 0;
const uint64_t launched_threads =
thread_dimensions ? group_x : static_cast<uint64_t>(group_x) * input.threads_num[0];
if (!supported_shape || !dimensions_match || metadata.Stride() == 0 ||
launched_threads != metadata.NumRecords()) {
EXIT("HTile compute clear does not cover the complete metadata surface\n");
}
}
static bool TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer, uint32_t group_x,
uint32_t group_y, uint32_t group_z, uint32_t mode) {
const auto& ctx = buffer.GetRegisters();
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
const RenderCommandBuffer& buffer) {
const auto& program = *input.stage.program;
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");
}
const auto& z = ctx.GetDepthRenderTarget();
const uint64_t meta_addr = z.htile_data_base_addr;
auto& cache = GetRenderContext().GetTextureCache();
uint32_t current_references = 0;
uint32_t registered_writes = 0;
uint64_t described_meta_size = 0;
HtileClearTarget registered_target {};
TextureCache::MetaRangeInfo registered_meta {};
auto& cache = buffer.GetContext().GetTextureCache();
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
const auto descriptor_size = BufferDescriptorSize(descriptor);
if (meta_addr != 0 && descriptor.Base48() == meta_addr) {
current_references++;
described_meta_size = descriptor_size;
}
// An exact registered metadata range remains
// identifiable even when it is no longer the currently bound depth target.
TextureCache::MetaRangeInfo resolved_meta {};
if (resource.written &&
cache.ResolveMetaRange(descriptor.Base48(), descriptor_size, resolved_meta)) {
registered_writes++;
registered_target = {.address = descriptor.Base48(), .size = descriptor_size};
registered_meta = resolved_meta;
if (!resource.written && cache.IsMeta(descriptor.Base48())) {
return false;
}
}
if (current_references == 0 && registered_writes == 0) {
return false;
}
if (current_references > 1 || (current_references == 0 && registered_writes > 1)) {
EXIT("HTile clear has ambiguous metadata descriptors: current=%u registered=%u\n",
current_references, registered_writes);
}
HtileClearTarget target {};
if (current_references != 0) {
if (!ResolveHtileClearTarget(z, described_meta_size, target)) {
EXIT("unsupported HTile compute-clear target state: current=%u registered=%u "
"meta=0x%016" PRIx64 "+0x%016" PRIx64 " depth=0x%016" PRIx64 "/0x%016" PRIx64
" stencil=0x%016" PRIx64 "/0x%016" PRIx64
" extent=%d:%ux%u wh=%d:%ux%u pitch=%d:%u/%u/%u zfmt=%u sfmt=%u samples=%u\n",
current_references, registered_writes, meta_addr, described_meta_size,
z.z_read_base_addr, z.z_write_base_addr, z.stencil_read_base_addr,
z.stencil_write_base_addr, z.size.valid, static_cast<uint32_t>(z.size.x_max) + 1u,
static_cast<uint32_t>(z.size.y_max) + 1u, z.width_height_valid, z.width, z.height,
z.pitch_height_valid, z.pitch_div8_minus1, z.height_div8_minus1,
z.slice_div64_minus1, z.z_info.format, z.stencil_info.format,
z.z_info.num_samples);
}
cache.RegisterMeta(target.address, target.size);
if (!cache.ResolveMetaRange(target.address, target.size, registered_meta)) {
EXIT("failed to resolve registered HTile compute-clear range\n");
}
} else {
target = registered_target;
}
GetRenderContext().GetBufferCache().ValidateGpuAccess(target.address, target.size, false, true);
uint32_t metadata_writes = 0;
ShaderBufferResource metadata_descriptor {};
if (!program.info.images.empty() || !program.info.samplers.empty() ||
!program.info.addresses.empty()) {
EXIT("HTile clear with non-buffer resources is unsupported: images=%zu samplers=%zu "
"addresses=%zu\n",
program.info.images.size(), program.info.samplers.size(),
program.info.addresses.size());
}
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
const auto descriptor_size = BufferDescriptorSize(descriptor);
if (descriptor.Base48() == target.address) {
if (!resource.written || resource.read || resource.atomic ||
descriptor_size != target.size || descriptor.SwizzleEnabled() ||
descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride()) {
EXIT("unsupported HTile compute metadata access\n");
if (!program.info.has_bitwise_xor) {
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
const auto& resource = program.info.buffers[i];
if (resource.written) {
const auto descriptor =
DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
if (cache.ClearMeta(descriptor.Base48())) {
return true;
}
}
metadata_descriptor = descriptor;
metadata_writes++;
continue;
}
if (resource.written || !resource.read || resource.atomic || descriptor.Base48() == 0 ||
descriptor_size == 0 ||
cache.QueryRegion(descriptor.Base48(), descriptor_size).metadata_pages) {
EXIT("unsupported HTile clear side-buffer access\n");
}
GetRenderContext().GetBufferCache().ValidateGpuAccess(descriptor.Base48(), descriptor_size,
true, false);
}
if (metadata_writes != 1) {
EXIT("HTile clear requires exactly one write-only metadata buffer, writes=%u\n",
metadata_writes);
}
ValidateFullHtileClearDispatch(input, metadata_descriptor, group_x, group_y, group_z, mode);
const bool recorded = registered_meta.full ? cache.ClearMeta(registered_meta.metadata_address)
: cache.TouchMeta(registered_meta.metadata_address,
registered_meta.slice, true);
if (!recorded) {
EXIT("failed to record HTile compute clear\n");
}
return true;
return false;
}
bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t group_x,
@@ -324,7 +137,7 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
size)) {
return false;
}
auto& cache = GetRenderContext().GetTextureCache();
auto& cache = command.GetContext().GetTextureCache();
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
return false;
}
@@ -337,8 +150,9 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
return true;
}
void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t thread_group_x,
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode) {
void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) {
EXIT_IF(buffer.IsInvalid());
auto& ctx = buffer.GetRegisters();
auto& sh_ctx = buffer.GetShaders();
@@ -347,8 +161,7 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
thread_group_x, thread_group_y, thread_group_z, mode,
sh_ctx.GetCs().cs_regs.data_addr);
Common::LockGuard lock(GetRenderContext().GetMutex());
Common::LockGuard lock(m_context.GetMutex());
if (sh_ctx.GetCs().cs_regs.data_addr == 0) {
LOGF("GraphicsRenderDispatchDirect: temporary: ignoring dispatch with null CS shader, "
"groups=%ux%ux%u mode=%u\n",
@@ -396,17 +209,18 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
input_info.dispatch_threads_num[2] = thread_group_z;
}
const uint32_t frame_num = GraphicsRunGetFrameNum();
const uint32_t frame_num = static_cast<uint32_t>(m_context.GetGpu().GetFrameNum());
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;
if (TryConsumeComputeMetaClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
if (TryConsumeComputeMetaClear(input_info, buffer)) {
ResetBindings();
return;
}
if (TryConsumeComputeImageClear(input_info, buffer, thread_group_x, thread_group_y,
thread_group_z, mode)) {
ResetBindings();
return;
}
const auto sampled_images = std::count_if(
@@ -501,15 +315,13 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
return;
}
auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline(
input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(buffer, input_info.stage,
vk::ShaderStageFlagBits::eCompute,
buffer.EndRendering();
auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(buffer, input_info.stage, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute);
RebindBuffers(buffer, bindings);
RebindImages(buffer, bindings);
DescriptorCache::PreparedBindings* binding_sets[] = {&bindings};
ActivateImageWrites(binding_sets);
auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
@@ -518,17 +330,17 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
has_storage_writes =
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);
}) ||
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);
}) ||
has_storage_writes;
if (has_storage_writes) {
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
}
ResetBindings();
}
} // namespace Libs::Graphics
@@ -2,6 +2,8 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/presentation/videoOut.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
@@ -10,24 +12,66 @@
namespace Libs::Graphics {
RenderContext::RenderContext(GraphicContext& graphics)
: m_graphics(graphics), m_pipeline_cache(graphics), m_descriptor_cache(graphics),
m_framebuffer_cache(graphics), m_sampler_cache(graphics), m_gds_buffer(graphics),
m_gpu_resources(graphics) {
: m_graphics(graphics), m_render_executor(*this), m_command_scheduler(*this, graphics),
m_descriptor_cache(graphics), m_pipeline_cache(graphics, m_descriptor_cache),
m_sampler_cache(graphics),
m_gpu_resources(graphics, m_command_scheduler) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
RenderContext::~RenderContext() {
ShutdownGpu();
m_command_scheduler.Shutdown();
}
void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
EXIT_IF(m_gpu != nullptr);
m_video_out = video_out;
m_gpu = std::make_unique<Gpu>(*this);
m_gpu_resources.SetGpu(m_gpu.get());
}
void RenderContext::ShutdownGpu() {
if (m_gpu != nullptr) {
m_gpu_resources.SetGpu(nullptr);
m_gpu->Shutdown();
m_gpu.reset();
}
if (m_video_out != nullptr) {
if (m_command_scheduler.Active()) {
m_command_scheduler.FinishCurrent();
}
m_command_scheduler.DrainPriorityOperations();
m_video_out = nullptr;
}
}
Gpu& RenderContext::GetGpu() const {
EXIT_IF(m_gpu == nullptr);
return *m_gpu;
}
VideoOut::VideoOutDriver& RenderContext::GetVideoOut() const {
EXIT_IF(m_video_out == nullptr);
return *m_video_out;
}
void RenderContext::AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
auto queue = LibKernel::EventQueue::KernelPinEqueue(eq);
if (!queue) {
return;
}
Common::LockGuard lock(m_eop_mutex);
auto it = std::find_if(m_eop_eqs.begin(), m_eop_eqs.end(), [eq, id](const auto& entry) {
return entry.eq == eq && entry.id == id;
});
if (it != m_eop_eqs.end()) {
it->count++;
return;
}
m_eop_eqs.push_back({eq, id, 1});
m_eop_eqs.push_back({eq, std::move(queue), id});
}
void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id) {
@@ -40,22 +84,27 @@ void RenderContext::DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id)
return;
}
if (--it->count == 0) {
m_eop_eqs.erase(it);
}
m_eop_eqs.erase(it);
}
void RenderContext::TriggerEopEvent(uint32_t context_id) {
Common::LockGuard lock(m_eop_mutex);
std::vector<EopEqRegistration> registrations;
{
Common::LockGuard lock(m_eop_mutex);
registrations = m_eop_eqs;
}
for (auto& eop_entry: m_eop_eqs) {
if (eop_entry.eq != nullptr) {
const auto id = static_cast<uintptr_t>(eop_entry.id);
auto result = LibKernel::EventQueue::KernelTriggerEvent(
eop_entry.eq, id, LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
for (const auto& registration: registrations) {
const auto result = LibKernel::EventQueue::KernelTriggerEvent(
registration.eq, static_cast<uintptr_t>(registration.id),
LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS,
reinterpret_cast<void*>(static_cast<uintptr_t>(context_id)));
if (result == LibKernel::KERNEL_ERROR_EBADF ||
result == LibKernel::KERNEL_ERROR_ENOENT) {
DeleteEopEq(registration.eq, registration.id);
continue;
}
EXIT_NOT_IMPLEMENTED(result != OK);
}
auto tsc = LibKernel::KernelReadTsc();
+25 -19
View File
@@ -8,38 +8,45 @@
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/gdsBuffer.h"
#include "graphics/host_gpu/renderer/gpuResourceManager.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/samplerCache.h"
#include "graphics/host_gpu/renderer/textureCache.h"
#include "kernel/eventQueue.h"
#include <memory>
#include <vector>
namespace Libs::VideoOut {
class VideoOutDriver;
}
namespace Libs::Graphics {
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
class Gpu;
class RenderContext {
public:
explicit RenderContext(GraphicContext& graphics);
~RenderContext() { KYTY_NOT_IMPLEMENTED; }
~RenderContext();
KYTY_CLASS_NO_COPY(RenderContext);
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
void ShutdownGpu();
[[nodiscard]] Gpu& GetGpu() const;
[[nodiscard]] VideoOut::VideoOutDriver& GetVideoOut() const;
Common::Mutex& GetMutex() { return m_mutex; }
CommandScheduler& GetCommandScheduler() { return m_command_scheduler; }
PipelineCache& GetPipelineCache() { return m_pipeline_cache; }
DescriptorCache& GetDescriptorCache() { return m_descriptor_cache; }
FramebufferCache& GetFramebufferCache() { return m_framebuffer_cache; }
SamplerCache& GetSamplerCache() { return m_sampler_cache; }
GdsBuffer& GetGdsBuffer() { return m_gds_buffer; }
GpuResourceManager& GetGpuResources() { return m_gpu_resources; }
BufferCache& GetBufferCache() { return m_gpu_resources.GetBufferCache(); }
TextureCache& GetTextureCache() { return m_gpu_resources.GetTextureCache(); }
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
void AddEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
void DeleteEopEq(LibKernel::EventQueue::KernelEqueue eq, int id);
@@ -47,27 +54,26 @@ public:
private:
struct EopEqRegistration {
LibKernel::EventQueue::KernelEqueue eq = nullptr;
int id = 0;
uint32_t count = 0;
LibKernel::EventQueue::KernelEqueue eq = LibKernel::EventQueue::KERNEL_EQUEUE_INVALID;
LibKernel::EventQueue::KernelEqueueRef queue;
int id = 0;
};
GraphicContext& m_graphics;
Common::Mutex m_mutex;
CommandScheduler m_command_scheduler;
PipelineCache m_pipeline_cache;
DescriptorCache m_descriptor_cache;
FramebufferCache m_framebuffer_cache;
SamplerCache m_sampler_cache;
GdsBuffer m_gds_buffer;
GpuResourceManager m_gpu_resources;
GraphicContext& m_graphics;
Common::Mutex m_mutex;
RenderExecutor m_render_executor;
CommandScheduler m_command_scheduler;
DescriptorCache m_descriptor_cache;
PipelineCache m_pipeline_cache;
SamplerCache m_sampler_cache;
GpuResourceManager m_gpu_resources;
std::unique_ptr<Gpu> m_gpu;
VideoOut::VideoOutDriver* m_video_out = nullptr;
Common::Mutex m_eop_mutex;
std::vector<EopEqRegistration> m_eop_eqs;
};
[[nodiscard]] RenderContext& GetRenderContext() noexcept;
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERCONTEXT_H_
+271 -264
View File
@@ -13,18 +13,15 @@
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/colorRenderTarget.h"
#include "graphics/host_gpu/renderer/debug.h"
#include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/descriptorCache.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ShaderIR.h"
@@ -36,6 +33,7 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cmath>
#include <cstring>
#include <limits>
@@ -72,13 +70,9 @@ static std::atomic<uint32_t> g_mrt_state_log_count = 0;
static std::atomic<uint32_t> g_shader_stage_log_count = 0;
static std::atomic<uint32_t> g_framebuffer_skip_log_count = 0;
static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset);
static const char* RenderColorTypeName(RenderColorType type) {
switch (type) {
case RenderColorType::NoColorOutput: return "NoColorOutput";
case RenderColorType::DisplayBuffer: return "DisplayBuffer";
case RenderColorType::RenderTexture: return "RenderTexture";
default: return "Unknown";
}
@@ -107,8 +101,8 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
" color_image=%s depth_format=%s depth_image=%s depth_vaddr_num=%d target_mask=0x%08" PRIx32
" prim=%u index_count=%u flags=0x%08" PRIx32 "\n",
log_id, draw_name, RenderColorTypeName(color.type), color.base_addr, color.buffer_size,
color.vulkan_buffer != nullptr ? "yes" : "no", VulkanToString(depth.format).c_str(),
depth.vulkan_buffer != nullptr ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
color.image_id ? "yes" : "no", VulkanToString(depth.format).c_str(),
depth.image_id ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
ucfg.GetPrimType(), index_count, flags);
}
@@ -183,7 +177,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
}
auto log_id = g_draw_state_log_count.fetch_add(1);
if (log_id >= 192 && color.type != RenderColorType::DisplayBuffer) {
if (log_id >= 192) {
return;
}
@@ -199,7 +193,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint;
});
vk::Extent2D extent = color.vulkan_buffer != nullptr ? color.extent : vk::Extent2D {};
vk::Extent2D extent = color.image_id ? color.extent : vk::Extent2D {};
auto sc = calc_final_scissor(vp, ctx.GetScanModeControl(), extent);
LOGF(
@@ -209,12 +203,13 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
" blend=%s src=%u dst=%u comb=%u ps_tex=%d sampled=%d storage=%d ps_kill=%s target_mode0=%u"
" depth_test=%s depth_write=%s depth_func=%u depth_clear=%s viewport=(%.1f,%.1f %.1fx%.1f) "
"scissor=(%d,%d)-(%d,%d)\n",
log_id, GraphicsRunGetFrameNum(), draw_name, RenderColorTypeName(color.type),
color.base_addr, extent.width, extent.height, ucfg.GetPrimType(), index_count, flags,
ctx.GetRenderTargetMask(), color.color_clear_enable ? "true" : "false",
color.color_clear_value.float32[0], color.color_clear_value.float32[1],
color.color_clear_value.float32[2], color.color_clear_value.float32[3], cc.mode, cc.op,
bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name,
RenderColorTypeName(color.type), color.base_addr, extent.width, extent.height,
ucfg.GetPrimType(), index_count, flags, ctx.GetRenderTargetMask(),
color.color_clear_enable ? "true" : "false", color.color_clear_value.float32[0],
color.color_clear_value.float32[1], color.color_clear_value.float32[2],
color.color_clear_value.float32[3], cc.mode, cc.op, bc.enable ? "true" : "false",
bc.color_srcblend, bc.color_destblend, bc.color_comb_fcn,
static_cast<int>(ps_resources.images.size()), static_cast<int>(sampled_images),
static_cast<int>(ps_resources.images.size() - sampled_images),
ps_input_info.ps_pixel_kill_enable ? "true" : "false", ps_input_info.target_output_mode[0],
@@ -226,20 +221,21 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LogMrtState(draw_name, buffer, ps_input_info);
}
static void LogDrawInputState(const RenderColorInfo& color,
static void LogDrawInputState(const RenderCommandBuffer& buffer,
const RenderColorInfo& color,
const ShaderVertexInputInfo& vs_input_info,
uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr) {
auto log_id = g_draw_input_log_count.fetch_add(1);
if (log_id >= 64 && color.type != RenderColorType::DisplayBuffer) {
if (log_id >= 64) {
return;
}
LOGF("DrawInputState[%u]: frame=%d target=%s addr=0x%010" PRIx64
" index_type=%u index_count=%u index_addr=0x%016" PRIx64
" vs_resources=%d vs_buffers=%d\n",
log_id, GraphicsRunGetFrameNum(), RenderColorTypeName(color.type), color.base_addr,
index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr),
log_id, buffer.GetContext().GetGpu().GetFrameNum(), RenderColorTypeName(color.type),
color.base_addr, index_type_and_size, index_count, reinterpret_cast<uint64_t>(index_addr),
vs_input_info.resources_num, vs_input_info.buffers_num);
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
@@ -305,15 +301,6 @@ static void LogDrawInputState(const RenderColorInfo& color,
}
}
static void VulkanCmdSetColorWriteEnableEXT(vk::CommandBuffer command_buffer,
uint32_t attachment_count,
const vk::Bool32* p_color_write_enables) {
if (VULKAN_HPP_DEFAULT_DISPATCHER.vkCmdSetColorWriteEnableEXT == nullptr) {
EXIT("vkCmdSetColorWriteEnableEXT not present\n");
}
command_buffer.setColorWriteEnableEXT(attachment_count, p_color_write_enables);
}
static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandBuffer& buffer,
const RenderColorInfo* colors,
uint32_t color_count,
@@ -327,10 +314,10 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
const auto& vp = ctx.GetScreenViewport();
vk::Extent2D framebuffer_extent {};
if (color_count > 0 && colors[0].vulkan_buffer != nullptr) {
if (color_count > 0 && colors[0].image_id) {
framebuffer_extent = colors[0].extent;
} else if (depth.vulkan_buffer != nullptr) {
framebuffer_extent = depth.vulkan_buffer->extent;
} else if (depth.image_id) {
framebuffer_extent = {depth.width, depth.height};
}
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
@@ -412,7 +399,9 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
for (uint32_t i = 0; i < dynamic_params.color_write_count; i++) {
enable[i] = (dynamic_params.color_write_enable[i] ? VK_TRUE : VK_FALSE);
}
VulkanCmdSetColorWriteEnableEXT(vk_buffer, dynamic_params.color_write_count, enable);
if (dynamic_params.color_write_count != 0) {
vk_buffer.setColorWriteEnableEXT(dynamic_params.color_write_count, enable);
}
}
static bool DrawHasValidVertexShader(const HW::Shader& sh_ctx) {
@@ -488,12 +477,11 @@ struct DrawRenderState {
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
uint32_t color_count = 0;
bool ps_active = true;
VulkanFramebuffer* framebuffer = nullptr;
RenderState rendering;
ShaderVertexInputInfo vs_input_info;
ShaderPixelInputInfo ps_input_info;
std::span<const uint32_t> vs_shader;
std::span<const uint32_t> ps_shader;
std::vector<std::shared_ptr<void>> target_owners;
};
struct DrawCallInfo {
@@ -505,6 +493,123 @@ struct DrawCallInfo {
uint32_t first_instance = 0;
};
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
auto& cache = m_context.GetTextureCache();
RenderState state {};
state.width = std::numeric_limits<uint32_t>::max();
state.height = std::numeric_limits<uint32_t>::max();
state.num_layers = std::numeric_limits<uint32_t>::max();
state.num_color_attachments = color_count;
uint32_t attachment_samples = 0;
for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i];
EXIT_IF(!target.image_id);
const auto old_image = cache.ResolveOwner(target.image_id);
if (old_image == nullptr ||
(!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) {
if (old_image != nullptr) {
old_image->binding = {};
}
target.image_id = cache.FindImage(target.desc);
BindRenderTarget(target.image_id);
}
target.image_view = cache.FindRenderTarget(target.image_id, target.desc);
auto& image = cache.GetImage(target.image_id);
EXIT_IF(image.backing.samples != target.samples || target.image_view == nullptr);
if (attachment_samples == 0) {
attachment_samples = target.samples;
} else if (attachment_samples != target.samples) {
EXIT("mixed color attachment sample counts are unsupported: %u and %u\n",
attachment_samples, target.samples);
}
const auto& view = target.desc.view_info;
const auto layout =
image.binding.is_bound ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal;
image.Transit(layout,
vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits2::eColorAttachmentWrite,
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count},
buffer.Handle());
state.width = std::min(state.width, target.extent.width);
state.height = std::min(state.height, target.extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i];
attachment.image_view = target.image_view;
attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32;
attachment.is_clear = target.color_clear_enable;
}
if (depth.image_id) {
const auto owner = cache.ResolveOwner(depth.image_id);
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
EXIT("depth target changed after render-state discovery\n");
}
depth.image_view = cache.FindDepthTarget(depth.image_id, depth.desc);
if (depth.htile && depth.depth_clear_enable && !cache.ClearMeta(depth.htile_buffer_vaddr)) {
EXIT("failed to acquire HTile metadata for a depth clear\n");
}
depth.depth_meta_clear_enable =
depth.htile &&
cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer);
depth.depth_load_clear_enable =
depth.depth_clear_enable || depth.depth_meta_clear_enable;
if (depth.depth_meta_clear_enable &&
!cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) {
EXIT("failed to consume HTile clear state\n");
}
auto& image = cache.GetImage(depth.image_id);
EXIT_IF(depth.image_view == nullptr || image.backing.samples != depth.samples);
if (attachment_samples == 0) {
attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n",
attachment_samples, depth.samples);
}
const auto layout = depth_attachment_layout(depth);
const auto writes = depth.AttachmentWriteAspects();
auto access = vk::AccessFlags2 {vk::AccessFlagBits2::eDepthStencilAttachmentRead};
if (writes) {
access |= vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
}
const auto& view = depth.desc.view_info;
image.Transit(layout, access,
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count},
buffer.Handle());
state.width = std::min(state.width, depth.width);
state.height = std::min(state.height, depth.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
const auto aspects = ImageViewOps::DepthAspectMask(depth.format);
auto& attachment = state.depth_stencil_attachment;
attachment.image_view = depth.image_view;
attachment.image_layout = layout;
attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value);
attachment.clear_value[1] = depth.stencil_clear_value;
attachment.has_depth =
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
attachment.depth_clear = depth.depth_load_clear_enable;
attachment.has_stencil =
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.stencil_clear = depth.stencil_clear_enable;
}
if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("render state has no valid attachments\n");
}
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
state.num_layers = 1;
}
EXIT_IF(state.width == 0 || state.height == 0 || state.num_layers == 0 ||
state.width == std::numeric_limits<uint32_t>::max() ||
state.height == std::numeric_limits<uint32_t>::max());
return state;
}
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
const DrawRenderState& state, const DrawCallInfo& draw) {
EXIT_IF(draw.name == nullptr);
@@ -512,7 +617,7 @@ static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
const auto& sh_ctx = buffer.GetShaders();
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
state.depth_info.vulkan_buffer != nullptr);
static_cast<bool>(state.depth_info.image_id));
if (state.color_count != 0 || !with_depth) {
return true;
}
@@ -561,13 +666,13 @@ struct DrawIndexBufferSource {
};
struct PreparedIndexBuffer {
std::shared_ptr<VulkanBuffer> buffer;
const void* host_data = nullptr;
std::vector<uint8_t> owned_data;
uint64_t address = 0;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr;
uint64_t address = 0;
uint64_t size = 0;
vk::DeviceSize offset = 0;
vk::IndexType type = vk::IndexType::eUint16;
bool streamed = false;
};
static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer) {
@@ -625,26 +730,16 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw, bool use
return true;
}
static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, uint32_t render_target_slice_offset,
bool skip_null_framebuffer, bool log_setup_phases,
DrawRenderState& state) {
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state) {
EXIT_IF(draw.name == nullptr);
auto& ctx = buffer.GetRegisters();
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
if (ResolveColorTargets(submit_id, buffer, render_target_slice_offset)) {
return false;
}
if (state.depth_info.vulkan_buffer != nullptr) {
state.target_owners.push_back(
GetRenderContext().GetTextureCache().GetImageOwner(*state.depth_info.vulkan_buffer));
}
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderColorTarget");
}
@@ -653,20 +748,18 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
ctx.GetRenderTarget(slot).base.addr != 0)) {
ResolveRenderColorTarget(submit_id, buffer, state.color_info[state.color_count],
render_target_slice_offset, slot);
if (state.color_info[state.color_count].vulkan_buffer != nullptr) {
if (state.color_info[state.color_count].type == RenderColorType::RenderTexture) {
state.target_owners.push_back(GetRenderContext()
.GetTextureCache()
.GetImageOwner(*state.color_info[state.color_count]
.vulkan_buffer));
}
if (state.color_info[state.color_count].image_id) {
state.color_count++;
}
}
}
if (log_setup_phases) {
LogDrawPhase(draw.name, "ResolveRenderDepthTarget");
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
state.depth_info.vulkan_buffer != nullptr);
static_cast<bool>(state.depth_info.image_id));
if (state.color_count == 0 && !with_depth) {
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
draw.index_count, draw.flags);
@@ -674,20 +767,6 @@ static bool PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buff
}
state.ps_active = DrawHasActivePixelShader(buffer, state, draw);
if (log_setup_phases) {
LogDrawPhase(draw.name, "CreateFramebuffer");
}
state.framebuffer = GetRenderContext().GetFramebufferCache().CreateFramebuffer(
state.color_info, state.color_count, state.depth_info);
if (state.framebuffer == nullptr && skip_null_framebuffer) {
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
draw.index_count, draw.flags);
return false;
}
EXIT_NOT_IMPLEMENTED(state.framebuffer == nullptr);
EXIT_NOT_IMPLEMENTED(state.framebuffer->render_pass == nullptr);
return true;
}
@@ -731,9 +810,10 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
}
}
static std::vector<BufferBinding>
PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t submit_id,
RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr);
(void)submit_id;
@@ -744,100 +824,80 @@ PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer, const Draw
const auto& b = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(b);
if (size == 0) {
bindings.push_back({GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0});
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
} else {
bindings.push_back(
GetRenderContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
}
}
return bindings;
}
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info,
std::vector<BufferBinding>& bindings) {
std::vector<BufferBinding>& bindings) {
EXIT_IF(bindings.size() != static_cast<size_t>(vs_input_info.buffers_num));
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
bindings[i] = size == 0
? BufferBinding {GetRenderContext().GetBufferCache().ObtainNullBuffer(), 0}
: GetRenderContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
if (size == 0) {
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings[i] = {owner, owner->Handle(), 0};
} else {
bindings[i] =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
}
}
}
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
const DrawIndexBufferSource& source) {
PreparedIndexBuffer prepared;
if (!source.enabled) {
return prepared;
}
EXIT_IF(source.size == 0);
prepared.host_data = source.host_data;
prepared.address = source.address;
prepared.size = source.size;
prepared.type = source.type;
prepared.address = source.address;
prepared.size = source.size;
prepared.type = source.type;
if (source.host_data != nullptr) {
return prepared;
auto binding =
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
prepared.streamed = true;
} else {
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
buffer, source.address, source.size);
prepared.buffer = std::move(binding.buffer);
prepared.owned_data = std::move(binding.host_data);
prepared.offset = binding.offset;
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
}
return prepared;
}
static void RebindIndexBuffer(RenderCommandBuffer& buffer, PreparedIndexBuffer& prepared) {
if (prepared.size == 0 || prepared.host_data != nullptr) {
if (prepared.size == 0 || prepared.streamed) {
return;
}
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(
buffer, prepared.address, prepared.size);
prepared.buffer = std::move(binding.buffer);
prepared.owned_data = std::move(binding.host_data);
prepared.offset = binding.offset;
}
static void RevalidateDrawTargets(DrawRenderState& state) {
auto old_owners = std::move(state.target_owners);
std::vector<std::shared_ptr<void>> current_owners;
auto validate = [&](VulkanImage& image) {
auto owner = GetRenderContext().GetTextureCache().GetImageOwner(image);
const auto found = std::find_if(old_owners.begin(), old_owners.end(), [&](const auto& old) {
return old.get() == owner.get();
});
EXIT_IF(found == old_owners.end());
current_owners.push_back(std::move(owner));
};
if (state.depth_info.vulkan_buffer != nullptr) {
validate(*state.depth_info.vulkan_buffer);
}
for (uint32_t i = 0; i < state.color_count; i++) {
if (state.color_info[i].type == RenderColorType::RenderTexture) {
validate(*state.color_info[i].vulkan_buffer);
}
}
state.target_owners = std::move(current_owners);
auto binding =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, prepared.address, prepared.size);
prepared.owner = std::move(binding.owner);
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
}
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
std::vector<BufferBinding>& bindings) {
for (uint32_t slot = 0; slot < bindings.size(); slot++) {
auto& binding = bindings[slot];
VulkanBuffer* vertex_buffer = binding.buffer.get();
vk::DeviceSize vertex_offset = binding.offset;
if (!binding.host_data.empty()) {
vk::DeviceSize range = 0;
EXIT_IF(!GetRenderContext().GetBufferCache().UploadHostData(
buffer, binding.host_data.data(), binding.host_data.size(), 16, vertex_buffer,
vertex_offset, range));
} else {
buffer.RetainResourceUntilFence(binding.buffer);
auto& binding = bindings[slot];
if (binding.owner != nullptr) {
buffer.RetainResourceUntilFence(binding.owner);
}
EXIT_IF(vertex_buffer == nullptr);
vk_buffer.bindVertexBuffers(slot, 1, &vertex_buffer->buffer, &vertex_offset);
EXIT_IF(binding.buffer == nullptr);
vk_buffer.bindVertexBuffers(slot, 1, &binding.buffer, &binding.offset);
}
}
@@ -846,23 +906,11 @@ static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_
if (prepared.size == 0) {
return;
}
VulkanBuffer* index_buffer = prepared.buffer.get();
vk::DeviceSize index_offset = prepared.offset;
const void* host_data = prepared.host_data;
if (host_data == nullptr && !prepared.owned_data.empty()) {
host_data = prepared.owned_data.data();
if (prepared.owner != nullptr) {
buffer.RetainResourceUntilFence(prepared.owner);
}
if (host_data != nullptr) {
vk::DeviceSize range = 0;
if (!GetRenderContext().GetBufferCache().UploadHostData(
buffer, host_data, prepared.size, 16, index_buffer, index_offset, range)) {
EXIT("failed to upload host index buffer\n");
}
} else {
buffer.RetainResourceUntilFence(prepared.buffer);
}
EXIT_IF(index_buffer == nullptr);
vk_buffer.bindIndexBuffer(index_buffer->buffer, index_offset, prepared.type);
EXIT_IF(prepared.buffer == nullptr);
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
}
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
@@ -883,7 +931,7 @@ static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCa
LogDrawTargetState(draw.name, state.color_info[0], state.depth_info, buffer,
state.ps_input_info, draw.index_count, draw.flags);
}
LogDrawInputState(state.color_info[0], state.vs_input_info, index_type_and_size,
LogDrawInputState(buffer, state.color_info[0], state.vs_input_info, index_type_and_size,
draw.index_count, index_addr);
// LogDrawTextureState(draw.name, state.color_info[0], state.ps_input_info);
}
@@ -957,63 +1005,35 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
}
}
static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug) {
void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
bool log_pipeline_phase, bool set_bind_debug,
bool set_auto_debug) {
EXIT_IF(draw.name == nullptr);
auto& ucfg = buffer.GetUserConfig();
LogDrawPhase(draw.name, "PrepareBindings");
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage,
state.ps_input_info.stage, state.ps_active);
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
RebindIndexBuffer(buffer, index_binding);
state.rendering =
AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info);
if (log_pipeline_phase) {
LogDrawPhase(draw.name, "CreatePipeline");
}
auto& pipeline = GetRenderContext().GetPipelineCache().CreateGraphicsPipeline(
*state.framebuffer, state.color_info, state.color_count, state.depth_info,
state.vs_input_info, buffer, &state.ps_input_info, topology, state.ps_active,
state.vs_shader, state.ps_shader);
LogDrawPhase(draw.name, "PrepareBindingsVS");
auto vs_bindings = PrepareBindings(buffer, state.vs_input_info.stage,
vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex);
std::optional<DescriptorCache::PreparedBindings> ps_bindings;
if (state.ps_active) {
LogDrawPhase(draw.name, "PrepareBindingsPS");
ps_bindings.emplace(PrepareBindings(buffer, state.ps_input_info.stage,
vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel));
}
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source);
// The discovery pass above can merge cache allocations. Rebind every buffer only after the
// complete draw resource set is known.
RebindBuffers(buffer, vs_bindings);
if (ps_bindings.has_value()) {
RebindBuffers(buffer, *ps_bindings);
}
RebindVertexBuffers(buffer, state.vs_input_info, vertex_bindings);
RebindIndexBuffer(buffer, index_binding);
RebindImages(buffer, vs_bindings);
if (ps_bindings.has_value()) {
RebindImages(buffer, *ps_bindings);
}
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
&state.ps_input_info, topology, state.ps_active, state.vs_shader, state.ps_shader);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
// memory.
RevalidateDrawTargets(state);
DescriptorCache::PreparedBindings* binding_sets[] = {
&vs_bindings, ps_bindings.has_value() ? &*ps_bindings : nullptr};
ActivateImageWrites(binding_sets);
MarkRenderTargetGpuWritten(state.depth_info);
for (uint32_t i = 0; i < state.color_count; i++) {
MarkRenderTargetGpuWritten(state.color_info[i]);
}
for (const auto& owner: state.target_owners) {
buffer.RetainResourceUntilFence(owner);
}
auto vk_buffer = buffer.Handle();
if (set_bind_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
@@ -1023,26 +1043,25 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
}
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
vs_bindings);
if (ps_bindings.has_value()) {
bindings.vertex);
if (bindings.pixel.has_value()) {
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
}
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
*ps_bindings);
*bindings.pixel);
}
CommitIndexBuffer(buffer, vk_buffer, index_binding);
const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info,
state.color_count, state.depth_info);
const auto dynamic_params =
BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info);
SetDynamicParams(buffer, vk_buffer, dynamic_params);
LogDrawPhase(draw.name, "BeginRenderPass");
LogDrawPhase(draw.name, "BeginRendering");
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
}
buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count,
state.depth_info);
m_context.GetCommandScheduler().BeginRendering(state.rendering);
vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
@@ -1052,7 +1071,6 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
}
buffer.EndRenderPass();
vk::PipelineStageFlags shader_write_stages = {};
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader;
@@ -1061,18 +1079,20 @@ static void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
}
if (shader_write_stages) {
m_context.GetCommandScheduler().EndRendering();
ShaderWriteBarrier(vk_buffer, shader_write_stages);
}
LogDrawPhase(draw.name, "EndRenderPass");
LogDrawPhase(draw.name, "DrawComplete");
if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
}
}
void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& 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, uint32_t render_target_slice_offset,
int32_t vertex_offset_add, uint32_t first_instance) {
void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& 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, uint32_t render_target_slice_offset,
int32_t vertex_offset_add, uint32_t first_instance) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
@@ -1083,13 +1103,13 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
index_count, flags, type, instance_count,
reinterpret_cast<uint64_t>(index_addr));
Common::LockGuard lock(GetRenderContext().GetMutex());
Common::LockGuard lock(m_context.GetMutex());
if (index_count == 0) {
return;
}
if (ConsumeMetadataColorOperation(buffer)) {
ResetBindings();
return;
}
@@ -1181,8 +1201,8 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
index_source.type = index_type;
DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, true,
state)) {
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, state)) {
ResetBindings();
return;
}
@@ -1199,12 +1219,15 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, true, true,
false);
ResetBindings();
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) {
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex,
uint32_t first_instance) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
@@ -1214,13 +1237,13 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
buffer.SetDebugInfo(static_cast<uint32_t>(CommandBufferDebugOp::DrawIndexAuto), submit_id,
index_count, flags, first_vertex, instance_count, first_instance);
Common::LockGuard lock(GetRenderContext().GetMutex());
Common::LockGuard lock(m_context.GetMutex());
if (index_count == 0) {
return;
}
if (ConsumeMetadataColorOperation(buffer)) {
ResetBindings();
return;
}
@@ -1262,8 +1285,8 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
instance_count, first_instance};
DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, true, false,
state)) {
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, state)) {
ResetBindings();
return;
}
@@ -1271,6 +1294,7 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
const bool use_ngg_rectlist_draw = Config::NggRectlistDrawEnabled();
if (!GetDrawTopology(ucfg, true, use_ngg_rectlist_draw, topology)) {
ResetBindings();
return;
}
const bool draw_prim7_as_ngg =
@@ -1287,6 +1311,7 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
state.ps_input_info.input_num, sh_ctx.GetPs().ps_regs.chksum,
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
}
ResetBindings();
return;
}
@@ -1306,27 +1331,11 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
DrawIndexBufferSource index_source {};
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
true);
ResetBindings();
}
bool IsSameColorResolveSubresource(const RenderColorInfo& src, const RenderColorInfo& dst) {
return src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
src.base_array_layer == dst.base_array_layer;
}
ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src, const RenderColorInfo& dst,
uint32_t width, uint32_t height) {
ImageImageCopy region {*src.vulkan_buffer};
region.src_level = src.base_mip_level;
region.dst_level = dst.base_mip_level;
region.width = width;
region.height = height;
region.src_layer = src.base_array_layer;
region.dst_layer = dst.base_array_layer;
return region;
}
static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset) {
bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
uint32_t render_target_slice_offset) {
const auto& hw = buffer.GetRegisters();
if (hw.GetColorControl().mode != 3) {
return false;
@@ -1341,25 +1350,23 @@ static bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo src {};
RenderColorInfo dst {};
ResolveRenderColorTarget(submit_id, buffer, src, render_target_slice_offset, 0, true, true);
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true);
if (src.vulkan_buffer == nullptr || dst.vulkan_buffer == nullptr ||
src.type == RenderColorType::NoColorOutput || dst.type == RenderColorType::NoColorOutput) {
ResolveRenderColorTarget(submit_id, buffer, dst, render_target_slice_offset, 1, true, true);
if (!src.image_id || !dst.image_id || src.type == RenderColorType::NoColorOutput ||
dst.type == RenderColorType::NoColorOutput) {
return false;
}
if (IsSameColorResolveSubresource(src, dst)) {
if (src.base_addr == dst.base_addr && src.base_mip_level == dst.base_mip_level &&
src.base_array_layer == dst.base_array_layer) {
return true;
}
const uint32_t width = std::min(src.extent.width, dst.extent.width);
const uint32_t height = std::min(src.extent.height, dst.extent.height);
if (width == 0 || height == 0) {
return false;
}
const std::array regions {MakeColorResolveCopy(src, dst, width, height)};
MarkRenderTargetGpuWritten(dst);
Transfer::CopyImage(buffer, regions, *dst.vulkan_buffer, dst.vulkan_buffer->layout);
auto& cache = m_context.GetTextureCache();
cache.MarkGpuWritten(dst.image_id);
auto& source = cache.GetImage(src.image_id);
auto& destination = cache.GetImage(dst.image_id);
destination.Resolve(source,
{src.base_mip_level, 1, src.base_array_layer, 1},
{dst.base_mip_level, 1, dst.base_array_layer, 1});
return true;
}
+2 -8
View File
@@ -8,7 +8,6 @@
namespace Libs::Graphics {
struct ImageImageCopy;
struct RenderColorInfo;
struct ShaderVertexInputInfo;
@@ -39,13 +38,8 @@ static_assert(sizeof(PipelineDynamicParameters) ==
sizeof(uint32_t) + sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) +
sizeof(PipelineStencilDynamicState) * 2);
[[nodiscard]] bool IsSameColorResolveSubresource(const RenderColorInfo& src,
const RenderColorInfo& dst);
[[nodiscard]] ImageImageCopy MakeColorResolveCopy(const RenderColorInfo& src,
const RenderColorInfo& dst, uint32_t width,
uint32_t height);
[[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);
} // namespace Libs::Graphics
@@ -3,6 +3,7 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <cstdint>
#include <type_traits>
@@ -10,6 +11,30 @@ namespace Libs::Graphics {
static constexpr uint32_t RENDER_COLOR_ATTACHMENTS_MAX = 8;
struct RenderAttachment {
vk::ImageView image_view = nullptr;
vk::ImageLayout image_layout = vk::ImageLayout::eUndefined;
std::array<uint32_t, 4> clear_value = {};
bool is_clear = false;
bool has_depth = false;
bool depth_clear = false;
bool has_stencil = false;
bool stencil_clear = false;
bool operator==(const RenderAttachment&) const = default;
};
struct RenderState {
std::array<RenderAttachment, RENDER_COLOR_ATTACHMENTS_MAX> color_attachments;
RenderAttachment depth_stencil_attachment;
uint32_t width = 0;
uint32_t height = 0;
uint32_t num_layers = 1;
uint32_t num_color_attachments = 0;
bool operator==(const RenderState&) const = default;
};
[[nodiscard]] inline constexpr uint32_t render_sample_count(uint32_t encoded_samples) {
return encoded_samples <= 3 ? 1u << encoded_samples : 0;
}
@@ -1,221 +0,0 @@
#include "graphics/host_gpu/renderer/renderTargetBarriers.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/threads.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/framebufferCache.h"
#include "graphics/host_gpu/renderer/imageView.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/displayBuffer.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <limits>
namespace Libs::Graphics {
void GraphicsRenderMemoryBarrier(CommandBuffer& buffer) {
EXIT_IF(buffer.IsInvalid());
Common::LockGuard lock(GetRenderContext().GetMutex());
auto vk_buffer = buffer.Handle();
VulkanMemoryBarrier mem_barrier {};
mem_barrier.sType = vk::StructureType::eMemoryBarrier;
mem_barrier.pNext = nullptr;
mem_barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite;
mem_barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eAllCommands, vk::DependencyFlags {}, 1,
&mem_barrier, 0, nullptr, 0, nullptr);
}
void GraphicsRenderTextureBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image) {
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eMemoryRead;
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
image_memory_barrier.oldLayout = image.layout;
image_memory_barrier.newLayout = RENDER_COLOR_IMAGE_LAYOUT;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image.image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = image.layers;
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer |
vk::PipelineStageFlagBits::eFragmentShader,
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
image.layout = image_memory_barrier.newLayout;
}
void GraphicsRenderColorImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout) {
if (image.layout != new_layout) {
if (image.type == VulkanImageType::VideoOut &&
image.layout == vk::ImageLayout::eTransferDstOptimal) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 128) {
LOGF("GraphicsRenderColorImageBarrier: image=%p type=%d layout=%s -> %s\n",
VulkanHandleToPointer(image.image), static_cast<int>(image.type),
VulkanToString(image.layout).c_str(), VulkanToString(new_layout).c_str());
}
}
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead;
image_memory_barrier.dstAccessMask =
vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead;
image_memory_barrier.oldLayout = image.layout;
image_memory_barrier.newLayout = new_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image.image;
image_memory_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = image.layers;
const auto stages = static_cast<vk::PipelineStageFlags>(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer);
vk_buffer.pipelineBarrier(stages, stages, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
image.layout = image_memory_barrier.newLayout;
}
}
void GraphicsRenderDepthStencilImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout) {
EXIT_IF(image.type != VulkanImageType::DepthStencil);
if (new_layout == vk::ImageLayout::eUndefined ||
new_layout == vk::ImageLayout::ePreinitialized) {
EXIT("invalid destination depth/stencil image layout: %s\n",
VulkanToString(new_layout).c_str());
}
if (image.layout == new_layout) {
return;
}
vk::ImageMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eImageMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
barrier.oldLayout = image.layout;
barrier.newLayout = new_layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image.image;
barrier.subresourceRange = {ImageViewOps::DepthAspectMask(image.format), 0,
VK_REMAINING_MIP_LEVELS, 0, image.layers};
const auto stages = static_cast<vk::PipelineStageFlags>(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer);
vk_buffer.pipelineBarrier(stages, stages, vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1,
&barrier);
image.layout = new_layout;
}
void GraphicsRenderDepthStencilBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image) {
EXIT_IF(image.type != VulkanImageType::DepthStencil);
auto& depth = static_cast<DepthStencilVulkanImage&>(image);
if (depth.compressed) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
LOGF(
"DepthTexture: decompressing depth target for shader read format=%s extent=%ux%u\n",
VulkanToString(depth.format).c_str(), depth.extent.width, depth.extent.height);
}
depth.compressed = false;
}
if (image.layout != vk::ImageLayout::eDepthStencilReadOnlyOptimal) {
vk::ImageMemoryBarrier image_memory_barrier {};
image_memory_barrier.sType = vk::StructureType::eImageMemoryBarrier;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
image_memory_barrier.oldLayout = image.layout;
image_memory_barrier.newLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image.image;
image_memory_barrier.subresourceRange.aspectMask =
ImageViewOps::DepthAspectMask(image.format);
image_memory_barrier.subresourceRange.baseMipLevel = 0;
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
image_memory_barrier.subresourceRange.layerCount = image.layers;
vk_buffer.pipelineBarrier(
vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader |
vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader |
vk::PipelineStageFlagBits::eComputeShader,
vk::DependencyFlags {}, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
image.layout = image_memory_barrier.newLayout;
}
}
void GraphicsRenderTextureBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size) {
EXIT_IF(buffer.IsInvalid());
Common::LockGuard lock(GetRenderContext().GetMutex());
auto vk_buffer = buffer.Handle();
auto* native = GetRenderContext().GetTextureCache().FindRenderTargetByRange(buffer, vaddr, size);
if (native == nullptr) {
EXIT("render-target barrier range has no cached image\n");
}
GraphicsRenderTextureBarrier(vk_buffer, *native);
}
void GraphicsRenderDepthStencilBarrier(CommandBuffer& buffer, uint64_t vaddr, uint64_t size) {
EXIT_IF(buffer.IsInvalid());
Common::LockGuard lock(GetRenderContext().GetMutex());
auto vk_buffer = buffer.Handle();
auto* native = GetRenderContext().GetTextureCache().FindDepthTargetByRange(buffer, vaddr, size);
if (native == nullptr) {
EXIT("depth-target barrier range has no cached image\n");
}
GraphicsRenderDepthStencilBarrier(vk_buffer, *native);
}
} // namespace Libs::Graphics
@@ -1,19 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
#include "graphics/host_gpu/vulkanCommon.h"
namespace Libs::Graphics {
struct VulkanImage;
void GraphicsRenderTextureBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image);
void GraphicsRenderColorImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout);
void GraphicsRenderDepthStencilImageBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image,
vk::ImageLayout new_layout);
void GraphicsRenderDepthStencilBarrier(vk::CommandBuffer vk_buffer, VulkanImage& image);
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RENDERTARGETBARRIERS_H_
@@ -7,6 +7,13 @@
namespace Libs::Graphics {
SamplerCache::~SamplerCache() {
for (const auto& [key, sampler]: m_samplers) {
(void)key;
m_graphics.device.destroySampler(sampler, nullptr);
}
}
vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
Common::LockGuard lock(m_mutex);
@@ -21,7 +21,7 @@ public:
explicit SamplerCache(GraphicContext& graphics): m_graphics(graphics) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
}
~SamplerCache() { KYTY_NOT_IMPLEMENTED; }
~SamplerCache();
KYTY_CLASS_NO_COPY(SamplerCache);
vk::Sampler GetSampler(const ShaderSamplerResource& r);
@@ -36,36 +36,8 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
return barrier;
}
vk::ImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read,
bool written) {
EXIT_IF(image.image == nullptr || image.type == VulkanImageType::DepthStencil);
vk::ImageMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eImageMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryWrite;
barrier.dstAccessMask = {};
if (read) {
barrier.dstAccessMask |= vk::AccessFlagBits::eShaderRead;
}
if (written) {
barrier.dstAccessMask |= vk::AccessFlagBits::eShaderWrite;
}
barrier.oldLayout = image.layout;
barrier.newLayout = vk::ImageLayout::eGeneral;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image.image;
barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = image.layers;
EXIT_IF(!barrier.dstAccessMask || barrier.subresourceRange.layerCount == 0);
return barrier;
}
vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
EXIT_IF(buffer.buffer == nullptr);
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
EXIT_IF(buffer == nullptr);
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
@@ -74,7 +46,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer) {
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer.buffer;
barrier.buffer = buffer;
barrier.offset = 0;
barrier.size = VK_WHOLE_SIZE;
return barrier;
@@ -19,9 +19,7 @@ struct ShaderBufferWriteRange {
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderWriteDependency();
vk::ImageMemoryBarrier MakeStorageImageDependency(const VulkanImage& image, bool read,
bool written);
vk::BufferMemoryBarrier MakeGdsDependency(const VulkanBuffer& buffer);
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
+31 -20
View File
@@ -385,7 +385,9 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
return vk::BlendOp::eAdd;
}
static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_t& set_layouts_num,
static void CreateLayout(DescriptorCache& descriptor_cache,
std::span<vk::DescriptorSetLayout> set_layouts,
uint32_t& set_layouts_num,
std::span<vk::PushConstantRange> push_constant_info,
uint32_t& push_constant_info_num,
const ShaderRecompiler::IR::Program& program,
@@ -405,17 +407,16 @@ static void CreateLayout(std::span<vk::DescriptorSetLayout> set_layouts, uint32_
if (need_descriptor) {
EXIT_IF(bindings.descriptor_set != set_layouts_num);
set_layouts[set_layouts_num] =
GetRenderContext().GetDescriptorCache().GetDescriptorSetLayout(stage, program);
set_layouts[set_layouts_num] = descriptor_cache.GetDescriptorSetLayout(stage, program);
set_layouts_num++;
}
}
static void ConfigureSubgroupSize(vk::ShaderStageFlagBits vk_stage,
static void ConfigureSubgroupSize(const GraphicContext& graphics,
vk::ShaderStageFlagBits vk_stage,
const ShaderRecompiler::IR::Program& program,
vk::PipelineShaderStageRequiredSubgroupSizeCreateInfo& required,
vk::PipelineShaderStageCreateInfo& stage) {
const auto& graphics = GetRenderContext().GetGraphics();
const auto config =
ConfigureShaderSubgroup(ShaderSubgroupCapabilities {graphics}, vk_stage, program);
switch (config.mode) {
@@ -455,7 +456,9 @@ static void ConfigureSubgroupSize(vk::ShaderStageFlagBits
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::RenderPass render_pass,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::GraphicsPipeline& pipeline,
const PipelineRenderingState& rendering,
const ShaderVertexInputInfo& vs_input_info,
std::span<const uint32_t> vs_shader,
const ShaderPixelInputInfo* ps_input_info,
@@ -463,11 +466,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
const PipelineStaticParameters& static_params, uint32_t vs_hash0,
uint32_t vs_crc32, uint32_t ps_hash0, uint32_t ps_crc32,
bool ps_active) {
EXIT_IF(render_pass == nullptr);
EXIT_IF(ps_active && ps_input_info == nullptr);
auto& graphics = GetRenderContext().GetGraphics();
vk::ShaderModule vert_shader_module = nullptr;
vk::ShaderModule frag_shader_module = nullptr;
@@ -511,7 +511,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
vert_shader_stage_info.pName = "main";
vert_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!vs_input_info.stage);
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eVertex, *vs_input_info.stage.program,
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eVertex,
*vs_input_info.stage.program,
vert_subgroup_size, vert_shader_stage_info);
vk::PipelineShaderStageCreateInfo frag_shader_stage_info {};
@@ -525,7 +526,8 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
frag_shader_stage_info.pSpecializationInfo = nullptr;
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eFragment, *ps_input_info->stage.program,
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eFragment,
*ps_input_info->stage.program,
frag_subgroup_size, frag_shader_stage_info);
}
@@ -823,12 +825,14 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
uint32_t push_constant_info_num = 0;
EXIT_IF(!vs_input_info.stage);
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num,
*vs_input_info.stage.program, vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex);
if (ps_active) {
EXIT_IF(!ps_input_info->stage);
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num,
*ps_input_info->stage.program, vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel);
}
@@ -902,8 +906,14 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
dynamic_state.pDynamicStates = dynamic_states;
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 = nullptr;
pipeline_info.pNext = &rendering_info;
pipeline_info.flags = {};
pipeline_info.stageCount = shader_stage_count;
pipeline_info.pStages = shader_stages;
@@ -917,7 +927,7 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state;
pipeline_info.layout = pipeline.pipeline_layout;
pipeline_info.renderPass = render_pass;
pipeline_info.renderPass = nullptr;
pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = nullptr;
pipeline_info.basePipelineIndex = -1;
@@ -952,11 +962,10 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
void CreatePipelineInternal(GraphicContext& graphics, DescriptorCache& descriptor_cache,
PipelineCache::ComputePipeline& pipeline,
const ShaderComputeInputInfo& input_info,
std::span<const uint32_t> cs_shader) {
auto& graphics = GetRenderContext().GetGraphics();
vk::ShaderModule comp_shader_module = nullptr;
vk::ShaderModuleCreateInfo create_info {};
@@ -985,7 +994,8 @@ void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
comp_shader_stage_info.pName = "main";
comp_shader_stage_info.pSpecializationInfo = nullptr;
EXIT_IF(!input_info.stage);
ConfigureSubgroupSize(vk::ShaderStageFlagBits::eCompute, *input_info.stage.program,
ConfigureSubgroupSize(graphics, vk::ShaderStageFlagBits::eCompute,
*input_info.stage.program,
comp_subgroup_size, comp_shader_stage_info);
vk::DescriptorSetLayout set_layouts[1] = {};
@@ -995,7 +1005,8 @@ void CreatePipelineInternal(PipelineCache::ComputePipeline& pipeline,
uint32_t push_constant_info_num = 0;
EXIT_IF(!input_info.stage);
CreateLayout(set_layouts, set_layouts_num, push_constant_info, push_constant_info_num,
CreateLayout(descriptor_cache, set_layouts, set_layouts_num, push_constant_info,
push_constant_info_num,
*input_info.stage.program, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute);
+328 -63
View File
@@ -1,95 +1,360 @@
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/profiler.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/vma.h"
#include <atomic>
#include <cstring>
#include <limits>
#include <numeric>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
namespace {
std::atomic<uint64_t> g_stream_buffer_count {0};
} // namespace
HostStreamBuffer::HostStreamBuffer(GraphicContext& graphics): m_graphics(graphics) {}
constexpr size_t WATCHES_INITIAL_RESERVE = 0x4000;
constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
HostStreamBuffer::~HostStreamBuffer() {
Release();
[[nodiscard]] VmaAllocationCreateFlags AllocationFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal: return {};
}
return {};
}
void HostStreamBuffer::EnsureBuffer() {
if (m_buffer != nullptr) {
if (m_mapped == nullptr) {
EXIT("host stream buffer lost its mapped storage\n");
}
return;
[[nodiscard]] VmaMemoryUsage AllocationUsage(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream: return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download: return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
KYTY_PROFILER_BLOCK("HostStreamBuffer::create");
m_buffer = std::make_unique<VulkanBuffer>();
m_buffer->usage = vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eIndexBuffer |
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst;
m_buffer->memory.property = vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent |
vk::MemoryPropertyFlagBits::eHostCached;
m_graphics.CreateBuffer(CAPACITY, *m_buffer);
m_graphics.MapMemory(m_buffer->memory, m_mapped);
const auto count = g_stream_buffer_count.fetch_add(1, std::memory_order_relaxed) + 1;
LOGF("HostStreamBuffer: created serial=%" PRIu64 " capacity=%" PRIu64 "\n", count, CAPACITY);
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
}
bool HostStreamBuffer::Copy(const void* src, uint64_t size, uint64_t alignment,
VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range) {
KYTY_PROFILER_FUNCTION();
if (src == nullptr) {
EXIT("invalid host stream upload\n");
}
std::lock_guard lock(m_mutex);
if (size == 0) {
return false;
}
[[nodiscard]] bool AlignUp(uint64_t value, uint64_t alignment, uint64_t& result) {
if (alignment == 0) {
alignment = 1;
result = value;
return true;
}
if ((alignment & (alignment - 1)) != 0 || m_offset > UINT64_MAX - (alignment - 1)) {
const auto remainder = value % alignment;
if (remainder == 0) {
result = value;
return true;
}
const auto increment = alignment - remainder;
if (value > std::numeric_limits<uint64_t>::max() - increment) {
return false;
}
const auto offset = (m_offset + alignment - 1) & ~(alignment - 1);
if (offset > CAPACITY || size > CAPACITY - offset) {
return false;
}
EnsureBuffer();
std::memcpy(static_cast<uint8_t*>(m_mapped) + offset, src, static_cast<size_t>(size));
m_offset = offset + size;
out_buffer = m_buffer.get();
out_offset = offset;
out_range = size;
result = value + increment;
return true;
}
void HostStreamBuffer::Reset() noexcept {
std::lock_guard lock(m_mutex);
m_offset = 0;
} // namespace
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>()) {
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;
VmaAllocationCreateInfo allocation_info {};
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
allocation_info.usage = AllocationUsage(usage);
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
? VkMemoryPropertyFlags {}
: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
VmaAllocationInfo allocation_result {};
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));
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);
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;
if (allocation_result.pMappedData != nullptr) {
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
static_cast<size_t>(size)};
}
VulkanTrackAllocation(m_buffer->memory);
}
void HostStreamBuffer::Release() noexcept {
std::lock_guard lock(m_mutex);
if (m_buffer != nullptr) {
if (m_mapped == nullptr) {
EXIT("invalid host stream buffer release state\n");
}
m_graphics.UnmapMemory(m_buffer->memory);
m_graphics.DeleteBuffer(*m_buffer);
m_buffer.reset();
Buffer::~Buffer() {
if (m_buffer->buffer != nullptr) {
VulkanUntrackAllocation(m_buffer->memory);
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
}
m_mapped = nullptr;
m_offset = 0;
}
vk::Buffer Buffer::Handle() const noexcept {
return m_buffer->buffer;
}
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;
}
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);
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) {
const auto result = vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.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) {
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);
}
vk::BufferMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
barrier.srcAccessMask = source;
barrier.dstAccessMask = destination;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = Handle();
barrier.offset = offset;
barrier.size = size;
return barrier;
}
void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
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) {
EXIT("Buffer: invalid copy range\n");
}
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
destination_offset < source_offset + size) {
EXIT("Buffer: overlapping self-copy\n");
}
command.EndRendering();
const vk::BufferMemoryBarrier before[] = {
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
Barrier(destination_offset, size, destination_before,
vk::AccessFlagBits::eTransferWrite),
};
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_before | destination_before) & host_access)) {
before_stage |= vk::PipelineStageFlagBits::eHost;
}
const auto native = command.Handle();
native.pipelineBarrier(before_stage, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr);
const vk::BufferCopy copy {source_offset, destination_offset, size};
native.copyBuffer(source.Handle(), Handle(), 1, &copy);
const vk::BufferMemoryBarrier after[] = {
source.Barrier(source_offset, size, vk::AccessFlagBits::eTransferRead, source_after),
Barrier(destination_offset, size, vk::AccessFlagBits::eTransferWrite, destination_after),
};
auto after_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
if (static_cast<bool>((source_after | destination_after) & host_access)) {
after_stage |= vk::PipelineStageFlagBits::eHost;
}
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, after_stage,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, after, 0, nullptr);
}
void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
if (((offset | size) & 3u) != 0) {
EXIT("Buffer: fill range must be dword aligned\n");
}
auto& command = Scheduler().Current();
command.EndRendering();
const auto before =
Barrier(offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eTransferWrite);
const auto native = command.Handle();
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
0, nullptr, 1, &before, 0, nullptr);
native.fillBuffer(Handle(), offset, size, value);
const auto after =
Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
}
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);
}
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
uint64_t& alignment) {
if (coherent) {
return true;
}
if (!AlignUp(size, atom, size)) {
return false;
}
const auto divisor = std::gcd(alignment, atom);
if (alignment != 0 && alignment / divisor > UINT64_MAX / atom) {
return false;
}
alignment = alignment == 0 ? atom : alignment / divisor * atom;
return true;
}
std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignment,
bool allow_wait) {
if (Mapped().empty()) {
return {nullptr, 0};
}
uint64_t mapped_size = size;
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
return {nullptr, 0};
}
if (mapped_size > Size()) {
return {nullptr, 0};
}
uint64_t aligned_offset = 0;
if (!AlignUp(m_offset, alignment, aligned_offset)) {
return {nullptr, 0};
}
const bool wrap = aligned_offset > Size() - mapped_size;
if (wrap) {
aligned_offset = 0;
}
auto wait_cursor = wrap ? size_t {0} : m_wait_cursor;
auto wait_bound = wrap ? uint64_t {0} : m_wait_bound;
auto invalidation_mark =
wrap ? std::optional<size_t> {m_current_watch_cursor} : m_invalidation_mark;
auto& pending_watches = wrap ? m_current_watches : m_previous_watches;
if (!WaitPendingOperations(pending_watches, invalidation_mark, aligned_offset + mapped_size,
allow_wait, wait_cursor, wait_bound)) {
return {nullptr, 0};
}
if (wrap) {
m_invalidation_mark = invalidation_mark;
m_current_watch_cursor = 0;
std::swap(m_previous_watches, m_current_watches);
}
m_wait_cursor = wait_cursor;
m_wait_bound = wait_bound;
m_offset = aligned_offset;
m_mapped_size = mapped_size;
return {Mapped().data() + m_offset, m_offset};
}
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);
}
m_offset += m_mapped_size;
const auto tick = Scheduler().CurrentTick();
if (m_current_watch_cursor != 0 && m_current_watches[m_current_watch_cursor - 1].tick == tick) {
m_current_watches[m_current_watch_cursor - 1].upper_bound = m_offset;
return;
}
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
}
auto& watch = m_current_watches[m_current_watch_cursor++];
watch.upper_bound = m_offset;
watch.tick = tick;
}
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
if (IsCoherent() || size == 0) {
return;
}
const auto result = vmaInvalidateAllocation(Graphics().allocator,
NativeBuffer().memory.allocation, offset, size);
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
}
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
EXIT_IF(source == nullptr);
const auto [data, offset] = Map(size, alignment);
EXIT_IF(data == nullptr);
std::memcpy(data, source, static_cast<size_t>(size));
Commit();
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,
size_t& wait_cursor, uint64_t& wait_bound) {
if (!invalidation_mark.has_value()) {
return true;
}
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
const auto& watch = watches[wait_cursor];
if (!Scheduler().IsFree(watch.tick) && !allow_wait) {
return false;
}
Scheduler().Wait(watch.tick);
if (Usage() == MemoryUsage::Download) {
Scheduler().WaitPriorityOperations(watch.tick);
}
wait_bound = watch.upper_bound;
++wait_cursor;
}
return true;
}
} // namespace Libs::Graphics
+106 -23
View File
@@ -3,43 +3,126 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
class CommandBuffer;
class CommandScheduler;
struct StreamBufferTestAccess;
struct GraphicContext;
struct VulkanBuffer;
// Host-only mapped stream mechanism. BufferCache owns the upload policy/API; one backend remains
// attached to each CommandBuffer so Reset is provably after that command's fence. Replacing this
// backend with scheduler watches will not change descriptor/render call sites.
class HostStreamBuffer final {
public:
explicit HostStreamBuffer(GraphicContext& graphics);
~HostStreamBuffer();
KYTY_CLASS_NO_COPY(HostStreamBuffer);
enum class MemoryUsage : uint8_t {
DeviceLocal,
Upload,
Download,
Stream,
};
[[nodiscard]] bool Copy(const void* src, uint64_t size, uint64_t alignment,
VulkanBuffer*& out_buffer, uint64_t& out_offset, uint64_t& out_range);
void Reset() noexcept;
void Release() noexcept;
inline constexpr vk::BufferUsageFlags ReadFlags =
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eUniformBuffer |
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eIndirectBuffer;
inline constexpr vk::BufferUsageFlags AllFlags =
ReadFlags | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eStorageBuffer;
class Buffer {
public:
Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size);
~Buffer();
KYTY_CLASS_NO_COPY(Buffer);
[[nodiscard]] vk::Buffer Handle() const noexcept;
[[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]] MemoryUsage Usage() const noexcept { return m_usage; }
[[nodiscard]] uint64_t CpuAddress() const noexcept { return m_cpu_address; }
[[nodiscard]] uint64_t Offset(uint64_t address) const noexcept {
return address - m_cpu_address;
}
[[nodiscard]] bool IsInBounds(uint64_t address, uint64_t size) const noexcept;
void Write(uint64_t offset, const void* source, uint64_t size);
void Flush(uint64_t offset, uint64_t size);
void CopyFrom(
CommandBuffer& command, const Buffer& source, uint64_t source_offset,
uint64_t destination_offset, uint64_t size,
vk::AccessFlags source_before = vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_before =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags source_after =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlags destination_after =
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
void Fill(uint64_t offset, uint64_t size, uint32_t value);
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:
void EnsureBuffer();
[[nodiscard]] vk::BufferMemoryBarrier Barrier(uint64_t offset, uint64_t size,
vk::AccessFlags source,
vk::AccessFlags destination) const;
// Interim fence-scoped backend: one command processor's eight live command buffers reserve up
// to 128 MiB lazily. Additional active processors scale that aggregate until this backend is
// replaced by a single 64 MiB scheduler-watched ring; the BufferCache seam stays fixed.
static constexpr uint64_t CAPACITY = 16ull * 1024ull * 1024ull;
mutable std::mutex m_mutex;
GraphicContext& m_graphics;
GraphicContext* m_graphics = nullptr;
CommandScheduler* m_scheduler = nullptr;
MemoryUsage m_usage = MemoryUsage::DeviceLocal;
uint64_t m_cpu_address = 0;
uint64_t m_size = 0;
std::unique_ptr<VulkanBuffer> m_buffer;
void* m_mapped = nullptr;
uint64_t m_offset = 0;
std::span<uint8_t> m_mapped;
bool m_is_coherent = false;
};
class StreamBuffer final: public Buffer {
public:
StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
uint64_t size);
[[nodiscard]] std::pair<uint8_t*, uint64_t> Map(uint64_t size, uint64_t alignment = 0,
bool allow_wait = true);
void Commit();
// Download mappings become visible to the CPU only after their GPU completion tick is free.
// Call this from the scheduler's deferred completion operation before reading Mapped().
void Invalidate(uint64_t offset, uint64_t size);
[[nodiscard]] uint64_t Copy(const void* source, uint64_t size, uint64_t alignment = 0);
private:
friend struct StreamBufferTestAccess;
struct Watch {
uint64_t tick = 0;
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,
std::optional<size_t> invalidation_mark,
uint64_t requested_upper_bound, bool allow_wait,
size_t& wait_cursor, uint64_t& wait_bound);
uint64_t m_offset = 0;
uint64_t m_mapped_size = 0;
std::vector<Watch> m_current_watches;
size_t m_current_watch_cursor = 0;
std::optional<size_t> m_invalidation_mark;
std::vector<Watch> m_previous_watches;
size_t m_wait_cursor = 0;
uint64_t m_wait_bound = 0;
};
} // namespace Libs::Graphics
+47 -55
View File
@@ -5,16 +5,16 @@
#include "common/logging/log.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/label.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/presentation/displayBuffer.h"
#include "graphics/presentation/videoOut.h"
#include "kernel/eventQueue.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include <array>
#include <cstring>
#include <limits>
#include <optional>
@@ -57,19 +57,6 @@ uint64_t ReadReferenceClock() {
return value;
}
static void SubmitLabel(CommandBuffer& buffer, LabelCallback callback_1 = nullptr,
LabelCallback callback_2 = nullptr, const uint64_t* args = nullptr) {
auto* label = LabelCreate(callback_1, callback_2, args);
LabelSet(buffer, *label);
LabelDelete(*label);
}
static bool CompleteDisplayBufferFlip(const uint64_t* args) {
EXIT_IF(args == nullptr);
Presentation::DisplayBufferCompleteFlipFromGpu(args[0]);
return true;
}
enum class EndOfPipeCompletion { None, Interrupt, Flip, FlipAndInterrupt };
struct EndOfPipeSignal {
@@ -86,17 +73,6 @@ struct EndOfPipeSignal {
enum class EndOfPipeWriteSize : uint32_t { Dword = 4, Qword = 8 };
enum class EndOfPipeWriteAction { Write, WriteBack, Interrupt, InterruptWriteBack };
static bool TriggerEopEventCallback(const uint64_t* args) {
EXIT_IF(args == nullptr);
GetRenderContext().TriggerEopEvent(static_cast<uint32_t>(args[0]));
return true;
}
static bool TriggerDefaultEopEventCallback(const uint64_t* /*args*/) {
GetRenderContext().TriggerEopEvent(0);
return true;
}
static void ValidateEndOfPipeSignal(const EndOfPipeSignal& signal) {
if (signal.destination.has_value()) {
EXIT_IF(*signal.destination == 0);
@@ -111,19 +87,33 @@ static void RecordEndOfPipeSignal(const EndOfPipeSignal& signal) {
signal.debug_args[0], signal.debug_args[1], signal.debug_args[2],
signal.debug_args[3], signal.debug_data);
const uint64_t args[LABEL_ARGS_MAX] = {signal.completion_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:
SubmitLabel(*signal.buffer, nullptr, TriggerEopEventCallback, args);
case EndOfPipeCompletion::Interrupt: {
const auto context_id = static_cast<uint32_t>(signal.completion_data);
scheduler.DeferPriorityOperation(
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
return;
case EndOfPipeCompletion::Flip:
SubmitLabel(*signal.buffer, CompleteDisplayBufferFlip, nullptr, args);
}
case EndOfPipeCompletion::Flip: {
const auto request_id = signal.completion_data;
scheduler.DeferPriorityOperation(
[&renderer, request_id] { renderer.GetVideoOut().CompleteFlip(request_id); });
return;
case EndOfPipeCompletion::FlipAndInterrupt:
SubmitLabel(*signal.buffer, CompleteDisplayBufferFlip, TriggerDefaultEopEventCallback,
args);
}
case EndOfPipeCompletion::FlipAndInterrupt: {
const auto request_id = signal.completion_data;
scheduler.DeferPriorityOperation([&renderer, request_id] {
renderer.GetVideoOut().CompleteFlip(request_id);
renderer.TriggerEopEvent(0);
});
return;
}
}
}
@@ -172,10 +162,6 @@ void TriggerAgcUserInterrupt() {
EXIT_NOT_IMPLEMENTED(result != OK && result != LibKernel::KERNEL_ERROR_ENOENT);
}
void TriggerEopEvent(uint32_t context_id) {
GetRenderContext().TriggerEopEvent(context_id);
}
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
uint32_t value) {
RecordEndOfPipeWrite(submit_id, buffer, reinterpret_cast<uint64_t>(dst_gpu_addr), value,
@@ -261,11 +247,12 @@ void WriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBuffer& buffer,
EndOfPipeWriteSize::Dword, EndOfPipeWriteAction::Interrupt, context_id);
}
uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
int64_t flip_arg) {
uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index, int flip_mode,
int64_t flip_arg) {
for (;;) {
uint64_t request_id = 0;
const auto result = Presentation::DisplayBufferSubmitFlipFromGpu(
auto& video_out = buffer.GetContext().GetVideoOut();
const auto result = video_out.SubmitFlipFromGpu(
buffer, handle, index, flip_mode, flip_arg, request_id);
if (result == OK) {
EXIT_IF(request_id == 0);
@@ -276,7 +263,7 @@ uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index,
"\n",
result, handle, index, flip_mode, flip_arg);
}
Presentation::DisplayBufferWaitForFlipQueueSlot();
video_out.WaitForSubmitSlot();
}
}
@@ -331,9 +318,11 @@ void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int han
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id) {
ValidateEndOfPipeSignal({.buffer = &buffer});
uint64_t args[LABEL_ARGS_MAX] = {static_cast<uint64_t>(context_id)};
SubmitLabel(buffer, nullptr, TriggerEopEventCallback, args);
auto& renderer = buffer.GetContext();
auto& scheduler = renderer.GetCommandScheduler();
EXIT_IF(!scheduler.Active() || &buffer != &scheduler.Current());
scheduler.DeferPriorityOperation(
[&renderer, context_id] { renderer.TriggerEopEvent(context_id); });
}
static void EopEventResetFunc(LibKernel::EventQueue::KernelEqueueEvent* event) {
@@ -349,7 +338,9 @@ static void EopEventDeleteFunc(LibKernel::EventQueue::KernelEqueue eq,
EXIT_NOT_IMPLEMENTED(event->event.filter != LibKernel::EventQueue::KERNEL_EVFILT_GRAPHICS);
if (event->event.ident == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT ||
event->event.ident == GRAPHICS_EVENT_EOP) {
GetRenderContext().DeleteEopEq(eq, static_cast<int>(event->event.ident));
auto* renderer = static_cast<RenderContext*>(event->filter.data);
EXIT_IF(renderer == nullptr);
renderer->DeleteEopEq(eq, static_cast<int>(event->event.ident));
}
}
@@ -368,7 +359,8 @@ static void EopEventTriggerFunc(LibKernel::EventQueue::KernelEqueueEvent* event,
}
}
int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata) {
LibKernel::EventQueue::KernelEqueueEvent event;
event.triggered = false;
event.event.ident = static_cast<uintptr_t>(id);
@@ -379,13 +371,13 @@ int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata) {
event.filter.delete_event_func = EopEventDeleteFunc;
event.filter.reset_func = EopEventResetFunc;
event.filter.trigger_func = EopEventTriggerFunc;
event.filter.data = nullptr;
event.filter.data = &renderer;
int result = LibKernel::EventQueue::KernelAddEvent(eq, event);
if (result == 0 &&
(id == GRAPHICS_EVENT_QUEUED_GRAPHICS_INTERRUPT || id == GRAPHICS_EVENT_EOP)) {
GetRenderContext().AddEopEq(eq, id);
renderer.AddEopEq(eq, id);
}
return result;
@@ -398,12 +390,12 @@ int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
return result;
}
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
GetRenderContext().GetGdsBuffer().Read(dst, dw_offset, dw_size);
}
void DeleteBuffers() {
GetRenderContext().GetBufferCache().ResetNullBuffer();
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
const auto offset = uint64_t {dw_offset} * sizeof(uint32_t);
const auto size = uint64_t {dw_size} * sizeof(uint32_t);
EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset ||
gds.Mapped().empty());
std::memcpy(dst, gds.Mapped().data() + offset, static_cast<size_t>(size));
}
} // namespace Libs::Graphics::Sync
+7 -6
View File
@@ -8,6 +8,8 @@
namespace Libs::Graphics {
class CommandBuffer;
class Buffer;
class RenderContext;
namespace Sync {
@@ -16,7 +18,6 @@ namespace Sync {
[[nodiscard]] uint64_t ReadReferenceClock();
void TriggerAgcUserInterrupt();
void TriggerEopEvent(uint32_t context_id);
void TriggerEopEventAtEndOfPipe(CommandBuffer& buffer, uint32_t context_id);
void WriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
@@ -46,8 +47,8 @@ void WriteAtEndOfPipeWithInterruptWriteBack64(uint64_t submit_id, CommandBuffer&
uint64_t* dst_gpu_addr, uint64_t value,
uint32_t context_id = 0);
[[nodiscard]] uint64_t PrepareDisplayBufferFlip(CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg);
[[nodiscard]] uint64_t PrepareVideoOutFlip(CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg);
void WriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg, uint64_t request_id);
void WriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer& buffer, uint32_t* dst_gpu_addr,
@@ -58,10 +59,10 @@ void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuf
int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t request_id);
int AddEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id, void* udata);
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata);
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
void DeleteBuffers();
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
} // namespace Sync
} // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
+153 -143
View File
@@ -3,174 +3,184 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "common/lruCache.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/host_gpu/renderer/blitHelper.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/renderer/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/tiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <compare>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
struct GraphicContext;
struct ImageViewInfo;
struct RenderTextureVulkanImage;
struct StorageTextureVulkanImage;
struct VideoOutVulkanImage;
struct VulkanImage;
struct VulkanMemory;
class Buffer;
class BufferCache;
class CommandBuffer;
class DummyTextureCache;
class CommandScheduler;
class ResourceMutex;
[[nodiscard]] bool IsExactRenderTargetMipStorage(const ImageInfo& sampled, const ImageInfo& storage,
vk::Format sampled_view_format,
vk::Format storage_image_format) noexcept;
class RenderExecutor;
class StreamBuffer;
class TileManager;
struct TextureCacheTestAccess;
class TextureCache {
public:
struct RegionInfo {
bool image_pages = false;
bool image_bytes = false;
bool gpu_image_bytes = false;
bool non_sampled_pages = false;
bool metadata_pages = false;
bool metadata_bytes = false;
bool gpu_metadata_bytes = false;
};
struct MetaRangeInfo {
uint64_t metadata_address = 0;
uint64_t metadata_size = 0;
uint32_t slice = 0;
bool full = false;
enum class BindingType : uint8_t { Texture, Storage, RenderTarget, DepthTarget, VideoOut };
struct ImageDesc {
ImageInfo info;
ImageViewInfo view_info;
BindingType type = BindingType::Texture;
};
TextureCache(GraphicContext& graphics, PageManager& page_manager, BufferCache& buffer_cache,
ResourceMutex& resource_mutex);
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, ResourceMutex& resource_mutex);
~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] VulkanImage& FindTexture(CommandBuffer& command, const ImageInfo& info,
bool metadata_read);
[[nodiscard]] StorageTextureVulkanImage& FindStorageTexture(CommandBuffer& command,
const ImageInfo& info);
[[nodiscard]] RenderTextureVulkanImage& FindRenderTarget(CommandBuffer& command,
const RenderTargetInfo& info);
[[nodiscard]] DepthStencilVulkanImage& FindDepthTarget(CommandBuffer& command,
const DepthTargetInfo& info);
[[nodiscard]] std::vector<VideoOutVulkanImage*>
RegisterVideoOutSurfaces(const std::vector<VideoOutInfo>& infos);
void RefreshVideoOut(VideoOutVulkanImage& image, bool render_target = false);
void UnregisterVideoOutSurfaces(const std::vector<VideoOutVulkanImage*>& images);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
uint32_t packed_clear);
void MarkGpuWritten(VulkanImage& image);
void PrepareHostWrite(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t vaddr, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RenderTextureVulkanImage* FindRenderTargetByRange(CommandBuffer& command,
uint64_t vaddr, uint64_t size);
[[nodiscard]] vk::ImageView GetRenderTargetAttachmentView(RenderTextureVulkanImage& image,
vk::Format format, uint32_t level,
uint32_t base_layer,
uint32_t layer_count);
[[nodiscard]] vk::ImageView GetDepthTargetAttachmentView(DepthStencilVulkanImage& image,
uint32_t base_layer,
uint32_t layer_count);
[[nodiscard]] vk::ImageView
GetDepthTargetSampledView(DepthStencilVulkanImage& image, vk::Format view_format,
uint32_t swizzle, uint32_t base_level, uint32_t level_count,
vk::ImageViewType type, uint32_t base_layer, uint32_t layer_count);
[[nodiscard]] vk::ImageView GetSampledColorView(VulkanImage& image, vk::Format view_format,
uint32_t swizzle, uint32_t base_level,
uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count);
[[nodiscard]] vk::ImageView
GetRenderTargetStorageView(RenderTextureVulkanImage& image, vk::Format view_format,
uint32_t base_level, uint32_t level_count, vk::ImageViewType type,
uint32_t base_layer, uint32_t layer_count);
[[nodiscard]] vk::ImageView GetStorageTextureSampledView(StorageTextureVulkanImage& image,
const ImageInfo& info);
[[nodiscard]] vk::ImageView GetStorageTextureStorageView(StorageTextureVulkanImage& image,
uint32_t base_level);
[[nodiscard]] DepthStencilVulkanImage*
FindDepthTargetByRange(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool allow_containing_sampled = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool ResolveMetaRange(uint64_t vaddr, uint64_t size, MetaRangeInfo& info);
void RegisterMeta(uint64_t vaddr, uint64_t size, uint32_t layers = 1);
[[nodiscard]] bool IsMeta(uint64_t vaddr);
[[nodiscard]] bool IsMetaRange(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsMetaCleared(uint64_t vaddr, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t vaddr);
[[nodiscard]] bool TouchMeta(uint64_t vaddr, uint32_t slice, bool is_clear);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
bool ensure_valid = true);
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] Image& GetImage(ImageId id);
[[nodiscard]] const Image& GetImage(ImageId id) const;
void MarkGpuWritten(ImageId id);
VulkanImage& GetDummySampledTexture(bool uint_format, bool image_3d);
VulkanImage& GetDummyStorageTexture(bool uint_format, bool image_3d);
[[nodiscard]] std::shared_ptr<void> GetImageOwner(const VulkanImage& image);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear);
void PrepareHostWrite(uint64_t address, uint64_t size);
[[nodiscard]] bool SynchronizeImageToBuffer(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
bool formatted_buffer_write = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool ClearMeta(uint64_t address);
[[nodiscard]] bool TouchMeta(uint64_t address, uint32_t slice, bool is_clear);
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t address, uint64_t size,
PageFaultPhase phase) noexcept;
void UnmapMemory(uint64_t address, uint64_t size);
void ProcessDownloadImages();
void RunGarbageCollector();
private:
struct CachedImage;
using ImageOwnerIndex = MultiRangePageOwnerIndex<CachedImage*>;
struct ReadbackWorker;
struct MetaDataInfo {
uint64_t size = 0;
uint32_t layers = 1;
uint32_t clear_mask = 0;
bool gpu_modified = false;
};
[[nodiscard]] vk::ImageView GetImageView(VulkanImage& image, const ImageViewInfo& info);
[[nodiscard]] bool HasMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
[[nodiscard]] CachedImage* FindGpuReadbackPageCandidateLocked(uint64_t vaddr, uint64_t size);
void RequireNoMetaOverlapLocked(uint64_t vaddr, uint64_t size) const;
void ResolveImageMetadataOverlapsLocked(uint64_t vaddr, uint64_t size);
void MarkSampledAliasesCpuDirtyLocked(uint64_t vaddr, uint64_t size);
void RetireSampledTargetAliases(const ImageInfo& requested);
void ResolveStorageImageOverlaps(const ImageInfo& requested);
void RetireStorageDepthAliasLocked(const ImageInfo& requested);
void RegisterImageLocked(CachedImage& image);
void UnregisterImageLocked(CachedImage& image, bool release_tracking);
[[nodiscard]] VulkanImage& PublishImage(CommandBuffer& command,
std::shared_ptr<CachedImage> image);
[[nodiscard]] std::vector<CachedImage*> FindImagesInRegionLocked(uint64_t vaddr, uint64_t size,
bool page_overlap);
void RequireRetirementIsolation(const std::vector<CachedImage*>& retire, const char* operation,
uint64_t address, uint64_t size) const;
void RetireImages(const std::vector<CachedImage*>& retire,
const CachedImage* native_image_source = nullptr);
void RetireDepthMetadataLocked(const std::vector<CachedImage*>& retire,
uint64_t preserve_address = 0);
void MaterializeImagesToGuestLocked(const std::vector<std::shared_ptr<CachedImage>>& images);
void SynchronizeColorImageToBufferLocked(CachedImage& cached, uint64_t write_address,
uint64_t write_size);
void SynchronizeDepthImageToBufferLocked(CachedImage& cached, uint64_t write_address,
uint64_t write_size);
enum class TransferDirection { Upload, Download };
struct ColorTransferPlan;
struct DownloadPlan;
GraphicContext& m_graphics;
std::unique_ptr<DummyTextureCache> m_dummy_textures;
TrackingSpinLock m_lock;
std::mutex m_fault_mutex;
MemoryTracker m_memory_tracker;
MemoryTracker m_metadata_tracker;
Tiler m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<std::shared_ptr<CachedImage>> m_images;
ImageOwnerIndex m_image_owner_index;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
std::unique_ptr<ReadbackWorker> m_readback;
std::vector<uint8_t> m_buffer_transition_guest;
struct Slot {
std::shared_ptr<Image> image;
uint32_t generation = 1;
};
struct MetaDataInfo {
uint32_t clear_mask = 0;
};
struct OverlapResult {
ImageId image;
int32_t mip = -1;
int32_t layer = -1;
};
using ImageOwnerIndex = MultiRangePageOwnerIndex<ImageId>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
[[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id, bool release_tracking);
void DeleteImage(ImageId id, bool release_tracking = true);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image);
[[nodiscard]] bool SafeToDownload(const Image& image);
[[nodiscard]] std::vector<ImageId> FindImagesInRegion(uint64_t address, uint64_t size,
bool page_overlap) const;
[[nodiscard]] OverlapResult ResolveOverlap(const ImageInfo& requested, BindingType binding,
ImageId cached, ImageId merged);
[[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 DownloadImageData(Image& image, Buffer& destination, uint64_t destination_offset,
DownloadPlan plan);
void DownloadDepth(Image& image, Buffer& destination, uint64_t destination_offset);
void CommitGpuWrite(Image& image);
void PrepareImageCopy(Image& image);
void RefreshCopySource(ImageId id);
[[nodiscard]] bool CopyD16(Image& destination, Image& source);
void CopyImage(ImageId destination, ImageId source);
void AssociateStencil(ImageId depth, GuestRange stencil);
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size);
void RestoreGpuTracking(const Image& image);
void ReleaseGpuTracking(ImageId id);
[[nodiscard]] bool SynchronizeImageToBuffer(ImageId id);
void DownloadImage(ImageId id);
[[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
TrackingSpinLock m_lock;
MemoryTracker m_memory_tracker;
BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
ImageOwnerIndex m_image_owner_index;
std::map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0;
uint64_t m_pressure_gc_memory = 1536ull * 1024 * 1024;
uint64_t m_critical_gc_memory = 3ull * 1024 * 1024 * 1024;
uint64_t m_gc_tick = 0;
bool m_readback_linear_images = false;
friend struct TextureCacheTestAccess;
friend class RenderExecutor;
};
} // namespace Libs::Graphics
+698 -141
View File
@@ -1,161 +1,718 @@
#include "graphics/host_gpu/renderer/tiler.h"
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/gpu_format.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/gpuTiler.h"
#include "gpu_tiler_shaders/gpu_tiler_demote_d16_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_depth_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_promote_d16_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_prt_3d_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_prt_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_render_target_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard256_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard4_3d_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard4_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard64_3d_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_standard64_spv.h"
#include "gpu_tiler_shaders/gpu_tiler_swap_bgra16_spv.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/renderer/streamBuffer.h"
#include <vector>
#include <algorithm>
#include <array>
#include <bit>
#include <cstring>
#include <limits>
namespace Libs::Graphics {
namespace {
struct DepthTransfer {
std::vector<GpuTileInfo> infos;
std::vector<BufferImageCopy> regions;
};
DepthTransfer MakeDepthTransfer(uint64_t size, uint32_t layers, uint32_t format,
uint32_t bytes_per_element, uint32_t width, uint32_t height,
uint32_t pitch, uint32_t base_layer, vk::ImageAspectFlags aspect) {
EXIT_IF(size == 0 || layers == 0 || size % layers != 0);
TileBlockLayout block {};
EXIT_NOT_IMPLEMENTED(
!TileGetBlockLayout(TileBlockFamily::Depth64KB, bytes_per_element, block) ||
Prospero::NumBytesPerElement(format) != bytes_per_element);
const uint64_t slice_size = size / layers;
DepthTransfer transfer;
transfer.infos.reserve(layers);
transfer.regions.reserve(layers);
for (uint32_t layer = 0; layer < layers; layer++) {
const uint64_t offset = slice_size * layer;
GpuTileInfo info {block.family,
block.bytes_per_element,
offset,
slice_size,
offset,
slice_size,
0,
width,
height,
1,
pitch};
info.surface_z = base_layer + layer;
transfer.infos.push_back(info);
BufferImageCopy region {};
region.offset = static_cast<uint32_t>(offset);
region.pitch = pitch;
region.width = width;
region.height = height;
region.dst_layer = base_layer + layer;
region.aspect = aspect;
transfer.regions.push_back(region);
TileManager::TileManager(GraphicContext& graphics, CommandScheduler& scheduler,
StreamBuffer& stream_buffer)
: m_graphics(graphics), m_scheduler(scheduler), m_stream_buffer(stream_buffer) {
static_assert(FamilyCount == 9);
static_assert(sizeof(Push) == 52);
std::array<vk::DescriptorSetLayoutBinding, 3> bindings {};
for (uint32_t index = 0; index < 2; index++) {
bindings[index] = {index, vk::DescriptorType::eStorageBuffer, 1,
vk::ShaderStageFlagBits::eCompute, nullptr};
}
return transfer;
bindings[2] = {2, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eCompute,
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();
RequireVulkanSuccess(m_graphics.device.createDescriptorSetLayout(&descriptor_info, nullptr,
&m_descriptor_layout),
"create TileManager descriptor layout");
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;
layout_info.pPushConstantRanges = &push_range;
RequireVulkanSuccess(
m_graphics.device.createPipelineLayout(&layout_info, nullptr, &m_pipeline_layout),
"create TileManager pipeline layout");
}
void UploadDepth(DepthStencilVulkanImage& image, uint64_t source_address, uint64_t size,
uint32_t layers, uint32_t format, uint32_t bytes_per_element, uint32_t width,
uint32_t height, uint32_t pitch, uint32_t base_layer,
vk::ImageAspectFlags aspect) {
auto transfer = MakeDepthTransfer(size, layers, format, bytes_per_element, width, height, pitch,
base_layer, aspect);
Transfer::UploadTiledImage(image, reinterpret_cast<const void*>(source_address), size, size,
transfer.infos, transfer.regions,
vk::ImageLayout::eDepthStencilAttachmentOptimal);
}
template <uint32_t (*Encode)(uint16_t)>
void UploadPromotedD16Depth(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, uint32_t base_layer) {
const uint64_t guest_slice_size = info.size / info.layers;
const uint64_t texels = static_cast<uint64_t>(info.pitch) * info.height;
const uint64_t host_slice_size = texels * sizeof(uint32_t);
const uint64_t host_upload_size = host_slice_size * info.layers;
EXIT_IF(host_upload_size > UINT32_MAX);
auto transfer = MakeDepthTransfer(info.size, info.layers, info.guest_format,
info.bytes_per_element, info.width, info.height, info.pitch,
base_layer, vk::ImageAspectFlagBits::eDepth);
std::vector<uint16_t> guest_linear(info.size / sizeof(uint16_t));
GpuDetile(reinterpret_cast<const void*>(source.address), guest_linear.data(), info.size,
info.size, transfer.infos);
Transfer::ScratchBuffer host_linear(host_upload_size);
for (uint32_t layer = 0; layer < info.layers; layer++) {
const auto* guest = guest_linear.data() + guest_slice_size / sizeof(uint16_t) * layer;
auto* host = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(host_linear.Data()) +
host_slice_size * layer);
for (uint64_t texel = 0; texel < texels; texel++) {
host[texel] = Encode(guest[texel]);
}
transfer.regions[layer].offset = static_cast<uint32_t>(host_slice_size * layer);
}
Transfer::UploadImage(image, host_linear.Data(), host_upload_size, transfer.regions,
vk::ImageLayout::eDepthStencilAttachmentOptimal);
}
} // namespace
void Tiler::DetileImage(GpuTextureVulkanImage& image, const ImageInfo& info,
const BufferImageCopySource& source, bool refresh, bool storage) const {
if (refresh) Transfer::WaitForQueueIdle();
const bool array_texture = TextureIsLayeredTexture(info.type);
const bool volume_texture = TextureIs3DTexture(info.type);
auto layout = TextureCalcUploadLayout(
info.format, info.width, info.height, info.levels, info.depth, info.pitch, info.tile,
info.size, true, volume_texture, storage ? "StorageTextureCache" : "TextureCache");
auto regions = TextureBuildUploadRegions(layout, image.format, info.width, info.height,
info.depth, info.levels, array_texture, volume_texture,
TextureUploadDestination::MipLevels);
TextureUploadGuestImage(image, reinterpret_cast<const void*>(source.address), info.size,
regions, layout, info.format, info.width, info.height, info.depth,
info.levels, storage ? "StorageTextureCache" : "TextureCache",
storage ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eShaderReadOnlyOptimal);
}
void Tiler::DetileImage(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer) const {
EXIT_NOT_IMPLEMENTED(info.samples != 1 || image.samples != 1);
if (refresh) Transfer::WaitForQueueIdle();
if (DepthAspectTransferBytes(info.format) != info.bytes_per_element) {
switch (info.format) {
case vk::Format::eD24UnormS8Uint:
UploadPromotedD16Depth<EncodeD16AsD24>(image, info, source, base_layer);
return;
case vk::Format::eD32SfloatS8Uint:
UploadPromotedD16Depth<EncodeD16AsD32>(image, info, source, base_layer);
return;
default: EXIT_NOT_IMPLEMENTED(true);
TileManager::~TileManager() {
for (auto pipeline: m_pipelines) {
if (pipeline != nullptr) {
m_graphics.device.destroyPipeline(pipeline, nullptr);
}
}
UploadDepth(image, source.address, info.size, info.layers, info.guest_format,
info.bytes_per_element, info.width, info.height, info.pitch, base_layer,
vk::ImageAspectFlagBits::eDepth);
if (m_d16_to_d24 != nullptr) {
m_graphics.device.destroyPipeline(m_d16_to_d24, nullptr);
}
if (m_d16_to_d32 != nullptr) {
m_graphics.device.destroyPipeline(m_d16_to_d32, nullptr);
}
if (m_d24_to_d16 != nullptr) {
m_graphics.device.destroyPipeline(m_d24_to_d16, nullptr);
}
if (m_d32_to_d16 != nullptr) {
m_graphics.device.destroyPipeline(m_d32_to_d16, nullptr);
}
if (m_swap_bgra16 != nullptr) {
m_graphics.device.destroyPipeline(m_swap_bgra16, nullptr);
}
if (m_pipeline_layout != nullptr) {
m_graphics.device.destroyPipelineLayout(m_pipeline_layout, nullptr);
}
if (m_descriptor_layout != nullptr) {
m_graphics.device.destroyDescriptorSetLayout(m_descriptor_layout, nullptr);
}
}
void Tiler::DetileStencil(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer) const {
EXIT_NOT_IMPLEMENTED(info.samples != 1 || image.samples != 1);
if (refresh) Transfer::WaitForQueueIdle();
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;
const auto format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt);
const auto pitch = TileGetTexturePitch(format, info.width, 1,
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
UploadDepth(image, source.address, info.stencil_size, info.layers, format, 1, info.width,
info.height, pitch, base_layer, vk::ImageAspectFlagBits::eStencil);
VmaAllocationCreateInfo allocate {};
allocate.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VkBuffer buffer = VK_NULL_HANDLE;
VmaAllocation memory = nullptr;
const auto raw = static_cast<VkBufferCreateInfo>(create);
RequireVulkanSuccess(static_cast<vk::Result>(vmaCreateBuffer(
m_graphics.allocator, &raw, &allocate, &buffer, &memory, nullptr)),
"allocate TileManager scratch buffer");
return {buffer, memory, size};
}
void TileManager::DeferDestroy(Scratch scratch) {
auto allocator = m_graphics.allocator;
m_scheduler.DeferOperation(
[allocator, scratch] { vmaDestroyBuffer(allocator, scratch.buffer, scratch.allocation); });
}
void TileManager::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) {
EXIT_IF(infos.empty() || tiled_capacity == 0 || linear_capacity == 0);
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
EXIT_NOT_IMPLEMENTED(tiled_capacity > UINT32_MAX || linear_capacity > UINT32_MAX);
const auto checked_multiply = [](uint64_t left, uint64_t right, uint64_t& result) {
return (left == 0 || right <= UINT64_MAX / left) && (result = left * right, true);
};
const auto checked_add = [](uint64_t left, uint64_t right, uint64_t& result) {
return right <= UINT64_MAX - left && (result = left + right, true);
};
const auto valid_range = [](uint64_t offset, uint64_t size, uint64_t capacity) {
return size != 0 && offset <= capacity && size <= capacity - offset;
};
dispatches.clear();
dispatches.reserve(infos.size());
for (const auto& info: infos) {
TileBlockLayout block {};
const uint32_t tiled_width = info.tiled_width != 0 ? info.tiled_width : info.pitch;
const uint32_t tiled_height = info.tiled_height != 0 ? info.tiled_height : info.height;
const uint64_t groups_x = (static_cast<uint64_t>(info.width) + 7u) / 8u;
const uint64_t groups_y = (static_cast<uint64_t>(info.height) + 7u) / 8u;
EXIT_NOT_IMPLEMENTED(
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
groups_x > limits.maxComputeWorkGroupCount[0] ||
groups_y > limits.maxComputeWorkGroupCount[1] ||
info.depth > limits.maxComputeWorkGroupCount[2] ||
(!info.tail && (tiled_width < info.width || tiled_height < info.height)) ||
!valid_range(info.linear_offset, info.linear_size, linear_capacity) ||
!valid_range(info.tiled_offset, info.tiled_size, tiled_capacity) ||
(block.block_depth == 1 && info.depth != 1));
uint64_t pitch_bytes = 0;
EXIT_NOT_IMPLEMENTED(!checked_multiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
pitch_bytes > UINT32_MAX);
uint64_t slice_bytes = info.linear_slice_stride;
uint64_t minimum_slice = 0;
EXIT_NOT_IMPLEMENTED(!checked_multiply(pitch_bytes, info.height, minimum_slice));
if (slice_bytes == 0) {
slice_bytes = minimum_slice;
}
uint64_t linear_used = 0;
uint64_t bytes = 0;
EXIT_NOT_IMPLEMENTED((info.depth > 1 && slice_bytes < minimum_slice) ||
!checked_multiply(info.depth - 1u, slice_bytes, bytes) ||
!checked_add(linear_used, bytes, linear_used) ||
!checked_multiply(info.height - 1u, pitch_bytes, bytes) ||
!checked_add(linear_used, bytes, linear_used) ||
!checked_multiply(info.width, info.bytes_per_element, bytes) ||
!checked_add(linear_used, bytes, linear_used) ||
linear_used > info.linear_size || slice_bytes > UINT32_MAX);
const uint64_t columns =
(static_cast<uint64_t>(tiled_width) + block.block_width - 1u) / block.block_width;
const uint64_t rows =
(static_cast<uint64_t>(tiled_height) + block.block_height - 1u) / block.block_height;
uint64_t blocks_per_slice = 0;
EXIT_NOT_IMPLEMENTED(!checked_multiply(columns, rows, blocks_per_slice) ||
columns > UINT32_MAX || blocks_per_slice > UINT32_MAX);
if (info.tail) {
EXIT_NOT_IMPLEMENTED(
info.family == TileBlockFamily::Standard256B || info.depth > block.block_depth ||
info.tail_x >= block.block_width || info.width > block.block_width - info.tail_x ||
info.tail_y >= block.block_height ||
info.height > block.block_height - info.tail_y ||
info.tiled_size < block.block_size);
} else {
const uint64_t slices =
(static_cast<uint64_t>(info.depth) + block.block_depth - 1u) / block.block_depth;
uint64_t tiled_used = 0;
EXIT_NOT_IMPLEMENTED(!checked_multiply(blocks_per_slice, slices, tiled_used) ||
!checked_multiply(tiled_used, block.block_size, tiled_used) ||
tiled_used > info.tiled_size);
}
const uint32_t alignment = std::min(info.bytes_per_element, 4u);
EXIT_NOT_IMPLEMENTED(((info.linear_offset | info.tiled_offset | pitch_bytes | slice_bytes) &
(alignment - 1u)) != 0);
const uint64_t src = source_base + (tile ? info.linear_offset : info.tiled_offset);
const uint64_t dst = target_base + (tile ? info.tiled_offset : info.linear_offset);
EXIT_NOT_IMPLEMENTED(src > UINT32_MAX || dst > UINT32_MAX);
const uint32_t family_index = static_cast<uint32_t>(info.family);
const uint32_t element_index = std::countr_zero(info.bytes_per_element);
EXIT_NOT_IMPLEMENTED(family_index >= FamilyCount || element_index >= BytesPerElementCount);
Dispatch dispatch {};
dispatch.pipeline_slot =
((tile ? FamilyCount : 0u) + family_index) * BytesPerElementCount + element_index;
dispatch.push.src_base = static_cast<uint32_t>(src);
dispatch.push.dst_base = static_cast<uint32_t>(dst);
dispatch.push.width = info.width;
dispatch.push.height = info.height;
dispatch.push.depth = info.depth;
dispatch.push.surface_z = info.surface_z;
dispatch.push.pitch_bytes = static_cast<uint32_t>(pitch_bytes);
dispatch.push.slice_bytes = static_cast<uint32_t>(slice_bytes);
dispatch.push.blocks_per_row = static_cast<uint32_t>(columns);
dispatch.push.blocks_per_slice = static_cast<uint32_t>(blocks_per_slice);
dispatch.push.tail_x = info.tail_x;
dispatch.push.tail_y = info.tail_y;
dispatch.push.tail = info.tail;
dispatches.push_back(dispatch);
}
const uint64_t uniform_alignment =
std::max<uint64_t>(limits.minUniformBufferOffsetAlignment, 1);
const uint64_t stride = (sizeof(Push) + uniform_alignment - 1) & ~(uniform_alignment - 1);
EXIT_NOT_IMPLEMENTED(dispatches.size() > UINT64_MAX / stride);
const uint64_t bytes = dispatches.size() * stride;
auto [mapped, offset] = m_stream_buffer.Map(bytes, uniform_alignment);
EXIT_IF(mapped == nullptr);
for (size_t index = 0; index < dispatches.size(); index++) {
std::memcpy(mapped + index * stride, &dispatches[index].push, sizeof(Push));
dispatches[index].params_offset = offset + index * stride;
}
m_stream_buffer.Commit();
}
vk::Pipeline TileManager::GetPipeline(uint32_t slot) {
EXIT_IF(slot >= m_pipelines.size());
if (m_pipelines[slot] != nullptr) {
return m_pipelines[slot];
}
struct Shader {
const uint32_t* code;
size_t words;
};
static constexpr std::array<Shader, FamilyCount> shaders {{
{GPU_TILER_STANDARD256_SPV, std::size(GPU_TILER_STANDARD256_SPV)},
{GPU_TILER_STANDARD4_SPV, std::size(GPU_TILER_STANDARD4_SPV)},
{GPU_TILER_STANDARD4_3D_SPV, std::size(GPU_TILER_STANDARD4_3D_SPV)},
{GPU_TILER_STANDARD64_SPV, std::size(GPU_TILER_STANDARD64_SPV)},
{GPU_TILER_STANDARD64_3D_SPV, std::size(GPU_TILER_STANDARD64_3D_SPV)},
{GPU_TILER_PRT_SPV, std::size(GPU_TILER_PRT_SPV)},
{GPU_TILER_PRT_3D_SPV, std::size(GPU_TILER_PRT_3D_SPV)},
{GPU_TILER_RENDER_TARGET_SPV, std::size(GPU_TILER_RENDER_TARGET_SPV)},
{GPU_TILER_DEPTH_SPV, std::size(GPU_TILER_DEPTH_SPV)},
}};
const uint32_t element_index = slot % BytesPerElementCount;
const uint32_t direction_index = slot / (FamilyCount * BytesPerElementCount);
const uint32_t family_index = (slot / BytesPerElementCount) % FamilyCount;
const uint32_t values[] {1u << element_index, direction_index};
const vk::SpecializationMapEntry entries[] {{0, 0, 4}, {1, 4, 4}};
const vk::SpecializationInfo specialization {2, entries, sizeof(values), values};
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 =
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_pipelines[slot]);
m_graphics.device.destroyShaderModule(module, nullptr);
RequireVulkanSuccess(result, "create TileManager pipeline");
return m_pipelines[slot];
}
void TileManager::Record(bool tile, 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) {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const uint64_t descriptor_alignment =
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const uint64_t source_descriptor_offset = source_offset & ~(descriptor_alignment - 1);
const uint64_t target_descriptor_offset = target_offset & ~(descriptor_alignment - 1);
const uint64_t source_base = source_offset - source_descriptor_offset;
const uint64_t target_base = target_offset - target_descriptor_offset;
const uint64_t source_range = (source_base + source_capacity + 3u) & ~uint64_t {3};
const uint64_t target_range = (target_base + target_capacity + 3u) & ~uint64_t {3};
EXIT_NOT_IMPLEMENTED(source_range > limits.maxStorageBufferRange ||
target_range > limits.maxStorageBufferRange || target_offset % 4 != 0 ||
target_capacity % 4 != 0);
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;
barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barriers[0].buffer = source;
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;
barriers[1].buffer = target;
barriers[1].offset = target_offset;
barriers[1].size = target_capacity;
barriers[2] = barriers[0];
barriers[2].srcAccessMask = vk::AccessFlagBits::eHostWrite;
barriers[2].dstAccessMask = vk::AccessFlagBits::eUniformRead;
barriers[2].buffer = m_stream_buffer.Handle();
barriers[2].offset = dispatches.front().params_offset;
barriers[2].size =
dispatches.back().params_offset - dispatches.front().params_offset + sizeof(Push);
command.pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eTransfer, {}, 0,
nullptr, 3, barriers, 0, nullptr);
if (clear_target) {
command.fillBuffer(target, target_offset, target_capacity, 0);
barriers[1].srcAccessMask = vk::AccessFlagBits::eTransferWrite;
barriers[1].dstAccessMask =
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 1,
&barriers[1], 0, nullptr);
}
const vk::DescriptorBufferInfo source_info {source, source_descriptor_offset, source_range};
const vk::DescriptorBufferInfo target_info {target, target_descriptor_offset, target_range};
for (auto& dispatch: dispatches) {
const vk::DescriptorBufferInfo params_info {m_stream_buffer.Handle(),
dispatch.params_offset, sizeof(Push)};
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
: vk::DescriptorType::eStorageBuffer;
writes[index].pBufferInfo = &infos[index];
}
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
static_cast<uint32_t>(writes.size()), writes.data());
command.bindPipeline(vk::PipelineBindPoint::eCompute, GetPipeline(dispatch.pipeline_slot));
command.dispatch((dispatch.push.width + 7u) / 8u, (dispatch.push.height + 7u) / 8u,
dispatch.push.depth);
}
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 1,
&barriers[1], 0, nullptr);
}
TileManager::Result TileManager::Detile(vk::Buffer tiled, uint64_t tiled_offset,
uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos) {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const uint64_t descriptor_alignment =
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const uint64_t source_base = tiled_offset & (descriptor_alignment - 1);
std::vector<Dispatch> dispatches;
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,
true);
return {scratch.buffer, 0, linear_capacity};
}
void TileManager::Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity,
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
std::span<const GpuTileInfo> infos) {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const uint64_t descriptor_alignment =
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const uint64_t source_base = linear_offset & (descriptor_alignment - 1);
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,
dispatches, false);
}
void TileManager::TileImage(Image& image, std::span<const vk::BufferImageCopy> regions,
vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const GpuTileInfo> infos,
ColorTransform transform) {
EXIT_IF(regions.empty());
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const uint64_t descriptor_alignment =
std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const uint64_t target_base = tiled_offset & (descriptor_alignment - 1);
std::vector<Dispatch> dispatches;
// Reserve all stream parameters before creating a scheduler-lived scratch dependency:
// StreamBuffer::Map is allowed to submit the current tick when it wraps.
Prepare(true, tiled_capacity, linear_capacity, infos, 0, target_base, dispatches);
auto linear = AllocateScratch((linear_capacity + 3u) & ~uint64_t {3});
DeferDestroy(linear);
image.Download(regions, linear.buffer, 0, linear.size);
Result source {linear.buffer, 0, linear.size};
if (transform == ColorTransform::SwapBgra16) {
source = SwapBgra16(source);
}
Record(true, source.buffer, source.offset, linear_capacity, tiled, tiled_offset, tiled_capacity,
dispatches, false);
}
TileManager::Result TileManager::GetScratchBuffer(uint64_t size) {
auto scratch = AllocateScratch((size + 3u) & ~uint64_t {3});
DeferDestroy(scratch);
return {scratch.buffer, 0, scratch.size};
}
TileManager::StorageBinding TileManager::BindStorage(Result buffer, uint64_t size) const {
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const auto alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const auto descriptor_offset = buffer.offset - buffer.offset % alignment;
const auto base = buffer.offset - descriptor_offset;
EXIT_IF(buffer.buffer == nullptr || size == 0 || buffer.size < size || base > UINT32_MAX ||
size > UINT64_MAX - base || base + size > UINT64_MAX - 3);
const auto range = (base + size + 3) & ~uint64_t {3};
EXIT_IF(range > limits.maxStorageBufferRange || range > UINT32_MAX);
return {{buffer.buffer, descriptor_offset, range}, static_cast<uint32_t>(base)};
}
uint32_t TileManager::ConversionRows(uint64_t offset, uint64_t row_stride, uint64_t active,
uint32_t remaining, uint64_t alignment, uint64_t max_range,
uint32_t max_groups) noexcept {
if (row_stride == 0 || active == 0 || remaining == 0 || alignment == 0 || max_groups == 0) {
return 0;
}
const auto prefix = offset % alignment;
if (prefix >= max_range || active > max_range - prefix) {
return 0;
}
const auto descriptor_rows = 1 + (max_range - prefix - active) / row_stride;
return static_cast<uint32_t>(std::min<uint64_t>({remaining, descriptor_rows, max_groups}));
}
void TileManager::ConvertD16(Result source, Result target, D16Direction direction, bool d32,
const D16Layout& layout) {
vk::Pipeline* pipeline_pointer = nullptr;
if (direction == D16Direction::Promote) {
pipeline_pointer = d32 ? &m_d16_to_d32 : &m_d16_to_d24;
} else {
pipeline_pointer = d32 ? &m_d32_to_d16 : &m_d24_to_d16;
}
auto& pipeline = *pipeline_pointer;
if (pipeline == nullptr) {
const uint32_t value = d32 ? 1u : 0u;
const vk::SpecializationMapEntry entry {0, 0, sizeof(value)};
const vk::SpecializationInfo specialization {1, &entry, sizeof(value), &value};
const uint32_t* code = nullptr;
size_t words = 0;
if (direction == D16Direction::Promote) {
code = GPU_TILER_PROMOTE_D16_SPV;
words = std::size(GPU_TILER_PROMOTE_D16_SPV);
} else {
code = GPU_TILER_DEMOTE_D16_SPV;
words = std::size(GPU_TILER_DEMOTE_D16_SPV);
}
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 =
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &pipeline);
m_graphics.device.destroyShaderModule(module, nullptr);
RequireVulkanSuccess(result, "create D16 conversion pipeline");
}
const uint64_t source_element =
direction == D16Direction::Promote ? sizeof(uint16_t) : sizeof(uint32_t);
const uint64_t target_element =
direction == D16Direction::Promote ? sizeof(uint32_t) : sizeof(uint16_t);
const uint64_t source_active = static_cast<uint64_t>(layout.width) * source_element;
const uint64_t target_active = static_cast<uint64_t>(layout.width) * target_element;
const auto required = [](uint32_t height, uint32_t layers, uint64_t row_stride,
uint64_t slice_stride, uint64_t active) {
EXIT_IF(height == 0 || layers == 0 || row_stride < active ||
(height - 1) > (UINT64_MAX - active) / row_stride);
const auto slice = static_cast<uint64_t>(height - 1) * row_stride + active;
EXIT_IF(slice_stride < slice || (layers - 1) > (UINT64_MAX - slice) / slice_stride);
return static_cast<uint64_t>(layers - 1) * slice_stride + slice;
};
EXIT_IF(layout.width == 0 || layout.source_row_stride > UINT32_MAX ||
layout.target_row_stride > UINT32_MAX);
const auto source_required = required(layout.height, layout.layers, layout.source_row_stride,
layout.source_slice_stride, source_active);
const auto target_required = required(layout.height, layout.layers, layout.target_row_stride,
layout.target_slice_stride, target_active);
EXIT_IF(source_required > UINT64_MAX - 3 || target_required > UINT64_MAX - 3);
const auto source_barrier_size = (source_required + 3) & ~uint64_t {3};
const auto target_barrier_size = (target_required + 3) & ~uint64_t {3};
EXIT_IF(source.size < source_barrier_size || target.size < target_barrier_size);
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;
barriers[0].offset = source.offset;
barriers[0].size = source_barrier_size;
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite |
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;
barriers[1].offset = target.offset;
barriers[1].size = target_barrier_size;
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite |
vk::AccessFlagBits::eHostWrite |
vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eShaderWrite;
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
command.pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
command.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline);
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
const auto descriptor_alignment = std::max<uint64_t>(limits.minStorageBufferOffsetAlignment, 4);
const auto rows_for = [&](Result buffer, uint64_t relative, uint64_t stride, uint64_t active,
uint32_t remaining) {
EXIT_IF(relative > buffer.size || buffer.offset > UINT64_MAX - relative);
const auto offset = buffer.offset + relative;
return ConversionRows(offset, stride, active, remaining, descriptor_alignment,
limits.maxStorageBufferRange, limits.maxComputeWorkGroupCount[1]);
};
const auto groups_x = (static_cast<uint64_t>(layout.width) + 63u) / 64u;
EXIT_IF(groups_x == 0 || groups_x > limits.maxComputeWorkGroupCount[0]);
for (uint32_t layer = 0; layer < layout.layers; layer++) {
for (uint32_t row = 0; row < layout.height;) {
const auto source_relative =
layout.source_slice_stride * layer + layout.source_row_stride * row;
const auto target_relative =
layout.target_slice_stride * layer + layout.target_row_stride * row;
const auto remaining = layout.height - row;
const auto rows = std::min(rows_for(source, source_relative, layout.source_row_stride,
source_active, remaining),
rows_for(target, target_relative, layout.target_row_stride,
target_active, remaining));
EXIT_IF(rows == 0);
const auto source_span =
static_cast<uint64_t>(rows - 1) * layout.source_row_stride + source_active;
const auto target_span =
static_cast<uint64_t>(rows - 1) * layout.target_row_stride + target_active;
const auto source_binding = BindStorage(
{source.buffer, source.offset + source_relative, source.size - source_relative},
source_span);
const auto target_binding = BindStorage(
{target.buffer, target.offset + target_relative, target.size - target_relative},
target_span);
const vk::DescriptorBufferInfo infos[] {
source_binding.info,
target_binding.info,
};
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;
writes[index].pBufferInfo = &infos[index];
}
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
static_cast<uint32_t>(writes.size()), writes.data());
Push push {};
push.src_base = source_binding.base;
push.dst_base = target_binding.base;
push.width = layout.width;
push.height = rows;
push.pitch_bytes = static_cast<uint32_t>(layout.source_row_stride);
push.slice_bytes = static_cast<uint32_t>(layout.target_row_stride);
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0,
sizeof(push), &push);
command.dispatch(static_cast<uint32_t>(groups_x), rows, 1);
row += rows;
}
}
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eMemoryRead;
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 1,
&barriers[1], 0, nullptr);
}
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 =
m_graphics.device.createComputePipelines(nullptr, 1, &create, nullptr, &m_swap_bgra16);
m_graphics.device.destroyShaderModule(module, nullptr);
RequireVulkanSuccess(result, "create BGRA16 swap pipeline");
}
const uint64_t bytes = static_cast<uint64_t>(pixels) * 8u;
EXIT_IF(pixels == 0);
const auto input_binding = BindStorage(input, bytes);
const auto output_binding = BindStorage(output, bytes);
const vk::DescriptorBufferInfo infos[] {
input_binding.info,
output_binding.info,
};
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;
writes[index].pBufferInfo = &infos[index];
}
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;
barriers[index].offset = infos[index].offset;
barriers[index].size = infos[index].range;
}
barriers[0].srcAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eHostWrite |
vk::AccessFlagBits::eShaderWrite;
barriers[0].dstAccessMask = vk::AccessFlagBits::eShaderRead;
barriers[1].srcAccessMask = vk::AccessFlagBits::eMemoryRead;
barriers[1].dstAccessMask = vk::AccessFlagBits::eShaderWrite;
m_scheduler.EndRendering();
auto command = m_scheduler.Current().Handle();
command.pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands | vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eComputeShader, {}, 0, nullptr, 2, barriers, 0, nullptr);
command.bindPipeline(vk::PipelineBindPoint::eCompute, m_swap_bgra16);
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, m_pipeline_layout, 0,
static_cast<uint32_t>(writes.size()), writes.data());
Push push {};
push.src_base = input_binding.base;
push.dst_base = output_binding.base;
push.width = pixels;
command.pushConstants(m_pipeline_layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(push),
&push);
command.dispatch((pixels + 63u) / 64u, 1, 1);
barriers[1].srcAccessMask = vk::AccessFlagBits::eShaderWrite;
barriers[1].dstAccessMask = vk::AccessFlagBits::eTransferRead;
command.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 1, &barriers[1],
0, nullptr);
}
TileManager::Result TileManager::SwapBgra16(Result input) {
EXIT_NOT_IMPLEMENTED(input.size == 0 || input.size % 8u != 0 || input.size / 8u > UINT32_MAX);
auto output = AllocateScratch(input.size);
DeferDestroy(output);
Result result {output.buffer, 0, output.size};
SwapBgra16(input, result, static_cast<uint32_t>(input.size / 8u));
return result;
}
void TileManager::SwapBgra16(Result input, Result output) {
EXIT_NOT_IMPLEMENTED(input.size == 0 || input.size % 8u != 0 || input.size / 8u > UINT32_MAX ||
output.size < input.size);
SwapBgra16(input, output, static_cast<uint32_t>(input.size / 8u));
}
} // namespace Libs::Graphics
+136 -16
View File
@@ -2,26 +2,146 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_TILER_H_
#include "common/common.h"
#include "graphics/host_gpu/renderer/bufferCache.h"
#include "graphics/host_gpu/renderer/imageInfo.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <array>
#include <span>
#include <vector>
#include <vk_mem_alloc.h>
namespace Libs::Graphics {
struct DepthStencilVulkanImage;
struct GpuTextureVulkanImage;
class Tiler final {
public:
Tiler() = default;
KYTY_CLASS_NO_COPY(Tiler);
class CommandScheduler;
class Image;
class StreamBuffer;
struct GraphicContext;
struct TileManagerTestAccess;
void DetileImage(GpuTextureVulkanImage& image, const ImageInfo& info,
const BufferImageCopySource& source, bool refresh, bool storage) const;
void DetileImage(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer = 0) const;
void DetileStencil(DepthStencilVulkanImage& image, const DepthTargetInfo& info,
const BufferImageCopySource& source, bool refresh,
uint32_t base_layer = 0) const;
struct GpuTileInfo {
TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0;
uint64_t linear_offset = 0;
uint64_t linear_size = 0;
uint64_t tiled_offset = 0;
uint64_t tiled_size = 0;
uint64_t linear_slice_stride = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t pitch = 0;
uint32_t tail_x = 0;
uint32_t tail_y = 0;
bool tail = false;
uint32_t tiled_width = 0;
uint32_t tiled_height = 0;
uint32_t surface_z = 0;
};
class TileManager final {
public:
enum class D16Direction { Promote, Demote };
enum class ColorTransform { None, SwapBgra16 };
struct Result {
vk::Buffer buffer = nullptr;
uint64_t offset = 0;
uint64_t size = 0;
};
struct D16Layout {
uint32_t width = 0;
uint32_t height = 0;
uint32_t layers = 0;
uint64_t source_row_stride = 0;
uint64_t target_row_stride = 0;
uint64_t source_slice_stride = 0;
uint64_t target_slice_stride = 0;
};
TileManager(GraphicContext& graphics, CommandScheduler& scheduler, StreamBuffer& stream_buffer);
~TileManager();
KYTY_CLASS_NO_COPY(TileManager);
// The returned device-local buffer remains alive through the current scheduler tick.
[[nodiscard]] Result Detile(vk::Buffer tiled, uint64_t tiled_offset, uint64_t tiled_capacity,
uint64_t linear_capacity, std::span<const GpuTileInfo> infos);
void Tile(vk::Buffer linear, uint64_t linear_offset, uint64_t linear_capacity, vk::Buffer tiled,
uint64_t tiled_offset, uint64_t tiled_capacity, std::span<const GpuTileInfo> infos);
void TileImage(Image& image, std::span<const vk::BufferImageCopy> regions, vk::Buffer tiled,
uint64_t tiled_offset, uint64_t tiled_capacity, uint64_t linear_capacity,
std::span<const GpuTileInfo> infos,
ColorTransform transform = ColorTransform::None);
[[nodiscard]] Result GetScratchBuffer(uint64_t size);
void ConvertD16(Result source, Result target, D16Direction direction, bool d32,
const D16Layout& layout);
[[nodiscard]] Result SwapBgra16(Result input);
void SwapBgra16(Result input, Result output);
private:
friend struct TileManagerTestAccess;
static constexpr uint32_t FamilyCount = static_cast<uint32_t>(TileBlockFamily::Count);
static constexpr uint32_t BytesPerElementCount = 5;
static constexpr uint32_t DirectionCount = 2;
static constexpr uint32_t PipelineCount = FamilyCount * BytesPerElementCount * DirectionCount;
struct Push {
uint32_t src_base;
uint32_t dst_base;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t surface_z;
uint32_t pitch_bytes;
uint32_t slice_bytes;
uint32_t blocks_per_row;
uint32_t blocks_per_slice;
uint32_t tail_x;
uint32_t tail_y;
uint32_t tail;
};
struct Dispatch {
Push push {};
uint32_t pipeline_slot = 0;
uint64_t params_offset = 0;
};
struct Scratch {
vk::Buffer buffer = nullptr;
VmaAllocation allocation = nullptr;
uint64_t size = 0;
};
struct StorageBinding {
vk::DescriptorBufferInfo info;
uint32_t base = 0;
};
[[nodiscard]] Scratch AllocateScratch(uint64_t size);
[[nodiscard]] StorageBinding BindStorage(Result buffer, uint64_t size) const;
[[nodiscard]] static uint32_t ConversionRows(uint64_t offset, uint64_t row_stride,
uint64_t active, uint32_t remaining,
uint64_t alignment, uint64_t max_range,
uint32_t max_groups) noexcept;
void DeferDestroy(Scratch scratch);
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,
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);
void SwapBgra16(Result input, Result output, uint32_t pixels);
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
StreamBuffer& m_stream_buffer;
vk::DescriptorSetLayout m_descriptor_layout = nullptr;
vk::PipelineLayout m_pipeline_layout = nullptr;
std::array<vk::Pipeline, PipelineCount> m_pipelines {};
vk::Pipeline m_d16_to_d24 = nullptr;
vk::Pipeline m_d16_to_d32 = nullptr;
vk::Pipeline m_d24_to_d16 = nullptr;
vk::Pipeline m_d32_to_d16 = nullptr;
vk::Pipeline m_swap_bgra16 = nullptr;
};
} // namespace Libs::Graphics
@@ -0,0 +1,10 @@
#version 450 core
#extension GL_EXT_samplerless_texture_functions : require
layout(binding = 0, set = 0) uniform texture2D color;
layout(location = 0) in vec2 uv;
void main() {
ivec2 coord = ivec2(uv * vec2(textureSize(color, 0)));
gl_FragDepth = texelFetch(color, coord, 0)[gl_SampleID];
}
@@ -0,0 +1,9 @@
#version 450
layout(location = 0) out vec2 uv;
void main() {
vec2 position = vec2(float((gl_VertexIndex & 1u) << 2u), float((gl_VertexIndex & 2u) << 1u));
gl_Position = vec4(position - vec2(1.0), 0.0, 1.0);
uv = position * 0.5;
}
@@ -0,0 +1,23 @@
#version 450
#extension GL_EXT_samplerless_texture_functions : require
layout(local_size_x = 4) in;
layout(set = 0, binding = 0) uniform texture2DMS input_depth;
layout(set = 0, binding = 1, std430) buffer Output {
uint value[];
}
output_data;
layout(push_constant) uniform Push {
uint samples;
}
push;
void main() {
const uint sample_id = gl_LocalInvocationID.x;
if (sample_id < push.samples) {
output_data.value[sample_id] =
floatBitsToUint(texelFetch(input_depth, ivec2(0), int(sample_id)).x);
}
}
@@ -3,7 +3,7 @@ layout(local_size_x = 8, local_size_y = 8) in;
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
layout(push_constant) uniform Push {
layout(set = 0, binding = 2, std140) uniform Push {
uint src_base;
uint dst_base;
uint width;
@@ -27,7 +27,9 @@ void copy_element(uint src, uint dst) {
} else {
uint mask = ELEMENT_BYTES == 1 ? 0xffu : 0xffffu;
uint value = (input_buffer.data[src >> 2] >> ((src & 3u) * 8u)) & mask;
atomicOr(output_buffer.data[dst >> 2], value << ((dst & 3u) * 8u));
uint shift = (dst & 3u) * 8u;
atomicAnd(output_buffer.data[dst >> 2], ~(mask << shift));
atomicOr(output_buffer.data[dst >> 2], value << shift);
}
}
@@ -0,0 +1,45 @@
#version 450
layout(constant_id = 0) const uint D32 = 0u;
layout(local_size_x = 64) in;
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
layout(push_constant, std430) uniform Push {
uint src_base;
uint dst_base;
uint width;
uint height;
uint depth;
uint surface_z;
uint pitch_bytes;
uint slice_bytes;
uint blocks_per_row;
uint blocks_per_slice;
uint tail_x;
uint tail_y;
uint tail;
} params;
uint decode(uint value) {
float normalized = D32 != 0u
? clamp(uintBitsToFloat(value), 0.0, 1.0)
: float(value & 0x00ffffffu) / 16777215.0;
return uint(round(normalized * 65535.0));
}
void main() {
uint x = gl_GlobalInvocationID.x;
uint y = gl_GlobalInvocationID.y;
if (x >= params.width || y >= params.height) {
return;
}
uint src = params.src_base + y * params.pitch_bytes + x * 4u;
uint dst = params.dst_base + y * params.slice_bytes + x * 2u;
uint shift = (dst & 2u) * 8u;
uint mask = 0xffffu << shift;
uint word = dst >> 2u;
atomicAnd(output_buffer.data[word], ~mask);
atomicOr(output_buffer.data[word], decode(input_buffer.data[src >> 2u]) << shift);
}
@@ -0,0 +1,39 @@
#version 450
layout(constant_id = 0) const uint D32 = 0u;
layout(local_size_x = 64) in;
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
layout(push_constant, std430) uniform Push {
uint src_base;
uint dst_base;
uint width;
uint height;
uint depth;
uint surface_z;
uint pitch_bytes;
uint slice_bytes;
uint blocks_per_row;
uint blocks_per_slice;
uint tail_x;
uint tail_y;
uint tail;
} params;
void main() {
uint x = gl_GlobalInvocationID.x;
uint y = gl_GlobalInvocationID.y;
if (x >= params.width || y >= params.height) {
return;
}
uint src = params.src_base + y * params.pitch_bytes + x * 2u;
uint packed = input_buffer.data[src >> 2u];
uint value = (packed >> ((src & 2u) * 8u)) & 0xffffu;
uint encoded = D32 != 0u
? floatBitsToUint(float(value) / 65535.0)
: value * 256u + (value + 128u) / 257u;
uint dst = params.dst_base + y * params.slice_bytes + x * 4u;
output_buffer.data[dst >> 2u] = encoded;
}
@@ -0,0 +1,35 @@
#version 450
layout(local_size_x = 64) in;
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } input_buffer;
layout(set = 0, binding = 1, std430) buffer Output { uint data[]; } output_buffer;
layout(push_constant, std430) uniform Push {
uint src_base;
uint dst_base;
uint width;
uint height;
uint depth;
uint surface_z;
uint pitch_bytes;
uint slice_bytes;
uint blocks_per_row;
uint blocks_per_slice;
uint tail_x;
uint tail_y;
uint tail;
} params;
void main() {
uint pixel = gl_GlobalInvocationID.x;
if (pixel >= params.width) {
return;
}
uint src = (params.src_base >> 2u) + pixel * 2u;
uint dst = (params.dst_base >> 2u) + pixel * 2u;
uint bg = input_buffer.data[src];
uint ra = input_buffer.data[src + 1u];
output_buffer.data[dst] = (ra & 0x0000ffffu) | (bg & 0xffff0000u);
output_buffer.data[dst + 1u] = (bg & 0x0000ffffu) | (ra & 0xffff0000u);
}
File diff suppressed because it is too large Load Diff
-165
View File
@@ -1,165 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_TRANSFER_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_TRANSFER_H_
#include "common/abi.h"
#include "common/common.h"
#include "graphics/host_gpu/gpuTiler.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <functional>
#include <span>
#include <utility>
#include <vector>
namespace Libs::Graphics {
class CommandBuffer;
struct GraphicContext;
struct VulkanBuffer;
struct VulkanImage;
struct DepthStencilVulkanImage;
struct VulkanSwapchain;
struct BufferImageCopy {
uint32_t offset;
uint32_t pitch;
uint32_t dst_level;
uint32_t width;
uint32_t height;
uint32_t copy_height = 0;
uint32_t dst_layer = 0;
int dst_x;
int dst_y;
int dst_z = 0;
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
};
struct ImageBufferCopy {
uint32_t offset;
uint32_t pitch;
uint32_t src_level;
uint32_t width;
uint32_t height;
uint32_t copy_height = 0;
uint32_t src_layer = 0;
int src_x;
int src_y;
int src_z = 0;
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor;
};
struct ImageImageCopy {
explicit ImageImageCopy(VulkanImage& source): src_image(source) {}
VulkanImage& src_image;
uint32_t src_level = 0;
uint32_t dst_level = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t src_layer = 0;
uint32_t dst_layer = 0;
vk::ImageAspectFlags src_aspect = vk::ImageAspectFlagBits::eColor;
vk::ImageAspectFlags dst_aspect = vk::ImageAspectFlagBits::eColor;
int src_x = 0;
int src_y = 0;
int src_z = 0;
int dst_x = 0;
int dst_y = 0;
int dst_z = 0;
};
namespace Transfer {
[[nodiscard]] std::vector<ImageBufferCopy>
MakeLayeredImageBufferCopies(uint32_t layers, uint64_t slice_size, uint32_t pitch, uint32_t width,
uint32_t height,
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor);
enum class StagingBufferType { Texture, Vertex, ReadBack };
using DownloadedImageConsumer = std::function<void(std::span<const uint8_t>)>;
class ScratchBuffer {
public:
explicit ScratchBuffer(uint64_t size);
~ScratchBuffer();
KYTY_CLASS_NO_COPY(ScratchBuffer);
[[nodiscard]] void* Data() const { return m_data; }
private:
void* m_data = nullptr;
};
void CopyImage(CommandBuffer& buffer, std::span<const ImageImageCopy> regions,
VulkanImage& dst_image, vk::ImageLayout dst_layout);
void CopyImageViaBuffer(CommandBuffer& buffer, VulkanImage& src_image,
vk::ImageAspectFlags src_aspect, VulkanImage& dst_image,
vk::ImageAspectFlags dst_aspect, uint32_t bytes_per_element,
vk::ImageLayout dst_layout);
void BlitToSwapchain(CommandBuffer& buffer, VulkanImage& src_image, VulkanSwapchain& dst_swapchain);
void ClearColorImage(CommandBuffer& buffer, VulkanImage& image, const vk::ClearColorValue& color);
void UploadImage(VulkanImage& dst_image, const void* src_data, uint64_t size, uint32_t src_pitch,
vk::ImageLayout dst_layout);
void UploadImage(DepthStencilVulkanImage& dst_image, const void* src_data, uint64_t size,
uint32_t src_pitch, vk::ImageAspectFlags aspect);
void UploadImage(VulkanImage& dst_image, const void* src_data, uint64_t size,
std::span<const BufferImageCopy> regions, vk::ImageLayout dst_layout);
void UploadTiledImage(VulkanImage& dst_image, const void* tiled_data, uint64_t tiled_size,
uint64_t linear_size, std::span<const GpuTileInfo> infos,
std::span<const BufferImageCopy> regions, vk::ImageLayout dst_layout);
void CopyImageImmediate(std::span<const ImageImageCopy> regions, VulkanImage& dst_image,
vk::ImageLayout dst_layout);
void DownloadImage(void* dst_data, uint64_t size, uint32_t dst_pitch, VulkanImage& src_image,
vk::ImageLayout src_layout,
vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor);
void DownloadImage(void* dst_data, uint64_t size, std::span<const ImageBufferCopy> regions,
VulkanImage& src_image, vk::ImageLayout src_layout);
// Invokes consumer synchronously after the image copy fence while the readback staging buffer is
// reserved. The supplied span is valid only for the call; the consumer must not retain it or
// re-enter Transfer.
void ProcessDownloadedImage(uint64_t size, std::span<const ImageBufferCopy> regions,
VulkanImage& src_image, vk::ImageLayout src_layout,
const DownloadedImageConsumer& consumer);
void DownloadTiledImage(void* tiled_data, uint64_t tiled_size, uint64_t linear_size,
std::span<const GpuTileInfo> infos,
std::span<const ImageBufferCopy> regions, VulkanImage& src_image,
vk::ImageLayout src_layout);
void UploadBuffer(StagingBufferType type, VulkanBuffer& dst_buffer, uint64_t dst_offset,
const void* src_data, uint64_t size);
void CopyBuffer(VulkanBuffer& src_buffer, VulkanBuffer& dst_buffer, uint64_t size);
void DownloadBuffer(VulkanBuffer& src_buffer, uint64_t src_offset, void* dst_data, uint64_t size);
void ReleaseCachedResources();
bool GuestBufferIsTiled(uint64_t vaddr, uint64_t size);
bool IsBlockCompressedFormat(vk::Format format);
uint32_t BlockCompressedBytesPerBlock(vk::Format format);
void WaitForQueueIdle();
inline std::pair<int, int> MipmapAtlasOffset(uint32_t lod, uint32_t width, uint32_t height) {
uint32_t mip_width = width;
uint32_t mip_height = height;
int mip_x = 0;
int mip_y = 0;
for (uint32_t i = 0; i < 16; i++) {
if (i == lod) {
return {mip_x, mip_y};
}
bool odd = ((i & 1u) != 0);
mip_x += static_cast<int>(odd ? mip_width : 0);
mip_y += static_cast<int>(odd ? 0 : mip_height);
mip_width >>= (mip_width > 1 ? 1u : 0u);
mip_height >>= (mip_height > 1 ? 1u : 0u);
}
return {mip_x, mip_y};
}
} // namespace Transfer
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_TRANSFER_H_
+53
View File
@@ -20,6 +20,7 @@
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/vma.h"
#include <algorithm>
#include <atomic>
#include <cinttypes>
@@ -111,6 +112,58 @@ void GraphicContext::LogMemoryBudget() const {
}
}
uint64_t GraphicContext::GetDeviceMemoryUsage() const {
if (!CanReportMemoryUsage() || allocator == nullptr) {
return 0;
}
VmaBudget budgets[VK_MAX_MEMORY_HEAPS] {};
vmaGetHeapBudgets(allocator, budgets);
const bool discrete =
physical_device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu;
uint64_t usage = 0;
for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) {
const bool device_local = static_cast<bool>(
physical_device_memory_properties.memoryHeaps[heap].flags &
vk::MemoryHeapFlagBits::eDeviceLocal);
if (!discrete || device_local) {
usage += budgets[heap].usage;
}
}
return usage;
}
uint64_t GraphicContext::GetTotalMemoryBudget() const {
if (allocator == nullptr) {
return 0;
}
VmaBudget budgets[VK_MAX_MEMORY_HEAPS] {};
vmaGetHeapBudgets(allocator, budgets);
const bool discrete =
physical_device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu;
uint64_t budget = 0;
uint64_t local = 0;
uint64_t usage = 0;
for (uint32_t heap = 0; heap < physical_device_memory_properties.memoryHeapCount; heap++) {
const auto& properties = physical_device_memory_properties.memoryHeaps[heap];
const bool device_local =
static_cast<bool>(properties.flags & vk::MemoryHeapFlagBits::eDeviceLocal);
if (device_local) {
local += properties.size;
}
if (!discrete || device_local) {
budget += CanReportMemoryUsage() ? budgets[heap].budget : properties.size;
usage += CanReportMemoryUsage() ? budgets[heap].usage : 0;
}
}
if (discrete) {
return budget - std::min<uint64_t>(budget / 8, 1024ull * 1024 * 1024);
}
constexpr uint64_t system_reserve = 8ull * 1024 * 1024 * 1024;
const auto available = budget > usage ? budget - usage : uint64_t {0};
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 ||
-29
View File
@@ -1,29 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_PRESENTATION_DISPLAYBUFFER_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_PRESENTATION_DISPLAYBUFFER_H_
#include "common/common.h"
namespace Libs::Graphics {
class CommandBuffer;
struct VideoOutVulkanImage;
} // namespace Libs::Graphics
namespace Libs::Presentation {
struct DisplayBufferImage {
Graphics::VideoOutVulkanImage* image = nullptr;
uint32_t index = static_cast<uint32_t>(-1);
uint64_t size = 0;
uint64_t pitch = 0;
};
DisplayBufferImage DisplayBufferFind(uint64_t addr, bool render_target = false);
int DisplayBufferSubmitFlipFromGpu(Graphics::CommandBuffer& buffer, int handle, int index,
int flip_mode, int64_t flip_arg, uint64_t& request_id);
uint64_t DisplayBufferPrepareNextFlipOnGpu(Graphics::CommandBuffer& buffer);
void DisplayBufferCompleteFlipFromGpu(uint64_t request_id);
void DisplayBufferWaitForFlipQueueSlot();
} // namespace Libs::Presentation
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_PRESENTATION_DISPLAYBUFFER_H_
+38
View File
@@ -0,0 +1,38 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_PRESENTER_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_PRESENTER_H_
#include "common/common.h"
#include <memory>
namespace Libs::Graphics {
class CommandBuffer;
class RenderContext;
struct ImageInfo;
struct WindowContext;
class Presenter final {
public:
struct Frame;
explicit Presenter(WindowContext& window);
~Presenter();
KYTY_CLASS_NO_COPY(Presenter);
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info);
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
CommandBuffer* producer = nullptr);
[[nodiscard]] Frame* PrepareLastFrame();
[[nodiscard]] bool IsGuestPaused() const noexcept;
[[nodiscard]] RenderContext& Renderer() const noexcept;
void Present(Frame& frame, bool reuse = false);
void Discard(Frame& frame);
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_PRESENTER_H_
File diff suppressed because it is too large Load Diff
+29 -8
View File
@@ -7,9 +7,11 @@
#include "common/abi.h"
#include "kernel/eventQueue.h"
#include <memory>
namespace Libs::Graphics {
// struct VulkanSwapchain;
struct VideoOutVulkanImage;
class CommandBuffer;
class Presenter;
} // namespace Libs::Graphics
namespace Libs::VideoOut {
@@ -22,8 +24,31 @@ struct VideoOutOutputOptions;
struct VideoOutBuffers;
struct VideoOutColorSettings;
void VideoOutInit(uint32_t width, uint32_t height);
void VideoOutWaitFlipDone(int handle, int index);
class VideoOutDriver final {
public:
struct Impl;
VideoOutDriver(uint32_t width, uint32_t height, Graphics::Presenter& presenter);
~VideoOutDriver();
KYTY_CLASS_NO_COPY(VideoOutDriver);
int SubmitFlipFromGpu(Graphics::CommandBuffer& buffer, int handle, int index, int flip_mode,
int64_t flip_arg, uint64_t& request_id);
void PrepareFlip(uint64_t request_id, Graphics::CommandBuffer& buffer);
void CompleteFlip(uint64_t request_id);
void SubmitFlipPreparation(uint64_t request_id);
void WaitForSubmitSlot();
void WaitFlipDone(int handle, int index);
[[nodiscard]] Impl& State() noexcept;
private:
std::unique_ptr<Impl> m_impl;
};
[[nodiscard]] VideoOutDriver& VideoOutInit(uint32_t width, uint32_t height,
Graphics::Presenter& presenter);
void VideoOutShutdown();
KYTY_SYSV_ABI int VideoOutOpen(int user_id, int bus_type, int index, const void* param);
KYTY_SYSV_ABI int VideoOutClose(int handle);
@@ -75,10 +100,6 @@ KYTY_SYSV_ABI int VideoOutLatencyMeasureSetStartPoint(int handle, uint32_t point
KYTY_SYSV_ABI int VideoOutColorSettingsSetGamma(VideoOutColorSettings* settings, float gamma);
KYTY_SYSV_ABI int VideoOutAdjustColor(int handle, const VideoOutColorSettings* settings);
void VideoOutBeginVblank();
void VideoOutEndVblank();
bool VideoOutFlipWindow(uint32_t micros);
} // namespace Libs::VideoOut
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_VIDEOOUT_H_ */
+4 -9
View File
@@ -6,16 +6,11 @@
namespace Libs::Graphics {
class CommandBuffer;
struct VideoOutVulkanImage;
struct PreparedFrame;
class Presenter;
void WindowInit(uint32_t width, uint32_t height);
void WindowRun();
PreparedFrame& WindowPrepareFrame(CommandBuffer& buffer, VideoOutVulkanImage& image);
PreparedFrame& WindowPrepareBlankFrame(CommandBuffer& buffer, uint32_t width, uint32_t height,
bool opaque);
void WindowPresentFrame(PreparedFrame& frame);
[[nodiscard]] Presenter& WindowInit(uint32_t width, uint32_t height);
void WindowRun();
void WindowShutdown();
} // namespace Libs::Graphics
File diff suppressed because it is too large Load Diff
+146 -80
View File
@@ -28,9 +28,11 @@
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/presenter.h"
#include "kernel/memory.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window.h"
@@ -40,7 +42,6 @@
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fmt/format.h>
#include <memory>
@@ -64,6 +65,14 @@ struct VulkanExtensions {
std::vector<vk::LayerProperties> available_layers;
};
vk::PhysicalDeviceVulkan13Features WindowContext::RequiredVulkan13Features() noexcept {
vk::PhysicalDeviceVulkan13Features features {};
features.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
features.dynamicRendering = VK_TRUE;
features.synchronization2 = VK_TRUE;
return features;
}
static bool HasExtension(const std::vector<vk::ExtensionProperties>& extensions, const char* name) {
return std::any_of(extensions.begin(), extensions.end(),
[name](const auto& ext) { return strcmp(ext.extensionName, name) == 0; });
@@ -79,8 +88,8 @@ static bool HasLayer(const std::vector<vk::LayerProperties>& layers, const char*
[name](const auto& layer) { return strcmp(layer.layerName, name) == 0; });
}
void VulkanGetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface,
SurfaceCapabilities& r) {
static void GetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface,
SurfaceCapabilities& r) {
RequireVulkanSuccess(physical_device.getSurfaceCapabilitiesKHR(surface, &r.capabilities),
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
@@ -96,22 +105,6 @@ void VulkanGetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::Surfac
return physical_device.getSurfacePresentModesKHR(surface, count, values);
});
EXIT_NOT_IMPLEMENTED(r.present_modes.empty());
r.format_srgb_bgra32 = false;
r.format_unorm_bgra32 = false;
for (const auto& f: r.formats) {
if (f.format == vk::Format::eB8G8R8A8Srgb &&
f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
r.format_srgb_bgra32 = true;
}
if (f.format == vk::Format::eB8G8R8A8Unorm &&
f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
r.format_unorm_bgra32 = true;
}
if (r.format_srgb_bgra32 && r.format_unorm_bgra32) {
break;
}
}
}
static bool CheckFormat(vk::PhysicalDevice device, vk::Format format, bool tile,
@@ -214,6 +207,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
device_features2.pNext = &features13;
device.getFeatures2(&device_features2);
const auto required_features13 = WindowContext::RequiredVulkan13Features();
const auto queue_family = VulkanFindQueueFamily(device, surface);
if (queue_family == static_cast<uint32_t>(-1)) {
@@ -239,10 +233,28 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("samplerMirrorClampToEdge is not supported\n");
skip_device = true;
}
if (features12.timelineSemaphore != VK_TRUE) {
LOGF("timelineSemaphore is not supported\n");
skip_device = true;
}
if (features13.robustImageAccess != VK_TRUE) {
LOGF("robustImageAccess is not supported\n");
skip_device = true;
}
if (required_features13.dynamicRendering == VK_TRUE &&
features13.dynamicRendering != VK_TRUE) {
LOGF("dynamicRendering is not supported\n");
skip_device = true;
}
if (required_features13.synchronization2 == VK_TRUE &&
features13.synchronization2 != VK_TRUE) {
LOGF("synchronization2 is not supported\n");
skip_device = true;
}
if (device_features2.features.sampleRateShading != VK_TRUE) {
LOGF("sampleRateShading is not supported\n");
skip_device = true;
}
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
LOGF("fragmentStoresAndAtomics is not supported\n");
@@ -309,7 +321,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
SurfaceCapabilities candidate_capabilities;
if (!skip_device) {
VulkanGetSurfaceCapabilities(device, surface, candidate_capabilities);
GetSurfaceCapabilities(device, surface, candidate_capabilities);
if (!(candidate_capabilities.capabilities.supportedUsageFlags &
vk::ImageUsageFlagBits::eTransferDst)) {
@@ -318,12 +330,6 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
}
}
if (!skip_device && !CheckFormat(device, vk::Format::eR8G8B8A8Srgb, false,
vk::FormatFeatureFlagBits::eBlitSrc)) {
LOGF("Format vk::Format::eR8G8B8A8Srgb cannot be used as transfer source\n");
skip_device = true;
}
if (!skip_device && !CheckFormat(device, vk::Format::eD32Sfloat, true,
vk::FormatFeatureFlagBits::eDepthStencilAttachment)) {
LOGF("Format vk::Format::eD32Sfloat cannot be used as depth buffer\n");
@@ -420,9 +426,9 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
}
}
static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device) {
static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device,
GraphicContext& graphics) {
EXIT_IF(physical_device == nullptr);
auto& graphics = g_window_ctx->graphic_ctx;
vk::PhysicalDeviceSubgroupSizeControlProperties subgroup_size_control {};
subgroup_size_control.sType = vk::StructureType::ePhysicalDeviceSubgroupSizeControlProperties;
@@ -463,10 +469,10 @@ static void VulkanInitSubgroupSizeControl(vk::PhysicalDevice physical_device) {
}
static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const VulkanExtensions& r,
uint32_t queue_family,
const std::vector<const char*>& device_extensions) {
uint32_t queue_family,
const std::vector<const char*>& device_extensions,
GraphicContext& graphics) {
EXIT_IF(physical_device == nullptr);
auto& graphics = g_window_ctx->graphic_ctx;
EXIT_IF(queue_family == static_cast<uint32_t>(-1));
const float queue_priority = 1.0f;
@@ -502,7 +508,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
vk::PhysicalDeviceVulkan13Features supported_features13 {};
supported_features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
supported_features13.pNext = nullptr;
vk::PhysicalDeviceVulkan12Features supported_features12 {};
supported_features12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
supported_features12.pNext = nullptr;
supported_features13.pNext = &supported_features12;
const auto robustness2_ext_enabled =
HasExtension(device_extensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
@@ -511,13 +521,21 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
supported_robustness2.sType = vk::StructureType::ePhysicalDeviceRobustness2FeaturesEXT;
supported_robustness2.pNext = nullptr;
if (robustness2_ext_enabled) {
supported_features13.pNext = &supported_robustness2;
supported_features12.pNext = &supported_robustness2;
}
vk::PhysicalDeviceFeatures2 supported_features2 {};
supported_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
supported_features2.pNext = &supported_features13;
physical_device.getFeatures2(&supported_features2);
const auto required_features13 = WindowContext::RequiredVulkan13Features();
EXIT_NOT_IMPLEMENTED(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 &&
supported_features13.synchronization2 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE);
features12.timelineSemaphore = VK_TRUE;
vk::PhysicalDeviceFeatures device_features {};
device_features.fragmentStoresAndAtomics = VK_TRUE;
@@ -529,8 +547,8 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.shaderImageGatherExtended = VK_TRUE;
device_features.independentBlend = VK_TRUE;
device_features.tessellationShader = VK_TRUE;
device_features.sampleRateShading = supported_features2.features.sampleRateShading;
graphics.sample_rate_shading_enabled = device_features.sampleRateShading == VK_TRUE;
device_features.sampleRateShading = VK_TRUE;
graphics.sample_rate_shading_enabled = true;
device_features.vertexPipelineStoresAndAtomics =
supported_features2.features.vertexPipelineStoresAndAtomics;
@@ -552,8 +570,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
robustness2.nullDescriptor = supported_robustness2.nullDescriptor;
}
vk::PhysicalDeviceVulkan13Features features13 {};
features13.sType = vk::StructureType::ePhysicalDeviceVulkan13Features;
auto features13 = required_features13;
features13.pNext =
robustness2_ext_enabled ? &robustness2 : const_cast<void*>(base_feature_chain);
features13.robustImageAccess = supported_features13.robustImageAccess;
@@ -773,12 +790,12 @@ static void VulkanCheckInstanceVersion() {
}
}
void VulkanCreate(WindowContext& window) {
EXIT_IF(window.window == nullptr);
EXIT_IF(window.graphic_ctx.instance != nullptr);
EXIT_IF(window.graphic_ctx.physical_device != nullptr);
EXIT_IF(window.graphic_ctx.device != nullptr);
EXIT_IF(window.surface_capabilities != nullptr);
void WindowContext::CreateVulkan() {
EXIT_IF(window == nullptr);
EXIT_IF(graphic_ctx.instance != nullptr);
EXIT_IF(graphic_ctx.physical_device != nullptr);
EXIT_IF(graphic_ctx.device != nullptr);
EXIT_IF(surface != nullptr);
auto get_instance_proc_addr =
reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
@@ -788,7 +805,7 @@ void VulkanCreate(WindowContext& window) {
VULKAN_HPP_DEFAULT_DISPATCHER.init(get_instance_proc_addr);
VulkanExtensions r;
VulkanGetExtensions(window.window, r);
VulkanGetExtensions(window, r);
VulkanCheckInstanceVersion();
vk::ApplicationInfo app_info {};
@@ -852,36 +869,35 @@ void VulkanCreate(WindowContext& window) {
inst_info.ppEnabledLayerNames =
(r.enable_validation_layers ? r.required_layers.data() : nullptr);
const vk::Result result = vk::createInstance(&inst_info, nullptr, &window.graphic_ctx.instance);
const vk::Result result = vk::createInstance(&inst_info, nullptr, &graphic_ctx.instance);
switch (result) {
case vk::Result::eSuccess: break;
case vk::Result::eErrorIncompatibleDriver:
EXIT("Unable to find a compatible Vulkan Driver");
default: EXIT("Could not create a Vulkan instance (for unknown reasons)");
}
VULKAN_HPP_DEFAULT_DISPATCHER.init(window.graphic_ctx.instance);
VULKAN_HPP_DEFAULT_DISPATCHER.init(graphic_ctx.instance);
if (r.enable_validation_layers) {
dbg_create_info.pNext = nullptr;
if (VulkanCreateDebugUtilsMessengerEXT(window.graphic_ctx.instance, &dbg_create_info,
nullptr, &window.graphic_ctx.debug_messenger) !=
if (VulkanCreateDebugUtilsMessengerEXT(graphic_ctx.instance, &dbg_create_info, nullptr,
&graphic_ctx.debug_messenger) !=
vk::Result::eSuccess) {
EXIT("Could not create debug messenger");
}
}
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(window.window,
static_cast<vk::Instance::CType>(window.graphic_ctx.instance),
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
&native_surface) == SDL_FALSE) {
EXIT("Could not create a Vulkan surface");
}
window.surface = native_surface;
surface = native_surface;
std::vector<const char*> device_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME,
"VK_KHR_maintenance1"};
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"};
#ifdef KYTY_ENABLE_DEBUG_PRINTF
if (Config::SpirvDebugPrintfEnabled()) {
@@ -889,23 +905,18 @@ void VulkanCreate(WindowContext& window) {
}
#endif
window.surface_capabilities = new SurfaceCapabilities {};
uint32_t queue_family = static_cast<uint32_t>(-1);
VulkanFindPhysicalDevice(window.graphic_ctx.instance, window.surface, device_extensions,
*window.surface_capabilities, window.graphic_ctx.physical_device,
queue_family);
VulkanFindPhysicalDevice(graphic_ctx.instance, surface, device_extensions,
surface_capabilities, graphic_ctx.physical_device, queue_family);
if (window.graphic_ctx.physical_device == nullptr) {
if (graphic_ctx.physical_device == nullptr) {
EXIT("Could not find suitable device");
}
window.graphic_ctx.physical_device.getProperties(
&window.graphic_ctx.physical_device_properties);
window.graphic_ctx.physical_device.getMemoryProperties(
&window.graphic_ctx.physical_device_memory_properties);
const auto& device_properties = window.graphic_ctx.GetPhysicalDeviceProperties();
graphic_ctx.physical_device.getProperties(&graphic_ctx.physical_device_properties);
graphic_ctx.physical_device.getMemoryProperties(&graphic_ctx.physical_device_memory_properties);
const auto& device_properties = graphic_ctx.GetPhysicalDeviceProperties();
LOGF("Select device: %s\n", device_properties.deviceName.data());
@@ -913,41 +924,96 @@ void VulkanCreate(WindowContext& window) {
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
"vkEnumerateDeviceExtensionProperties",
[&](uint32_t* count, vk::ExtensionProperties* values) {
return window.graphic_ctx.physical_device.enumerateDeviceExtensionProperties(
nullptr, count, values);
return graphic_ctx.physical_device.enumerateDeviceExtensionProperties(nullptr,
count,
values);
});
if (HasExtension(available_extensions, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) {
device_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
window.graphic_ctx.memory_budget_ext_enabled = true;
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);
}
}
memcpy(window.device_name, device_properties.deviceName, sizeof(window.device_name));
std::snprintf(window.processor_name, sizeof(window.processor_name), "%s",
memcpy(device_name, device_properties.deviceName, sizeof(device_name));
std::snprintf(processor_name, sizeof(processor_name), "%s",
Common::GetSystemInfo().ProcessorName.c_str());
VulkanInitSubgroupSizeControl(window.graphic_ctx.physical_device);
VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx);
window.graphic_ctx.device =
VulkanCreateDevice(window.graphic_ctx.physical_device, r, queue_family, device_extensions);
if (window.graphic_ctx.device == nullptr) {
graphic_ctx.device = VulkanCreateDevice(graphic_ctx.physical_device, r, queue_family,
device_extensions, graphic_ctx);
if (graphic_ctx.device == nullptr) {
EXIT("Could not create device");
}
VULKAN_HPP_DEFAULT_DISPATCHER.init(window.graphic_ctx.device);
window.graphic_ctx.queue_family = queue_family;
window.graphic_ctx.device.getQueue(queue_family, 0, &window.graphic_ctx.queue);
EXIT_IF(window.graphic_ctx.queue == nullptr);
VULKAN_HPP_DEFAULT_DISPATCHER.init(graphic_ctx.device);
graphic_ctx.queue_family = queue_family;
graphic_ctx.device.getQueue(queue_family, 0, &graphic_ctx.queue);
EXIT_IF(graphic_ctx.queue == nullptr);
if (!window.graphic_ctx.CreateAllocator()) {
if (!graphic_ctx.CreateAllocator()) {
EXIT("Could not create Vulkan memory allocator");
}
window.swapchain = VulkanCreateSwapchain(2);
RenderDocSetActiveWindow(window.graphic_ctx.instance, window.window);
render_context = std::make_unique<RenderContext>(graphic_ctx);
LibKernel::Memory::InstallGpuResources(&render_context->GetGpuResources());
presenter = std::make_unique<Presenter>(*this);
RenderDocSetActiveWindow(graphic_ctx.instance, window);
}
void WindowContext::RefreshSurfaceCapabilities() {
EXIT_IF(graphic_ctx.physical_device == nullptr || surface == nullptr);
GetSurfaceCapabilities(graphic_ctx.physical_device, surface, surface_capabilities);
}
void WindowContext::RecreateSurface() {
EXIT_IF(window == nullptr || graphic_ctx.instance == nullptr);
if (surface != nullptr) {
graphic_ctx.instance.destroySurfaceKHR(surface, nullptr);
surface = nullptr;
}
vk::SurfaceKHR::CType native_surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(window, static_cast<vk::Instance::CType>(graphic_ctx.instance),
&native_surface) == SDL_FALSE) {
EXIT("Could not recreate the Vulkan surface: %s\n", SDL_GetError());
}
surface = native_surface;
RefreshSurfaceCapabilities();
}
WindowContext::~WindowContext() {
presenter.reset();
LibKernel::Memory::InstallGpuResources(nullptr);
render_context.reset();
if (graphic_ctx.device != nullptr) {
RequireVulkanSuccess(graphic_ctx.device.waitIdle(), "wait for Vulkan device shutdown");
graphic_ctx.DestroyAllocator();
graphic_ctx.device.destroy(nullptr);
graphic_ctx.device = nullptr;
graphic_ctx.queue = nullptr;
}
if (surface != nullptr) {
graphic_ctx.instance.destroySurfaceKHR(surface, nullptr);
surface = nullptr;
}
if (graphic_ctx.debug_messenger != nullptr) {
graphic_ctx.instance.destroyDebugUtilsMessengerEXT(graphic_ctx.debug_messenger, nullptr);
graphic_ctx.debug_messenger = nullptr;
}
if (graphic_ctx.instance != nullptr) {
graphic_ctx.instance.destroy(nullptr);
graphic_ctx.instance = nullptr;
graphic_ctx.physical_device = nullptr;
}
if (window != nullptr) {
SDL_DestroyWindow(window);
window = nullptr;
}
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
}
} // namespace Libs::Graphics
+126 -297
View File
@@ -24,24 +24,20 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/subsystems.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/transfer.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h"
#include "loader/systemContent.h"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
@@ -61,7 +57,6 @@
namespace Libs::Graphics {
constexpr float FPS_UPDATE_TIME = 1.0f;
constexpr int KEYBOARD_CONTROLLER_ID = -1000;
struct EventKeyboard {
@@ -213,163 +208,36 @@ constexpr uint32_t KYTY_SDL_BUTTON_RMASK = SDL_BUTTON_RMASK; // NOLINT(hicpp-s
constexpr uint32_t KYTY_SDL_BUTTON_X1MASK = SDL_BUTTON_X1MASK; // NOLINT(hicpp-signed-bitwise)
constexpr uint32_t KYTY_SDL_BUTTON_X2MASK = SDL_BUTTON_X2MASK; // NOLINT(hicpp-signed-bitwise)
struct WindowGame {
void* private_data = nullptr;
void* event = nullptr;
namespace {
bool m_game_need_exit = {false};
bool m_game_is_paused = {false};
uint32_t m_screen_width = {0};
uint32_t m_screen_height = {0};
double m_current_time_seconds = {0.0};
double m_previous_time_seconds = {0.0};
int m_update_num = {0};
int m_frame_num = {0};
double m_update_time_seconds = {0.0};
double m_current_fps = {0.0};
int m_max_updates_per_frame = {4};
double m_update_fixed_time = 1.0 / 60.0;
int m_fps_frames_num = {0};
double m_fps_start_time = {0};
};
std::unique_ptr<WindowContext> g_window;
struct WindowGamePrivate {
explicit WindowGamePrivate(GraphicContext& graphics): graphics(graphics) {}
Common::Mutex mutex;
int skip_frames = 0;
GraphicContext& graphics;
};
WindowContext* g_window_ctx = nullptr;
static WindowGame g_window_game;
} // namespace
constexpr const char* KYTY_SDL_WINDOW_CAPTION = "Game";
constexpr uint32_t KYTY_SDL_WINDOW_FLAGS =
(static_cast<uint32_t>(SDL_WINDOW_HIDDEN) | static_cast<uint32_t>(SDL_WINDOW_VULKAN));
constexpr int KYTY_SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED; /*NOLINT(hicpp-signed-bitwise)*/
static void CalcFrameTime(WindowGame& game, double game_time_s) {
game.m_previous_time_seconds = game.m_current_time_seconds;
game.m_current_time_seconds = game_time_s;
game.m_frame_num++;
game.m_fps_frames_num++;
const auto fps_time = game.m_current_time_seconds - game.m_fps_start_time;
if (fps_time > FPS_UPDATE_TIME) {
game.m_current_fps = static_cast<double>(game.m_fps_frames_num) / fps_time;
game.m_fps_frames_num = 0;
game.m_fps_start_time = game.m_current_time_seconds;
}
}
static bool Init(WindowGame& /*game*/) {
return true;
}
static bool Update(WindowGame& /*game*/) {
return true;
}
static bool Render(WindowGame& /*game*/) {
return true;
}
static bool Close(WindowGame& /*game*/) {
return true;
}
static void SetPause(WindowGame& game, bool flag) {
static void SetPause(WindowLoopState& game, bool flag) {
LOGF("Pause: %s\n", flag ? "true" : "false");
game.m_game_is_paused = flag;
game.paused.store(flag, std::memory_order_release);
}
static bool RenderAndUpdate(WindowGame& game) {
static double lag = 0.0;
lag += game.m_current_time_seconds - game.m_previous_time_seconds;
int num = 0;
bool ok = true;
while (lag >= game.m_update_fixed_time) {
if (num < game.m_max_updates_per_frame) {
ok = ok && Update(game);
game.m_update_num++;
num++;
game.m_update_time_seconds = game.m_update_num * game.m_update_fixed_time;
}
lag -= game.m_update_fixed_time;
}
ok = ok && Render(game);
return ok;
}
bool GameInit(WindowGame& game, const Common::Timer& timer) {
EXIT_IF(game.private_data || game.event);
auto& graphics = g_window_ctx->graphic_ctx;
EXIT_IF(graphics.screen_width == 0 || graphics.screen_height == 0);
auto* pdata = new WindowGamePrivate(graphics);
game.private_data = pdata;
game.event = new SDL_Event;
game.m_screen_width = graphics.screen_width;
game.m_screen_height = graphics.screen_height;
CalcFrameTime(game, timer.GetTimeS());
return Init(game);
}
bool GameClose(WindowGame& game) {
EXIT_IF(!game.private_data || !game.event);
delete (static_cast<WindowGamePrivate*>(game.private_data));
delete (static_cast<SDL_Event*>(game.event));
return Close(game);
}
void GameShowWindow(WindowGame& game, const Common::Timer& timer) {
auto* p = static_cast<WindowGamePrivate*>(game.private_data);
EXIT_IF(!p);
p->mutex.Lock();
{
if (p->skip_frames > 0) {
p->skip_frames--;
LOGF("skip frame %d\n", p->skip_frames);
} else {
VideoOut::VideoOutBeginVblank();
if (VideoOut::VideoOutFlipWindow(0)) {
CalcFrameTime(game, timer.GetTimeS());
}
VideoOut::VideoOutEndVblank();
}
}
p->mutex.Unlock();
}
void GameEventQuit(WindowGame& game) {
static void GameEventQuit(WindowLoopState& game) {
LOGF("Event: quit\n");
game.m_game_need_exit = true;
game.need_exit = true;
}
void GameEventTerminate(WindowGame& game) {
static void GameEventTerminate(WindowLoopState& game) {
LOGF("Event: terminate\n");
game.m_game_need_exit = true;
game.need_exit = true;
}
void GameEventKeyboard(WindowGame& game, const EventKeyboard& key) {
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
#ifdef KYTY_DBG_INPUT
LOGF("Key: time = %.04f, %s%s, %s%s, %s, scan = %d, key = %d, mod = %04" PRIx16 "\n",
key.timestamp_seconds, (key.down ? "down" : ""), (key.up ? "up" : ""),
@@ -380,8 +248,10 @@ void GameEventKeyboard(WindowGame& game, const EventKeyboard& key) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
if (key.down) {
switch (key.key_code) {
case SDLK_ESCAPE: game.m_game_need_exit = true; break;
case SDLK_SPACE: SetPause(game, !game.m_game_is_paused); break;
case SDLK_ESCAPE: game.need_exit = true; break;
case SDLK_SPACE:
SetPause(game, !game.paused.load(std::memory_order_acquire));
break;
case SDLK_F1:
if (!key.repeat) {
RenderDocRequestCapture();
@@ -403,7 +273,7 @@ void GameEventKeyboard(WindowGame& game, const EventKeyboard& key) {
#endif
}
void GameEventMouse([[maybe_unused]] WindowGame& game, [[maybe_unused]] const EventMouse& mb) {
static void GameEventMouse([[maybe_unused]] const EventMouse& mb) {
#ifdef KYTY_DBG_INPUT
if (mb.wheel) {
LOGF("Mouse wheel: time = %.04f, %s[%d, %d]\n", mb.timestamp_seconds,
@@ -424,7 +294,7 @@ void GameEventMouse([[maybe_unused]] WindowGame& game, [[maybe_unused]] const Ev
#endif
}
void GameEventFinger([[maybe_unused]] WindowGame& game, [[maybe_unused]] const EventFinger& f) {
static void GameEventFinger([[maybe_unused]] const EventFinger& f) {
#ifdef KYTY_DBG_INPUT
if (f.motion) {
LOGF("Finger motion: time = %.04f, %d, %d, (x,y) = [%f, %f], (dx,dy) = [%f, %f], pressure "
@@ -439,8 +309,7 @@ void GameEventFinger([[maybe_unused]] WindowGame& game, [[maybe_unused]] const E
#endif
}
void GameEventController([[maybe_unused]] WindowGame& game,
[[maybe_unused]] const EventController& f) {
static void GameEventController([[maybe_unused]] const EventController& f) {
EXIT_NOT_IMPLEMENTED(f.remapped);
#ifdef KYTY_DBG_INPUT
@@ -486,116 +355,104 @@ void GameEventController([[maybe_unused]] WindowGame& game,
}
}
void GameEventDisplay([[maybe_unused]] WindowGame& game) {
auto* p = static_cast<WindowGamePrivate*>(game.private_data);
p->mutex.Lock();
game.m_screen_width = p->graphics.screen_width;
game.m_screen_height = p->graphics.screen_height;
p->mutex.Unlock();
}
void GameEventLowMemory(WindowGame& /*game*/) {
static void GameEventLowMemory() {
LOGF("Event: low_memory\n");
}
void GameEventWillEnterBackground(WindowGame& game) {
static void GameEventWillEnterBackground(WindowLoopState& game) {
LOGF("Event: will_enter_background\n");
SetPause(game, true);
}
void GameEventDidEnterBackground(WindowGame& /*game*/) {
static void GameEventDidEnterBackground() {
LOGF("Event: did_enter_background\n");
}
void GameEventWillEnterForeground(WindowGame& /*game*/) {
static void GameEventWillEnterForeground() {
LOGF("Event: will_enter_foreground\n");
}
void GameEventDidEnterForeground(WindowGame& game) {
static void GameEventDidEnterForeground(WindowLoopState& game) {
LOGF("Event: did_enter_foreground\n");
SetPause(game, false);
}
void GameEventResize(WindowGame& game, uint32_t new_width, uint32_t new_height) {
void WindowContext::Resize(uint32_t new_width, uint32_t new_height) {
EXIT_IF(new_width == 0 || new_height == 0);
auto* p = static_cast<WindowGamePrivate*>(game.private_data);
EXIT_IF(p == nullptr);
p->mutex.Lock();
{
p->skip_frames++;
p->graphics.screen_width = new_width;
p->graphics.screen_height = new_height;
game.m_screen_width = p->graphics.screen_width;
game.m_screen_height = p->graphics.screen_height;
}
p->mutex.Unlock();
graphic_ctx.screen_width = new_width;
graphic_ctx.screen_height = new_height;
}
static void ProcessWindowEvent(WindowGame& game, SDL_WindowEvent window) {
switch (window.event) {
case SDL_WINDOWEVENT_SHOWN: LOGF("Window %" PRIu32 " shown\n", window.windowID); break;
void WindowContext::ProcessWindowEvent(const SDL_WindowEvent& event) {
const auto& window_event = event;
switch (window_event.event) {
case SDL_WINDOWEVENT_SHOWN: LOGF("Window %" PRIu32 " shown\n", window_event.windowID); break;
case SDL_WINDOWEVENT_HIDDEN: LOGF("Window %" PRIu32 " hidden\n", window.windowID); break;
case SDL_WINDOWEVENT_HIDDEN:
LOGF("Window %" PRIu32 " hidden\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_EXPOSED: LOGF("Window %" PRIu32 " exposed\n", window.windowID); break;
case SDL_WINDOWEVENT_EXPOSED:
LOGF("Window %" PRIu32 " exposed\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_MOVED:
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n", window.windowID,
window.data1, window.data2);
LOGF("Window %" PRIu32 " moved to %" PRId32 ",%" PRId32 "\n",
window_event.windowID, window_event.data1, window_event.data2);
break;
case SDL_WINDOWEVENT_RESIZED:
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n", window.windowID,
window.data1, window.data2);
LOGF("Window %" PRIu32 " resized to %" PRId32 "x%" PRId32 "\n",
window_event.windowID, window_event.data1, window_event.data2);
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
GameEventResize(game, window.data1, window.data2);
Resize(window_event.data1, window_event.data2);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n", window.windowID,
window.data1, window.data2);
LOGF("Window %" PRIu32 " size changed to %" PRId32 "x%" PRId32 "\n",
window_event.windowID, window_event.data1, window_event.data2);
LOGF("m: %d\n", static_cast<int>(SDL_ThreadID()));
GameEventResize(game, window.data1, window.data2);
Resize(window_event.data1, window_event.data2);
break;
case SDL_WINDOWEVENT_MINIMIZED:
LOGF("Window %" PRIu32 " minimized\n", window.windowID);
LOGF("Window %" PRIu32 " minimized\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
LOGF("Window %" PRIu32 " maximized\n", window.windowID);
LOGF("Window %" PRIu32 " maximized\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_RESTORED:
LOGF("Window %" PRIu32 " restored\n", window.windowID);
LOGF("Window %" PRIu32 " restored\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_ENTER:
LOGF("Mouse entered window %" PRIu32 "\n", window.windowID);
LOGF("Mouse entered window %" PRIu32 "\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_LEAVE:
LOGF("Mouse left window %" PRIu32 "\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_LEAVE: LOGF("Mouse left window %" PRIu32 "\n", window.windowID); break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
LOGF("Window %" PRIu32 " gained keyboard focus\n", window.windowID);
LOGF("Window %" PRIu32 " gained keyboard focus\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
LOGF("Window %" PRIu32 " lost keyboard focus\n", window.windowID);
LOGF("Window %" PRIu32 " lost keyboard focus\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_CLOSE:
LOGF("Window %" PRIu32 " closed\n", window_event.windowID);
break;
case SDL_WINDOWEVENT_CLOSE: LOGF("Window %" PRIu32 " closed\n", window.windowID); break;
default:
LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window.windowID,
window.event);
LOGF("Window %" PRIu32 " got unknown event %" PRIu8 "\n", window_event.windowID,
window_event.event);
break;
}
}
static void ProcessDisplayEvent(WindowGame& game, SDL_DisplayEvent display) {
void WindowContext::ProcessDisplayEvent(const SDL_DisplayEvent& display) {
bool sdl = false;
switch (display.event) {
@@ -613,10 +470,6 @@ static void ProcessDisplayEvent(WindowGame& game, SDL_DisplayEvent display) {
default: LOGF("???\n");
}
if (!sdl) {
GameEventDisplay(game);
}
break;
}
default:
@@ -626,27 +479,9 @@ static void ProcessDisplayEvent(WindowGame& game, SDL_DisplayEvent display) {
}
}
int GamePollEvent(WindowGame& game) {
auto* event = static_cast<SDL_Event*>(game.event);
EXIT_IF(!event);
return SDL_PollEvent(event);
}
int GameWaitEvent(WindowGame& game) {
auto* event = static_cast<SDL_Event*>(game.event);
EXIT_IF(!event);
return SDL_WaitEvent(event);
}
void GameProcessEvent(WindowGame& game, double time_s) {
auto* event = static_cast<SDL_Event*>(game.event);
EXIT_IF(!event);
void WindowContext::ProcessEvent(double time_s) {
auto& game = loop;
auto* event = &game.event;
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
switch (event->type) {
@@ -654,13 +489,13 @@ void GameProcessEvent(WindowGame& game, double time_s) {
case SDL_APP_TERMINATING: GameEventTerminate(game); break;
case SDL_APP_LOWMEMORY: GameEventLowMemory(game); break;
case SDL_APP_LOWMEMORY: GameEventLowMemory(); break;
case SDL_APP_WILLENTERBACKGROUND: GameEventWillEnterBackground(game); break;
case SDL_APP_DIDENTERBACKGROUND: GameEventDidEnterBackground(game); break;
case SDL_APP_DIDENTERBACKGROUND: GameEventDidEnterBackground(); break;
case SDL_APP_WILLENTERFOREGROUND: GameEventWillEnterForeground(game); break;
case SDL_APP_WILLENTERFOREGROUND: GameEventWillEnterForeground(); break;
case SDL_APP_DIDENTERFOREGROUND: GameEventDidEnterForeground(game); break;
@@ -683,9 +518,9 @@ void GameProcessEvent(WindowGame& game, double time_s) {
break;
}
case SDL_WINDOWEVENT: ProcessWindowEvent(game, event->window); break;
case SDL_WINDOWEVENT: ProcessWindowEvent(event->window); break;
case SDL_DISPLAYEVENT: ProcessDisplayEvent(game, event->display); break;
case SDL_DISPLAYEVENT: ProcessDisplayEvent(event->display); break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
@@ -710,7 +545,7 @@ void GameProcessEvent(WindowGame& game, double time_s) {
mb.motion_y = 0;
mb.timestamp_seconds = time_s;
GameEventMouse(game, mb);
GameEventMouse(mb);
break;
}
@@ -737,7 +572,7 @@ void GameProcessEvent(WindowGame& game, double time_s) {
mb.motion_y = 0;
mb.timestamp_seconds = time_s;
GameEventMouse(game, mb);
GameEventMouse(mb);
break;
}
@@ -764,7 +599,7 @@ void GameProcessEvent(WindowGame& game, double time_s) {
mb.motion_y = event->motion.yrel;
mb.timestamp_seconds = time_s;
GameEventMouse(game, mb);
GameEventMouse(mb);
break;
}
@@ -786,7 +621,7 @@ void GameProcessEvent(WindowGame& game, double time_s) {
f.pressure = event->tfinger.pressure;
f.timestamp_seconds = time_s;
GameEventFinger(game, f);
GameEventFinger(f);
break;
}
@@ -808,7 +643,7 @@ void GameProcessEvent(WindowGame& game, double time_s) {
c.released = false;
c.timestamp_seconds = time_s;
GameEventController(game, c);
GameEventController(c);
break;
}
@@ -831,7 +666,7 @@ void GameProcessEvent(WindowGame& game, double time_s) {
c.released = (event->cbutton.state == SDL_RELEASED);
c.timestamp_seconds = time_s;
GameEventController(game, c);
GameEventController(c);
break;
}
@@ -855,67 +690,43 @@ void GameProcessEvent(WindowGame& game, double time_s) {
c.released = false;
c.timestamp_seconds = time_s;
GameEventController(game, c);
GameEventController(c);
break;
}
}
}
void GameMainLoop(WindowGame& game) {
bool need_exit = false;
void WindowContext::Run() {
Common::Timer timer;
timer.Start();
if (!GameInit(game, timer)) {
need_exit = true;
}
loop.event = {};
loop.need_exit = false;
loop.paused.store(false, std::memory_order_release);
for (;;) {
if (need_exit) {
break;
}
if (GamePollEvent(game) != 0) {
GameProcessEvent(game, timer.GetTimeS());
while (!loop.need_exit) {
if (SDL_PollEvent(&loop.event) != 0) {
ProcessEvent(timer.GetTimeS());
continue;
}
if (game.m_game_is_paused) {
if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) {
timer.Pause();
}
GameWaitEvent(game);
GameProcessEvent(game, timer.GetTimeS());
need_exit = game.m_game_need_exit;
if (SDL_WaitEvent(&loop.event) == 0) {
EXIT("%s\n", SDL_GetError());
}
ProcessEvent(timer.GetTimeS());
continue;
}
need_exit = game.m_game_need_exit;
if (game.m_game_is_paused) {
if (!timer.IsPaused()) {
timer.Pause();
}
} else {
if (timer.IsPaused()) {
timer.Resume();
}
if (!need_exit) {
need_exit = !RenderAndUpdate(game);
}
if (!need_exit) {
GameShowWindow(game, timer);
}
if (timer.IsPaused()) {
timer.Resume();
}
Common::Thread::SleepMicro(1000);
}
GameClose(game);
}
static void WindowCreate(WindowContext& context) {
@@ -949,29 +760,33 @@ static void WindowCreate(WindowContext& context) {
SDL_SetWindowResizable(context.window, SDL_FALSE);
}
void WindowInit(uint32_t width, uint32_t height) {
Presenter& WindowInit(uint32_t width, uint32_t height) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
EXIT_IF(g_window_ctx != nullptr);
EXIT_IF(g_window != nullptr);
g_window_ctx = new WindowContext;
auto window = std::make_unique<WindowContext>();
g_window_ctx->graphic_ctx.screen_width = width;
g_window_ctx->graphic_ctx.screen_height = height;
window->graphic_ctx.screen_width = width;
window->graphic_ctx.screen_height = height;
WindowCreate(*g_window_ctx);
VulkanCreate(*g_window_ctx);
GraphicsRenderInit(g_window_ctx->graphic_ctx);
WindowCreate(*window);
window->CreateVulkan();
auto& presenter = *window->presenter;
g_window = std::move(window);
return presenter;
}
void WindowRun() {
KYTY_PROFILER_THREAD("Thread_Window");
EXIT_IF(g_window == nullptr);
GameMainLoop(g_window_game);
g_window->Run();
}
// TODO: replace std::_Exit shutdown with full Vulkan teardown, then destroy
// the VMA allocator immediately before vkDestroyDevice.
Common::SubsystemsListSingleton::Instance()->ShutdownAll();
std::_Exit(0);
void WindowShutdown() {
if (g_window != nullptr) {
g_window.reset();
}
}
static int WindowIconRead(void* user, char* data, int size) {
@@ -1033,7 +848,7 @@ static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
EXIT_NOT_IMPLEMENTED(icon->surface == nullptr);
}
void WindowUpdateIcon() {
void WindowContext::UpdateIcon() {
static WindowIcon icon;
static bool icon_loaded = false;
@@ -1046,11 +861,11 @@ void WindowUpdateIcon() {
}
if (icon.surface != nullptr) {
SDL_SetWindowIcon(g_window_ctx->window, icon.surface);
SDL_SetWindowIcon(window, icon.surface);
}
}
void WindowUpdateTitle() {
void WindowContext::UpdateTitle() {
static char title[128];
static char title_id[12];
static char app_ver[12];
@@ -1059,15 +874,29 @@ void WindowUpdateTitle() {
Loader::SystemContentParamSfoGetString("TITLE_ID", title_id, sizeof(title_id));
static bool has_app_ver =
Loader::SystemContentParamSfoGetString("APP_VER", app_ver, sizeof(app_ver));
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
static uint64_t frame_num = 0;
static uint64_t fps_frames = 0;
static double current_fps = 0.0;
const auto now = Common::Timer::QueryPerformanceCounter();
const auto frequency = Common::Timer::QueryPerformanceFrequency();
frame_num++;
fps_frames++;
if (now - fps_start >= frequency) {
current_fps = static_cast<double>(fps_frames) * static_cast<double>(frequency) /
static_cast<double>(now - fps_start);
fps_start = now;
fps_frames = 0;
}
auto fps = fmt::format("{}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", (has_title ? title : ""),
(has_title ? ", " : ""), (has_title_id ? title_id : ""),
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""),
(has_app_ver ? " " : ""), g_window_ctx->device_name,
g_window_ctx->processor_name, g_window_game.m_frame_num,
g_window_game.m_current_fps);
(has_app_ver ? " " : ""), device_name, processor_name,
frame_num, current_fps);
SDL_SetWindowTitle(g_window_ctx->window, fps.c_str());
SDL_SetWindowTitle(window, fps.c_str());
}
} // namespace Libs::Graphics
@@ -1,31 +1,60 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
#include "SDL_events.h"
#include "SDL_video.h"
#include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <vector>
namespace Libs::Graphics {
class Presenter;
class RenderContext;
struct SurfaceCapabilities {
vk::SurfaceCapabilitiesKHR capabilities {};
std::vector<vk::SurfaceFormatKHR> formats;
std::vector<vk::PresentModeKHR> present_modes;
bool format_srgb_bgra32 = false;
bool format_unorm_bgra32 = false;
};
struct WindowLoopState {
SDL_Event event {};
bool need_exit = false;
std::atomic_bool paused = false;
};
struct WindowContext {
GraphicContext graphic_ctx;
VulkanSwapchain* swapchain = nullptr;
SDL_Window* window = nullptr;
bool window_hidden = true;
vk::SurfaceKHR surface = nullptr;
SurfaceCapabilities* surface_capabilities = nullptr;
WindowContext();
~WindowContext();
KYTY_CLASS_NO_COPY(WindowContext);
[[nodiscard]] static vk::PhysicalDeviceVulkan13Features
RequiredVulkan13Features() noexcept;
void CreateVulkan();
void RecreateSurface();
void RefreshSurfaceCapabilities();
void UpdateIcon();
void UpdateTitle();
void Resize(uint32_t width, uint32_t height);
void ProcessWindowEvent(const SDL_WindowEvent& event);
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
void ProcessEvent(double time_seconds);
void Run();
GraphicContext graphic_ctx;
SDL_Window* window = nullptr;
bool window_hidden = true;
vk::SurfaceKHR surface = nullptr;
SurfaceCapabilities surface_capabilities;
std::unique_ptr<RenderContext> render_context;
std::unique_ptr<Presenter> presenter;
WindowLoopState loop;
char device_name[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE] = {0};
char processor_name[64] = {0};
@@ -33,16 +62,6 @@ struct WindowContext {
Common::Mutex mutex;
};
extern WindowContext* g_window_ctx;
void VulkanGetSurfaceCapabilities(vk::PhysicalDevice physical_device, vk::SurfaceKHR surface,
SurfaceCapabilities& capabilities);
VulkanSwapchain* VulkanCreateSwapchain(uint32_t image_count);
void VulkanCreate(WindowContext& window);
void WindowUpdateIcon();
void WindowUpdateTitle();
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_WINDOW_WINDOWINTERNAL_H_
@@ -726,6 +726,7 @@ struct ShaderInfo {
std::vector<StageInput> inputs;
std::vector<StageOutput> outputs;
int32_t vertex_offset_sgpr = -1;
bool has_bitwise_xor = false;
bool operator==(const ShaderInfo& other) const = default;
};
@@ -87,7 +87,7 @@ bool ValidateOptions(const Program& program, const ShaderInfoOptions& options, s
}
void CollectVertexInputs(const Program& program, const ShaderVertexInputInfo* vertex,
ShaderInfo& info) {
ShaderInfo& info) {
AddInput(info, StageInputKind::VertexIndex, 0, 1, "gl_VertexIndex");
AddInput(info, StageInputKind::InstanceIndex, 0, 1, "gl_InstanceIndex");
if (vertex == nullptr) {
@@ -130,7 +130,7 @@ void CollectPixelInputs(const ShaderPixelInputInfo* pixel, ShaderInfo& info) {
}
void CollectComputeInputs(const Program& program, const ShaderComputeInputInfo* compute,
ShaderInfo& info) {
ShaderInfo& info) {
if (compute != nullptr) {
if (compute->group_id[0] || compute->group_id[1] || compute->group_id[2]) {
AddInput(info, StageInputKind::WorkgroupId, 0, 3, "gl_WorkGroupID");
@@ -197,7 +197,7 @@ bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::
if (!program.resource_tracking_complete || program.shader_info_complete) {
if (error != nullptr) {
*error = !program.resource_tracking_complete ? "shader resources were not tracked"
: "shader info already collected";
: "shader info already collected";
}
return false;
}
@@ -208,6 +208,18 @@ bool CollectShaderInfo(Program& program, const ShaderInfoOptions& options, std::
auto next = program.info;
next.inputs.clear();
next.outputs.clear();
next.has_bitwise_xor =
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto& block) {
return std::any_of(block.instructions.begin(), block.instructions.end(),
[](const auto& inst) {
switch (inst.op) {
case Opcode::BitwiseXorU32:
case Opcode::BitwiseXor3U32:
case Opcode::XorAddU32: return true;
default: return false;
}
});
});
switch (program.stage) {
case ShaderType::Vertex: CollectVertexInputs(program, options.vertex, next); break;
case ShaderType::Pixel: CollectPixelInputs(options.pixel, next); break;
@@ -139,9 +139,8 @@ uint32_t EmitVsharpDwordLoad(EmitterState& state, uint32_t dword_index) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_push_constant_uint, pointer,
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index / 4u),
ConstantU32(state, dword_index % 4u)});
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
return value;
}
@@ -149,8 +148,8 @@ uint32_t EmitVsharpDwordLoad(EmitterState& state, uint32_t dword_index) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer,
state.vsharp_storage_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.vsharp_storage_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
return value;
}
@@ -192,8 +191,8 @@ void EmitRegisterVariables(EmitterState& state) {
{OpVariable, state.ptr_func_uint, state.dispatch_pc_variable, StorageClassFunction});
}
if (state.pixel_valid_mask_variable != 0) {
state.builder.AddFunction({OpVariable, state.ptr_func_uint,
state.pixel_valid_mask_variable, StorageClassFunction});
state.builder.AddFunction({OpVariable, state.ptr_func_uint, state.pixel_valid_mask_variable,
StorageClassFunction});
state.builder.AddFunction(
{OpStore, state.pixel_valid_mask_variable, ConstantU32(state, 1)});
}
@@ -226,9 +225,9 @@ void EmitComputeInputRegisters(EmitterState& state) {
if (!cs->group_id[i]) {
continue;
}
const auto pointer = PointerForRegister(
state,
{IR::RegisterFile::Scalar, static_cast<uint32_t>(cs->workgroup_register) + reg_offset});
const auto pointer =
PointerForRegister(state, {IR::RegisterFile::Scalar,
static_cast<uint32_t>(cs->workgroup_register) + reg_offset});
if (pointer != 0) {
state.builder.AddFunction(
{OpStore, pointer,
@@ -238,9 +237,9 @@ void EmitComputeInputRegisters(EmitterState& state) {
}
if (cs->tg_size_en) {
const auto pointer = PointerForRegister(
state,
{IR::RegisterFile::Scalar, static_cast<uint32_t>(cs->workgroup_register) + reg_offset});
const auto pointer =
PointerForRegister(state, {IR::RegisterFile::Scalar,
static_cast<uint32_t>(cs->workgroup_register) + reg_offset});
if (pointer != 0) {
const uint32_t wave_size = cs->wave_size != 0 ? cs->wave_size : 64u;
const uint32_t total_threads = std::max<uint32_t>(cs->threads_num[0], 1u) *
@@ -263,7 +262,7 @@ void EmitComputeInputRegisters(EmitterState& state) {
state.builder.AddFunction(
{OpIEqual, state.bool_type, is_first, wave_id, ConstantU32(state, 0)});
state.builder.AddFunction({OpSelect, state.uint_type, first_bit, is_first,
ConstantU32(state, 0x80000000u), ConstantU32(state, 0)});
ConstantU32(state, 0x80000000u), ConstantU32(state, 0)});
state.builder.AddFunction(
{OpBitwiseOr, state.uint_type, base, wave_bits, ConstantU32(state, waves)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, packed, base, first_bit});
@@ -360,7 +359,7 @@ void EmitVertexInputRegisters(EmitterState& state) {
const auto instance_index = PointerForRegister(state, {IR::RegisterFile::Vector, 8});
if (instance_index != 0) {
state.builder.AddFunction({OpStore, instance_index,
EmitInputScalarU32(state, IR::StageInputKind::InstanceIndex)});
EmitInputScalarU32(state, IR::StageInputKind::InstanceIndex)});
}
}
@@ -861,7 +860,7 @@ uint32_t DispatcherTargetValue(EmitterState& state, uint32_t block_id) {
void EmitDispatcherStoreTarget(EmitterState& state, uint32_t block_id) {
state.builder.AddFunction({OpStore, state.dispatch_pc_variable,
ConstantU32(state, DispatcherTargetValue(state, block_id))});
ConstantU32(state, DispatcherTargetValue(state, block_id))});
}
void EmitDispatcherExit(EmitterState& state);
@@ -912,7 +911,7 @@ void EmitDispatcherStoreSelectorTarget(EmitterState& state, const CFG::Terminato
for (uint32_t i = 0; i < term.indirect_selector_values.size(); i++) {
const auto match = state.builder.AllocateId();
state.builder.AddFunction({OpIEqual, state.bool_type, match, selector_value,
ConstantU32(state, term.indirect_selector_values[i])});
ConstantU32(state, term.indirect_selector_values[i])});
const auto next = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, next, match,
@@ -949,7 +948,7 @@ void EmitDispatcherStoreIndirectTarget(EmitterState& state, const CFG::Terminato
for (uint32_t i = 0; i < count; i++) {
const auto match = state.builder.AllocateId();
state.builder.AddFunction({OpIEqual, state.bool_type, match, pc_value,
ConstantU32(state, term.indirect_target_pcs[i])});
ConstantU32(state, term.indirect_target_pcs[i])});
const auto next = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, next, match,
@@ -1001,8 +1000,8 @@ void EmitDispatcherSwitch(EmitterState& state, const IR::Program& program) {
const auto done = state.builder.AllocateId();
state.builder.AddFunction(
{OpIEqual, state.bool_type, done, pc_value, ConstantU32(state, UINT32_MAX)});
state.builder.AddFunction({OpLoopMerge, state.dispatch_merge_label,
state.dispatch_continue_label, LoopControlNone});
state.builder.AddFunction(
{OpLoopMerge, state.dispatch_merge_label, state.dispatch_continue_label, LoopControlNone});
state.builder.AddFunction(
{OpBranchConditional, done, state.dispatch_merge_label, state.dispatch_select_label});
@@ -304,73 +304,72 @@ struct EmitterState {
Builder builder;
const IR::Program& program;
const IR::ResourceSnapshot& resources;
const ShaderVertexInputInfo* vertex_input_info = nullptr;
const ShaderPixelInputInfo* pixel_input_info = nullptr;
const ShaderComputeInputInfo* compute_input_info = nullptr;
ShaderType stage = ShaderType::Unknown;
uint32_t wave_size = 64;
bool exact_subgroup_operations = false;
bool per_invocation_masks = false;
uint32_t void_type = 0;
uint32_t bool_type = 0;
uint32_t uint_type = 0;
uint32_t uint_pair_type = 0;
uint32_t int_pair_type = 0;
uint32_t int_type = 0;
uint32_t float_type = 0;
uint32_t vec2_uint_type = 0;
uint32_t vec3_uint_type = 0;
uint32_t vec4_uint_type = 0;
uint32_t vec2_int_type = 0;
uint32_t vec3_int_type = 0;
uint32_t vec4_int_type = 0;
uint32_t vec2_float_type = 0;
uint32_t vec3_float_type = 0;
uint32_t vec4_float_type = 0;
uint32_t ptr_func_uint = 0;
uint32_t ptr_input_float = 0;
uint32_t ptr_input_bool = 0;
uint32_t ptr_input_int = 0;
uint32_t ptr_input_uint = 0;
uint32_t ptr_input_vec2_float = 0;
uint32_t ptr_input_vec3_float = 0;
uint32_t ptr_input_vec2_int = 0;
uint32_t ptr_input_vec3_int = 0;
uint32_t ptr_input_vec4_int = 0;
uint32_t ptr_input_vec2_uint = 0;
uint32_t ptr_input_vec3_uint = 0;
uint32_t ptr_input_vec4_uint = 0;
uint32_t ptr_input_vec4_float = 0;
uint32_t sample_mask_array_type = 0;
uint32_t ptr_output_int = 0;
uint32_t ptr_output_sample_mask_array = 0;
uint32_t ptr_output_float = 0;
uint32_t ptr_output_vec4_float = 0;
uint32_t per_vertex_type = 0;
uint32_t ptr_output_per_vertex = 0;
uint32_t storage_runtime_array_type = 0;
uint32_t storage_buffer_type = 0;
uint32_t ptr_storage_buffer = 0;
uint32_t ptr_storage_buffer_uint = 0;
uint32_t storage_buffer_array_type = 0;
uint32_t ptr_storage_buffer_array = 0;
uint32_t storage_buffer_variable = 0;
uint32_t address_memory_array_type = 0;
uint32_t ptr_address_memory_array = 0;
uint32_t address_memory_variable = 0;
uint32_t gds_variable = 0;
uint32_t push_constant_u32x4_array_type = 0;
uint32_t push_constant_rows_array_type = 0;
uint32_t push_constant_block_type = 0;
uint32_t ptr_push_constant_block = 0;
uint32_t ptr_push_constant_uint = 0;
uint32_t push_constant_variable = 0;
uint32_t vsharp_storage_variable = 0;
uint32_t flattened_srt_variable = 0;
uint32_t lds_array_type = 0;
uint32_t ptr_workgroup_array = 0;
uint32_t ptr_workgroup_uint = 0;
uint32_t lds_variable = 0;
const ShaderVertexInputInfo* vertex_input_info = nullptr;
const ShaderPixelInputInfo* pixel_input_info = nullptr;
const ShaderComputeInputInfo* compute_input_info = nullptr;
ShaderType stage = ShaderType::Unknown;
uint32_t wave_size = 64;
bool exact_subgroup_operations = false;
bool per_invocation_masks = false;
uint32_t void_type = 0;
uint32_t bool_type = 0;
uint32_t uint_type = 0;
uint32_t uint_pair_type = 0;
uint32_t int_pair_type = 0;
uint32_t int_type = 0;
uint32_t float_type = 0;
uint32_t vec2_uint_type = 0;
uint32_t vec3_uint_type = 0;
uint32_t vec4_uint_type = 0;
uint32_t vec2_int_type = 0;
uint32_t vec3_int_type = 0;
uint32_t vec4_int_type = 0;
uint32_t vec2_float_type = 0;
uint32_t vec3_float_type = 0;
uint32_t vec4_float_type = 0;
uint32_t ptr_func_uint = 0;
uint32_t ptr_input_float = 0;
uint32_t ptr_input_bool = 0;
uint32_t ptr_input_int = 0;
uint32_t ptr_input_uint = 0;
uint32_t ptr_input_vec2_float = 0;
uint32_t ptr_input_vec3_float = 0;
uint32_t ptr_input_vec2_int = 0;
uint32_t ptr_input_vec3_int = 0;
uint32_t ptr_input_vec4_int = 0;
uint32_t ptr_input_vec2_uint = 0;
uint32_t ptr_input_vec3_uint = 0;
uint32_t ptr_input_vec4_uint = 0;
uint32_t ptr_input_vec4_float = 0;
uint32_t sample_mask_array_type = 0;
uint32_t ptr_output_int = 0;
uint32_t ptr_output_sample_mask_array = 0;
uint32_t ptr_output_float = 0;
uint32_t ptr_output_vec4_float = 0;
uint32_t per_vertex_type = 0;
uint32_t ptr_output_per_vertex = 0;
uint32_t storage_runtime_array_type = 0;
uint32_t storage_buffer_type = 0;
uint32_t ptr_storage_buffer = 0;
uint32_t ptr_storage_buffer_uint = 0;
uint32_t storage_buffer_array_type = 0;
uint32_t ptr_storage_buffer_array = 0;
uint32_t storage_buffer_variable = 0;
uint32_t address_memory_array_type = 0;
uint32_t ptr_address_memory_array = 0;
uint32_t address_memory_variable = 0;
uint32_t gds_variable = 0;
uint32_t push_constant_array_type = 0;
uint32_t push_constant_block_type = 0;
uint32_t ptr_push_constant_block = 0;
uint32_t ptr_push_constant_uint = 0;
uint32_t push_constant_variable = 0;
uint32_t vsharp_storage_variable = 0;
uint32_t flattened_srt_variable = 0;
uint32_t lds_array_type = 0;
uint32_t ptr_workgroup_array = 0;
uint32_t ptr_workgroup_uint = 0;
uint32_t lds_variable = 0;
std::array<SampledImageDescriptors, 6> sampled_images;
uint32_t sampler_type = 0;
uint32_t sampler_array_type = 0;
@@ -237,9 +237,9 @@ uint32_t BuiltInForInput(IR::StageInputKind kind) {
void AddInputAnnotationsAndNames(EmitterState& state) {
if (state.subgroup_local_invocation_id_variable != 0) {
state.builder.AddName(state.subgroup_local_invocation_id_variable,
"gl_SubgroupInvocationID");
"gl_SubgroupInvocationID");
state.builder.AddAnnotation({OpDecorate, state.subgroup_local_invocation_id_variable,
DecorationBuiltIn, BuiltInSubgroupLocalInvocationId});
DecorationBuiltIn, BuiltInSubgroupLocalInvocationId});
if (state.stage == ShaderType::Pixel) {
state.builder.AddAnnotation(
{OpDecorate, state.subgroup_local_invocation_id_variable, DecorationFlat});
@@ -373,13 +373,10 @@ void AddVsharpAnnotationsAndNames(EmitterState& state) {
state.builder.AddName(state.push_constant_block_type, "BufferResource");
state.builder.AddName(state.push_constant_variable, "vsharp");
state.builder.AddAnnotation(
{OpDecorate, state.push_constant_u32x4_array_type, DecorationArrayStride, 4});
state.builder.AddAnnotation(
{OpDecorate, state.push_constant_rows_array_type, DecorationArrayStride, 16});
{OpDecorate, state.push_constant_array_type, DecorationArrayStride, 4});
state.builder.AddAnnotation({OpMemberDecorate, state.push_constant_block_type, 0,
DecorationOffset, bind.push_constant_offset});
state.builder.AddAnnotation(
{OpDecorate, state.push_constant_block_type, DecorationBlock});
DecorationOffset, bind.push_constant_offset});
state.builder.AddAnnotation({OpDecorate, state.push_constant_block_type, DecorationBlock});
}
if (state.vsharp_storage_variable != 0) {
const auto* storage = DescriptorBinding(state, IR::DescriptorBindingKind::UserData);
@@ -439,11 +436,10 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.ptr_address_memory_array = state.builder.AllocateId();
}
if (state.push_constant_variable != 0) {
state.push_constant_u32x4_array_type = state.builder.AllocateId();
state.push_constant_rows_array_type = state.builder.AllocateId();
state.push_constant_block_type = state.builder.AllocateId();
state.ptr_push_constant_block = state.builder.AllocateId();
state.ptr_push_constant_uint = state.builder.AllocateId();
state.push_constant_array_type = state.builder.AllocateId();
state.push_constant_block_type = state.builder.AllocateId();
state.ptr_push_constant_block = state.builder.AllocateId();
state.ptr_push_constant_uint = state.builder.AllocateId();
}
state.lds_array_type = state.builder.AllocateId();
state.ptr_workgroup_array = state.builder.AllocateId();
@@ -517,7 +513,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddExtInstImport(state.glsl_std450, "GLSL.std.450");
state.builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
state.interface_variables);
state.interface_variables);
if (state.stage == ShaderType::Compute) {
uint32_t local_x = state.needs_compute_derivatives ? 2u : 1u;
uint32_t local_y = state.needs_compute_derivatives ? 2u : 1u;
@@ -563,8 +559,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddType({OpTypeVoid, state.void_type});
state.builder.AddType({OpTypeBool, state.bool_type});
state.builder.AddType({OpTypeInt, state.uint_type, 32, 0});
state.builder.AddType(
{OpTypeStruct, state.uint_pair_type, state.uint_type, state.uint_type});
state.builder.AddType({OpTypeStruct, state.uint_pair_type, state.uint_type, state.uint_type});
state.builder.AddType({OpTypeInt, state.int_type, 32, 1});
state.builder.AddType({OpTypeStruct, state.int_pair_type, state.int_type, state.int_type});
state.builder.AddType({OpTypeFloat, state.float_type, 32});
@@ -583,8 +578,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
{OpTypePointer, state.ptr_input_float, StorageClassInput, state.float_type});
state.builder.AddType(
{OpTypePointer, state.ptr_input_bool, StorageClassInput, state.bool_type});
state.builder.AddType(
{OpTypePointer, state.ptr_input_int, StorageClassInput, state.int_type});
state.builder.AddType({OpTypePointer, state.ptr_input_int, StorageClassInput, state.int_type});
state.builder.AddType(
{OpTypePointer, state.ptr_input_uint, StorageClassInput, state.uint_type});
state.builder.AddType(
@@ -607,7 +601,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
{OpTypePointer, state.ptr_input_vec4_float, StorageClassInput, state.vec4_float_type});
if (state.subgroup_local_invocation_id_variable != 0) {
state.builder.AddType({OpVariable, state.ptr_input_uint,
state.subgroup_local_invocation_id_variable, StorageClassInput});
state.subgroup_local_invocation_id_variable, StorageClassInput});
}
for (const auto& input: state.inputs) {
uint32_t ptr_type = state.ptr_input_uint;
@@ -643,15 +637,15 @@ void EmitHeaderAndTypes(EmitterState& state) {
if (state.per_vertex_variable != 0) {
state.builder.AddType({OpTypeStruct, state.per_vertex_type, state.vec4_float_type});
state.builder.AddType({OpTypePointer, state.ptr_output_per_vertex, StorageClassOutput,
state.per_vertex_type});
state.builder.AddType({OpVariable, state.ptr_output_per_vertex,
state.per_vertex_variable, StorageClassOutput});
state.per_vertex_type});
state.builder.AddType({OpVariable, state.ptr_output_per_vertex, state.per_vertex_variable,
StorageClassOutput});
}
for (const auto& binding: state.outputs) {
if (binding.kind == IR::StageOutputKind::Parameter ||
binding.kind == IR::StageOutputKind::Mrt) {
state.builder.AddType({OpVariable, state.ptr_output_vec4_float, binding.variable_id,
StorageClassOutput});
state.builder.AddType(
{OpVariable, state.ptr_output_vec4_float, binding.variable_id, StorageClassOutput});
}
}
if (state.depth_variable != 0) {
@@ -662,52 +656,48 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddType(
{OpTypeArray, state.sample_mask_array_type, state.int_type, ConstantU32(state, 1)});
state.builder.AddType({OpTypePointer, state.ptr_output_sample_mask_array,
StorageClassOutput, state.sample_mask_array_type});
StorageClassOutput, state.sample_mask_array_type});
state.builder.AddType({OpVariable, state.ptr_output_sample_mask_array,
state.sample_mask_variable, StorageClassOutput});
state.sample_mask_variable, StorageClassOutput});
}
state.builder.AddType(
{OpTypeRuntimeArray, state.storage_runtime_array_type, state.uint_type});
state.builder.AddType({OpTypeRuntimeArray, state.storage_runtime_array_type, state.uint_type});
state.builder.AddType(
{OpTypeStruct, state.storage_buffer_type, state.storage_runtime_array_type});
state.builder.AddType({OpTypePointer, state.ptr_storage_buffer, StorageClassStorageBuffer,
state.storage_buffer_type});
state.builder.AddType({OpTypePointer, state.ptr_storage_buffer_uint,
StorageClassStorageBuffer, state.uint_type});
state.storage_buffer_type});
state.builder.AddType(
{OpTypePointer, state.ptr_storage_buffer_uint, StorageClassStorageBuffer, state.uint_type});
if (state.storage_buffer_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Buffers));
state.builder.AddType(
{OpTypeArray, state.storage_buffer_array_type, state.storage_buffer_type, count});
state.builder.AddType({OpTypePointer, state.ptr_storage_buffer_array,
StorageClassStorageBuffer, state.storage_buffer_array_type});
StorageClassStorageBuffer, state.storage_buffer_array_type});
state.builder.AddType({OpVariable, state.ptr_storage_buffer_array,
state.storage_buffer_variable, StorageClassStorageBuffer});
state.storage_buffer_variable, StorageClassStorageBuffer});
}
if (state.gds_variable != 0) {
state.builder.AddType({OpVariable, state.ptr_storage_buffer, state.gds_variable,
StorageClassStorageBuffer});
state.builder.AddType(
{OpVariable, state.ptr_storage_buffer, state.gds_variable, StorageClassStorageBuffer});
}
if (state.push_constant_variable != 0) {
const auto row_width = ConstantU32(state, 4);
const auto row_count = ConstantU32(
state, std::max((state.program.bindings.push_constant_size + 15u) / 16u, 1u));
const auto dword_count =
ConstantU32(state, state.program.bindings.push_constant_size / sizeof(uint32_t));
state.builder.AddType(
{OpTypeArray, state.push_constant_u32x4_array_type, state.uint_type, row_width});
state.builder.AddType({OpTypeArray, state.push_constant_rows_array_type,
state.push_constant_u32x4_array_type, row_count});
{OpTypeArray, state.push_constant_array_type, state.uint_type, dword_count});
state.builder.AddType(
{OpTypeStruct, state.push_constant_block_type, state.push_constant_rows_array_type});
{OpTypeStruct, state.push_constant_block_type, state.push_constant_array_type});
state.builder.AddType({OpTypePointer, state.ptr_push_constant_block,
StorageClassPushConstant, state.push_constant_block_type});
StorageClassPushConstant, state.push_constant_block_type});
state.builder.AddType({OpTypePointer, state.ptr_push_constant_uint,
StorageClassPushConstant, state.uint_type});
StorageClassPushConstant, state.uint_type});
state.builder.AddType({OpVariable, state.ptr_push_constant_block,
state.push_constant_variable, StorageClassPushConstant});
state.push_constant_variable, StorageClassPushConstant});
}
if (state.vsharp_storage_variable != 0) {
state.builder.AddType({OpVariable, state.ptr_storage_buffer,
state.vsharp_storage_variable, StorageClassStorageBuffer});
state.builder.AddType({OpVariable, state.ptr_storage_buffer, state.vsharp_storage_variable,
StorageClassStorageBuffer});
}
if (state.address_memory_variable != 0) {
const auto count =
@@ -715,13 +705,13 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddType(
{OpTypeArray, state.address_memory_array_type, state.storage_buffer_type, count});
state.builder.AddType({OpTypePointer, state.ptr_address_memory_array,
StorageClassStorageBuffer, state.address_memory_array_type});
StorageClassStorageBuffer, state.address_memory_array_type});
state.builder.AddType({OpVariable, state.ptr_address_memory_array,
state.address_memory_variable, StorageClassStorageBuffer});
state.address_memory_variable, StorageClassStorageBuffer});
}
if (state.flattened_srt_variable != 0) {
state.builder.AddType({OpVariable, state.ptr_storage_buffer,
state.flattened_srt_variable, StorageClassStorageBuffer});
state.builder.AddType({OpVariable, state.ptr_storage_buffer, state.flattened_srt_variable,
StorageClassStorageBuffer});
}
if (state.stage == ShaderType::Compute || state.needs_function_lds) {
const auto storage_class =
@@ -733,8 +723,8 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddType(
{OpTypePointer, state.ptr_workgroup_uint, storage_class, state.uint_type});
if (state.stage == ShaderType::Compute) {
state.builder.AddType({OpVariable, state.ptr_workgroup_array, state.lds_variable,
StorageClassWorkgroup});
state.builder.AddType(
{OpVariable, state.ptr_workgroup_array, state.lds_variable, StorageClassWorkgroup});
}
}
for (uint32_t i = 0; i < state.sampled_images.size(); i++) {
@@ -744,7 +734,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
const auto dimension = view == ImageViewKind::Dim3D ? Dim3D : Dim2D;
const auto arrayed = view == ImageViewKind::Dim2DArray ? 1u : 0u;
state.builder.AddType({OpTypeImage, image.image_type, component, dimension, 0, arrayed, 0,
1, ImageFormatUnknown});
1, ImageFormatUnknown});
state.builder.AddType({OpTypeSampledImage, image.sampled_image_type, image.image_type});
state.builder.AddType(
{OpTypePointer, image.pointer_type, StorageClassUniformConstant, image.image_type});
@@ -753,113 +743,110 @@ void EmitHeaderAndTypes(EmitterState& state) {
const auto count = ConstantU32(state, DescriptorCount(state, kind));
state.builder.AddType({OpTypeArray, image.array_type, image.image_type, count});
state.builder.AddType({OpTypePointer, image.array_pointer_type,
StorageClassUniformConstant, image.array_type});
StorageClassUniformConstant, image.array_type});
state.builder.AddType({OpVariable, image.array_pointer_type, image.variable,
StorageClassUniformConstant});
StorageClassUniformConstant});
}
}
state.builder.AddType({OpTypeSampler, state.sampler_type});
state.builder.AddType({OpTypePointer, state.ptr_uniform_sampler, StorageClassUniformConstant,
state.sampler_type});
state.sampler_type});
if (state.sampler_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Samplers));
state.builder.AddType(
{OpTypeArray, state.sampler_array_type, state.sampler_type, count});
state.builder.AddType({OpTypeArray, state.sampler_array_type, state.sampler_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_sampler_array,
StorageClassUniformConstant, state.sampler_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_sampler_array,
state.sampler_variable, StorageClassUniformConstant});
StorageClassUniformConstant, state.sampler_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_sampler_array, state.sampler_variable,
StorageClassUniformConstant});
}
state.builder.AddType({OpTypeImage, state.storage_image_type, state.float_type, Dim2D, 0, 0,
0, 2, ImageFormatUnknown});
state.builder.AddType({OpTypeImage, state.storage_image_type, state.float_type, Dim2D, 0, 0, 0,
2, ImageFormatUnknown});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image,
StorageClassUniformConstant, state.storage_image_type});
StorageClassUniformConstant, state.storage_image_type});
if (state.storage_image_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Storage2D));
state.builder.AddType(
{OpTypeArray, state.storage_image_array_type, state.storage_image_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_array,
StorageClassUniformConstant, state.storage_image_array_type});
StorageClassUniformConstant, state.storage_image_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_array,
state.storage_image_variable, StorageClassUniformConstant});
state.storage_image_variable, StorageClassUniformConstant});
}
state.builder.AddType({OpTypeImage, state.storage_image_2d_array_type, state.float_type,
Dim2D, 0, 1, 0, 2, ImageFormatUnknown});
state.builder.AddType({OpTypeImage, state.storage_image_2d_array_type, state.float_type, Dim2D,
0, 1, 0, 2, ImageFormatUnknown});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_2d_array,
StorageClassUniformConstant, state.storage_image_2d_array_type});
StorageClassUniformConstant, state.storage_image_2d_array_type});
if (state.storage_image_2d_array_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Storage2DArray));
state.builder.AddType({OpTypeArray, state.storage_image_2d_array_array_type,
state.storage_image_2d_array_type, count});
state.storage_image_2d_array_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_2d_array_array,
StorageClassUniformConstant,
state.storage_image_2d_array_array_type});
StorageClassUniformConstant,
state.storage_image_2d_array_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_2d_array_array,
state.storage_image_2d_array_variable,
StorageClassUniformConstant});
state.storage_image_2d_array_variable, StorageClassUniformConstant});
}
state.builder.AddType({OpTypeImage, state.storage_image_3d_type, state.float_type, Dim3D, 0,
0, 0, 2, ImageFormatUnknown});
state.builder.AddType({OpTypeImage, state.storage_image_3d_type, state.float_type, Dim3D, 0, 0,
0, 2, ImageFormatUnknown});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_3d,
StorageClassUniformConstant, state.storage_image_3d_type});
StorageClassUniformConstant, state.storage_image_3d_type});
if (state.storage_image_3d_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::Storage3D));
state.builder.AddType(
{OpTypeArray, state.storage_image_3d_array_type, state.storage_image_3d_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_3d_array,
StorageClassUniformConstant, state.storage_image_3d_array_type});
StorageClassUniformConstant, state.storage_image_3d_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_3d_array,
state.storage_image_3d_variable, StorageClassUniformConstant});
state.storage_image_3d_variable, StorageClassUniformConstant});
}
state.builder.AddType({OpTypeImage, state.storage_image_uint_type, state.uint_type, Dim2D, 0,
0, 0, 2, ImageFormatR32ui});
state.builder.AddType({OpTypeImage, state.storage_image_uint_type, state.uint_type, Dim2D, 0, 0,
0, 2, ImageFormatR32ui});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint,
StorageClassUniformConstant, state.storage_image_uint_type});
StorageClassUniformConstant, state.storage_image_uint_type});
if (state.storage_image_uint_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::StorageUint2D));
state.builder.AddType({OpTypeArray, state.storage_image_uint_array_type,
state.storage_image_uint_type, count});
state.storage_image_uint_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_array,
StorageClassUniformConstant, state.storage_image_uint_array_type});
StorageClassUniformConstant, state.storage_image_uint_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_uint_array,
state.storage_image_uint_variable, StorageClassUniformConstant});
state.storage_image_uint_variable, StorageClassUniformConstant});
}
state.builder.AddType({OpTypeImage, state.storage_image_uint_2d_array_type, state.uint_type,
Dim2D, 0, 1, 0, 2, ImageFormatR32ui});
Dim2D, 0, 1, 0, 2, ImageFormatR32ui});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_2d_array,
StorageClassUniformConstant, state.storage_image_uint_2d_array_type});
StorageClassUniformConstant, state.storage_image_uint_2d_array_type});
if (state.storage_image_uint_2d_array_variable != 0) {
const auto count = ConstantU32(
state, DescriptorCount(state, IR::DescriptorBindingKind::StorageUint2DArray));
state.builder.AddType({OpTypeArray, state.storage_image_uint_2d_array_array_type,
state.storage_image_uint_2d_array_type, count});
state.storage_image_uint_2d_array_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_2d_array_array,
StorageClassUniformConstant,
state.storage_image_uint_2d_array_array_type});
StorageClassUniformConstant,
state.storage_image_uint_2d_array_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_uint_2d_array_array,
state.storage_image_uint_2d_array_variable,
StorageClassUniformConstant});
state.storage_image_uint_2d_array_variable,
StorageClassUniformConstant});
}
state.builder.AddType({OpTypeImage, state.storage_image_uint_3d_type, state.uint_type, Dim3D,
0, 0, 0, 2, ImageFormatR32ui});
state.builder.AddType({OpTypeImage, state.storage_image_uint_3d_type, state.uint_type, Dim3D, 0,
0, 0, 2, ImageFormatR32ui});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_3d,
StorageClassUniformConstant, state.storage_image_uint_3d_type});
StorageClassUniformConstant, state.storage_image_uint_3d_type});
if (state.storage_image_uint_3d_variable != 0) {
const auto count =
ConstantU32(state, DescriptorCount(state, IR::DescriptorBindingKind::StorageUint3D));
state.builder.AddType({OpTypeArray, state.storage_image_uint_3d_array_type,
state.storage_image_uint_3d_type, count});
state.storage_image_uint_3d_type, count});
state.builder.AddType({OpTypePointer, state.ptr_uniform_storage_image_uint_3d_array,
StorageClassUniformConstant,
state.storage_image_uint_3d_array_type});
StorageClassUniformConstant,
state.storage_image_uint_3d_array_type});
state.builder.AddType({OpVariable, state.ptr_uniform_storage_image_uint_3d_array,
state.storage_image_uint_3d_variable,
StorageClassUniformConstant});
state.storage_image_uint_3d_variable, StorageClassUniformConstant});
}
state.builder.AddType(
{OpTypePointer, state.ptr_image_uint, StorageClassImage, state.uint_type});
+15 -8
View File
@@ -12,6 +12,7 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/shader/recompiler/ShaderDecoder.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/shaderVertexMetadata.h"
@@ -825,6 +826,11 @@ static void ShaderGetStaticInputInfoPS(
vs_info.stage.program != nullptr && !vs_info.stage.program->bindings.descriptors.empty()
? 1
: 0;
ps_info.push_constant_offset =
vs_info.stage.program != nullptr
? vs_info.stage.program->bindings.push_constant_offset +
vs_info.stage.program->bindings.push_constant_size
: 0;
for (int i = 0; i < 8; i++) {
ps_info.target_output_mode[i] = sh.target_output_mode[i];
@@ -991,7 +997,7 @@ static void ShaderAppendNativeSpecialization(std::vector<uint32_t>&
ids.push_back(program.bindings.push_constant_size);
ids.push_back(static_cast<uint32_t>(program.bindings.user_data_registers.size()));
ids.insert(ids.end(), program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end());
program.bindings.user_data_registers.end());
ids.push_back(static_cast<uint32_t>(program.bindings.descriptors.size()));
for (const auto& binding: program.bindings.descriptors) {
ids.push_back(static_cast<uint32_t>(binding.kind));
@@ -1284,9 +1290,9 @@ static void DumpShaderRecompilerSpirv(const char* type, uint64_t shader_hash,
static std::atomic_int id = 0;
const auto base_name = Config::GetShaderLogFolder() /
fmt::format("{:04d}_{:04d}_new_shader_{}_{:016x}",
GraphicsRunGetFrameNum(), id++, type, shader_hash);
const auto base_name =
Config::GetShaderLogFolder() /
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
Common::File::CreateDirectories(base_name.parent_path());
Common::File spv_file;
@@ -1335,9 +1341,9 @@ static void DumpShaderRecompilerOriginal(const char* type, uint64_t shader_hash,
static std::atomic_int id = 0;
const auto base_name = Config::GetShaderLogFolder() / "original" /
fmt::format("{:04d}_{:04d}_new_shader_{}_{:016x}",
GraphicsRunGetFrameNum(), id++, type, shader_hash);
const auto base_name =
Config::GetShaderLogFolder() / "original" /
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
Common::File::CreateDirectories(base_name.parent_path());
Common::File bin_file;
@@ -1437,7 +1443,7 @@ bool ShaderCompileSpirvPS(const HW::PixelShaderInfo& regs, const HW::ShaderRegis
options.user_data_count = regs.ps_regs.rsrc2.user_sgpr;
options.user_data = regs.ps_user_sgpr.value;
options.descriptor_set = input_info.descriptor_set;
options.push_constant_offset = 0;
options.push_constant_offset = input_info.push_constant_offset;
options.pixel_input_info = &input_info;
options.dump_ir = ShaderRecompilerTextDumpEnabled();
options.early_dump = options.dump_ir;
@@ -1591,6 +1597,7 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo& regs, const ShaderPixelInputIn
ret.crc32 = regs.ps_regs.chksum & 0xffffffffu;
ret.ids.push_back(input_info.descriptor_set);
ret.ids.push_back(input_info.push_constant_offset);
ret.ids.push_back(input_info.input_num);
ret.ids.push_back(input_info.ps_system_input_base);
ret.ids.push_back(static_cast<uint32_t>(input_info.ps_pos_x));
+1
View File
@@ -103,6 +103,7 @@ struct ShaderPixelInputInfo {
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
uint32_t mrt_output_mask = 0;
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
bool ps_pos_x = false;
bool ps_pos_y = false;
bool ps_pos_xy = false;

Some files were not shown because too many files have changed in this diff Show More