mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7351eae117 | ||
|
|
125287dc30 | ||
|
|
142b3c20fa | ||
|
|
f43567e12c | ||
|
|
c86d4b1f0d | ||
|
|
31fb3ec5af | ||
|
|
c3b2ae9733 | ||
|
|
93774ee37c | ||
|
|
a20cf5d298 | ||
|
|
a720b28b7f | ||
|
|
7638c25d76 | ||
|
|
85876388cf | ||
|
|
f60007e923 |
@@ -17,8 +17,10 @@ endif()
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
include(utils.cmake)
|
||||
include(CTest)
|
||||
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
|
||||
|
||||
@@ -113,6 +115,7 @@ add_custom_target( KytyGitVersion
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-D GIT_WORKING_DIRECTORY=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
@@ -183,6 +186,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 +210,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,17 +348,83 @@ 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
|
||||
)
|
||||
target_link_libraries(image_page_table_tests common fmt::fmt)
|
||||
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})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace Common {
|
||||
|
||||
|
||||
@@ -93,4 +93,8 @@ bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
+8
-18
@@ -160,24 +160,14 @@ static void LoadElf(const std::filesystem::path& elf, bool dbg_print_reloc = fal
|
||||
}
|
||||
|
||||
static void Execute() {
|
||||
int thread_model = 1;
|
||||
|
||||
if (thread_model == 0) {
|
||||
Common::Thread t([](void* /*unused*/) { Libs::Graphics::WindowRun(); }, nullptr);
|
||||
t.Detach();
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
} else {
|
||||
Common::Thread t(
|
||||
[](void* /*unused*/) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
},
|
||||
nullptr);
|
||||
t.Detach();
|
||||
Libs::Graphics::WindowRun();
|
||||
t.Join();
|
||||
}
|
||||
Common::Thread guest_thread(
|
||||
[](void* /*unused*/) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
},
|
||||
nullptr);
|
||||
Libs::Graphics::WindowRun();
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
void Run(const RunOptions& options) {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
#message("${GIT_EXECUTABLE}")
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always OUTPUT_VARIABLE KYTY_GIT_VERSION)
|
||||
else()
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" describe --tags --always
|
||||
WORKING_DIRECTORY "${GIT_WORKING_DIRECTORY}"
|
||||
OUTPUT_VARIABLE KYTY_GIT_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE GIT_RESULT
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT GIT_RESULT EQUAL 0)
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
endif()
|
||||
endif()
|
||||
string(STRIP ${KYTY_GIT_VERSION} KYTY_GIT_VERSION)
|
||||
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
|
||||
configure_file("${INPUT_FILE}" "${OUTPUT_FILE}")
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class AsyncJob final {
|
||||
public:
|
||||
using Task = std::function<void()>;
|
||||
|
||||
explicit AsyncJob(std::string thread_name = {})
|
||||
: m_thread_name(std::move(thread_name)), m_worker_thread(WorkerEntry, this) {}
|
||||
|
||||
~AsyncJob() {
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_stop_requested = true;
|
||||
m_task_available_condition.Signal();
|
||||
}
|
||||
|
||||
m_worker_thread.Join();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(AsyncJob);
|
||||
|
||||
void Execute(Task task) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_is_busy) {
|
||||
m_idle_condition.Wait(&m_mutex);
|
||||
}
|
||||
|
||||
m_task = std::move(task);
|
||||
m_is_busy = true;
|
||||
m_task_available_condition.Signal();
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_is_busy) {
|
||||
m_idle_condition.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void WorkerEntry(void* data) { static_cast<AsyncJob*>(data)->WorkerLoop(); }
|
||||
|
||||
void WorkerLoop() {
|
||||
if (!m_thread_name.empty()) {
|
||||
KYTY_PROFILER_THREAD(m_thread_name.c_str());
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
Task task;
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (!m_is_busy && !m_stop_requested) {
|
||||
m_task_available_condition.Wait(&m_mutex);
|
||||
}
|
||||
|
||||
if (!m_is_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
task = std::move(m_task);
|
||||
}
|
||||
|
||||
task();
|
||||
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_is_busy = false;
|
||||
m_idle_condition.SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_thread_name;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_task_available_condition;
|
||||
Common::CondVar m_idle_condition;
|
||||
Task m_task;
|
||||
bool m_is_busy = false;
|
||||
bool m_stop_requested = false;
|
||||
Common::Thread m_worker_thread;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_ */
|
||||
@@ -2,109 +2,36 @@
|
||||
#define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
|
||||
|
||||
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
|
||||
|
||||
class CommandScheduler {
|
||||
enum class Pm4ProcessResult { Complete, Blocked };
|
||||
|
||||
class Pm4Execution {
|
||||
public:
|
||||
static constexpr int BuffersNum = 8;
|
||||
|
||||
CommandScheduler(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders)
|
||||
: m_registers(registers), m_user_config(user_config), m_shaders(shaders) {}
|
||||
|
||||
bool Active() const { return m_current >= 0 && m_current < BuffersNum; }
|
||||
void CheckActive() const { EXIT_IF(!Active()); }
|
||||
|
||||
RenderCommandBuffer& Current() const {
|
||||
CheckActive();
|
||||
EXIT_IF(m_buffers[m_current] == nullptr);
|
||||
return *m_buffers[m_current];
|
||||
}
|
||||
|
||||
void Init() {
|
||||
if (m_current >= 0) {
|
||||
return;
|
||||
}
|
||||
for (auto& buf: m_buffers) {
|
||||
EXIT_IF(buf != nullptr);
|
||||
buf = new RenderCommandBuffer(m_registers, m_user_config, m_shaders);
|
||||
}
|
||||
m_current = 0;
|
||||
Current().Begin();
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
SubmitCurrent();
|
||||
BeginNext();
|
||||
}
|
||||
|
||||
CommandBuffer& FlushAndGetSubmitted() {
|
||||
auto& submitted = SubmitCurrent();
|
||||
BeginNext();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CopyBuffers(std::array<CommandBuffer*, BuffersNum>& out) const {
|
||||
for (int i = 0; i < BuffersNum; i++) {
|
||||
auto* buf = m_buffers[i];
|
||||
EXIT_IF(buf == nullptr);
|
||||
out[i] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitAll() {
|
||||
for (auto* buf: m_buffers) {
|
||||
EXIT_IF(buf == nullptr);
|
||||
buf->WaitForFenceAndReset();
|
||||
}
|
||||
}
|
||||
|
||||
void SubmitForReadback() {
|
||||
if (!Active()) {
|
||||
return;
|
||||
}
|
||||
SubmitCurrent();
|
||||
}
|
||||
|
||||
void ResumeAfterReadback() {
|
||||
if (!Active()) {
|
||||
return;
|
||||
}
|
||||
Current().WaitForFenceAndReset();
|
||||
Current().Begin();
|
||||
}
|
||||
[[nodiscard]] bool MadeProgress() const noexcept { return m_made_progress; }
|
||||
|
||||
private:
|
||||
CommandBuffer& SubmitCurrent() {
|
||||
auto& submitted = Current();
|
||||
submitted.End();
|
||||
submitted.Execute();
|
||||
return submitted;
|
||||
}
|
||||
friend class CommandProcessor;
|
||||
|
||||
void BeginNext() {
|
||||
m_current = (m_current + 1) % BuffersNum;
|
||||
Current().WaitForFenceAndReset();
|
||||
Current().Begin();
|
||||
}
|
||||
struct BufferCursor {
|
||||
uint32_t* next_packet = nullptr;
|
||||
uint32_t remaining_dw = 0;
|
||||
uint32_t total_dw = 0;
|
||||
uint32_t deferred_advance_dw = 0;
|
||||
};
|
||||
|
||||
RenderCommandBuffer* m_buffers[BuffersNum] = {};
|
||||
int m_current = -1;
|
||||
HW::Context& m_registers;
|
||||
HW::UserConfig& m_user_config;
|
||||
HW::Shader& m_shaders;
|
||||
std::vector<BufferCursor> m_buffer_stack;
|
||||
bool m_suspended = false;
|
||||
bool m_made_progress = false;
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
@@ -116,8 +43,8 @@ public:
|
||||
int64_t flip_arg = 0;
|
||||
};
|
||||
|
||||
CommandProcessor();
|
||||
~CommandProcessor() { KYTY_NOT_IMPLEMENTED; }
|
||||
explicit CommandProcessor(RenderContext& renderer): m_renderer(renderer) {}
|
||||
~CommandProcessor() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandProcessor);
|
||||
|
||||
@@ -128,26 +55,18 @@ public:
|
||||
void BufferFlushAndWait();
|
||||
void BufferWait();
|
||||
void BeginReadbackTransaction() {
|
||||
m_mutex.Lock();
|
||||
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_mutex.Unlock();
|
||||
m_readback_active = false;
|
||||
}
|
||||
|
||||
void RunLock() { m_run_mutex.Lock(); }
|
||||
void RunUnlock() { m_run_mutex.Unlock(); }
|
||||
|
||||
HW::Context& GetCtx() { return m_ctx; }
|
||||
HW::UserConfig& GetUcfg() { return m_ucfg; }
|
||||
HW::Shader& GetShCtx() { return m_sh_ctx; }
|
||||
@@ -182,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);
|
||||
@@ -196,16 +113,13 @@ public:
|
||||
|
||||
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
|
||||
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
|
||||
void SetEmbeddedDataMarker(const uint32_t* buffer, uint32_t num_dw, uint32_t align) {}
|
||||
void PushMarker(const char* str) {}
|
||||
void PopMarker() {}
|
||||
|
||||
void PrefetchL2(void* addr, uint32_t size) {}
|
||||
void ResetDeCe();
|
||||
void SetCeComplete(bool complete) { m_ce_complete = complete; }
|
||||
void WaitCe();
|
||||
void WaitDeDiff(uint32_t diff);
|
||||
void IncremenetDe();
|
||||
void IncremenetCe();
|
||||
void IncrementDe();
|
||||
void IncrementCe();
|
||||
|
||||
void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num);
|
||||
void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num);
|
||||
@@ -222,32 +136,29 @@ public:
|
||||
const volatile void* address, uint32_t count_in_dwords);
|
||||
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
|
||||
|
||||
void Run(uint32_t* data, uint32_t num_dw);
|
||||
Pm4ProcessResult Process(Pm4Execution& execution, uint32_t* buffer, uint32_t size_dw);
|
||||
void ProcessIndirectBuffer(uint32_t* buffer, uint32_t size_dw);
|
||||
|
||||
[[nodiscard]] const FlipInfo& GetFlip() const { return m_flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
|
||||
private:
|
||||
struct Counter {
|
||||
Common::Mutex mutex;
|
||||
Common::CondVar cond_var;
|
||||
uint32_t value = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
|
||||
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
void FinishCommandProcessors();
|
||||
void ProcessPm4(Pm4Execution& execution, size_t stop_depth);
|
||||
void SuspendPm4();
|
||||
|
||||
RenderCommandBuffer& CurrentBuffer() { return m_scheduler.Current(); }
|
||||
void CheckBuffer() const { m_scheduler.CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return GetRenderContext().GetGpuResources(); }
|
||||
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
|
||||
RenderCommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
|
||||
void CheckBuffer() const { GetScheduler().CheckActive(); }
|
||||
GpuResourceManager& GetGpuResources() const { return m_renderer.GetGpuResources(); }
|
||||
|
||||
RenderContext& m_renderer;
|
||||
HW::Context m_ctx;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
@@ -259,16 +170,10 @@ private:
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
inline static Common::Mutex m_mutex;
|
||||
inline static std::vector<CommandProcessor*> m_processors;
|
||||
inline static bool m_readback_active = false;
|
||||
inline static bool m_readback_finished = false;
|
||||
Common::Mutex m_run_mutex;
|
||||
|
||||
CommandScheduler m_scheduler;
|
||||
|
||||
Counter m_de_counter;
|
||||
Counter m_ce_counter;
|
||||
uint32_t m_de_count = 0;
|
||||
uint32_t m_ce_count = 0;
|
||||
bool m_ce_complete = 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"
|
||||
@@ -598,30 +597,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthRenderTarget) {
|
||||
uint32_t count = 1;
|
||||
|
||||
if (cmd_id == 0xC0016900) {
|
||||
HW::DepthZInfo r;
|
||||
|
||||
// r.expclear_enabled = (buffer[0] & 0x08000000u) != 0;
|
||||
// r.format = (buffer[0] >> Pm4::DB_Z_INFO_FORMAT_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_FORMAT_MASK; r.num_samples = (buffer[0] >>
|
||||
// Pm4::DB_Z_INFO_NUM_SAMPLES_SHIFT) & Pm4::DB_Z_INFO_NUM_SAMPLES_MASK; r.tile_mode_index =
|
||||
//(buffer[0] >> Pm4::DB_Z_INFO_TILE_MODE_INDEX_SHIFT) & Pm4::DB_Z_INFO_TILE_MODE_INDEX_MASK;
|
||||
// r.tile_surface_enable = ((buffer[0] >> Pm4::DB_Z_INFO_TILE_SURFACE_ENABLE_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_TILE_SURFACE_ENABLE_MASK) !=0
|
||||
// r.zrange_precision = (buffer[0] >> Pm4::DB_Z_INFO_ZRANGE_PRECISION_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_ZRANGE_PRECISION_MASK;
|
||||
|
||||
r.format = KYTY_PM4_GET(buffer[0], DB_Z_INFO, FORMAT);
|
||||
r.num_samples = KYTY_PM4_GET(buffer[0], DB_Z_INFO, NUM_SAMPLES);
|
||||
r.embedded_sample_locations = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(buffer[0], DB_Z_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.num_mip_levels = KYTY_PM4_GET(buffer[0], DB_Z_INFO, MAXMIP);
|
||||
r.tile_mode_index = KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_MODE_INDEX);
|
||||
r.plane_compression = KYTY_PM4_GET(buffer[0], DB_Z_INFO, DECOMPRESS_ON_N_ZPLANES);
|
||||
r.expclear_enabled = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_surface_enable = KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
r.zrange_precision = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ZRANGE_PRECISION);
|
||||
|
||||
cp.GetCtx().SetDepthZInfo(r);
|
||||
cp.GetCtx().SetDepthZInfo(HW::DepthZInfo::Decode(buffer[0]));
|
||||
} else if (cmd_id == 0xC0086900) {
|
||||
if (dw >= 22 && buffer[8] == 0xC0016900 && buffer[9] == Pm4::DB_DEPTH_INFO &&
|
||||
buffer[11] == 0xC0016900 && buffer[12] == Pm4::DB_DEPTH_VIEW &&
|
||||
@@ -632,51 +608,8 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthRenderTarget) {
|
||||
|
||||
HW::DepthRenderTarget z;
|
||||
|
||||
// z.z_info.expclear_enabled = (buffer[0] & 0x08000000u) != 0;
|
||||
// z.z_info.format = (buffer[0] >> Pm4::DB_Z_INFO_FORMAT_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_FORMAT_MASK; z.z_info.num_samples = (buffer[0] >>
|
||||
// Pm4::DB_Z_INFO_NUM_SAMPLES_SHIFT) & Pm4::DB_Z_INFO_NUM_SAMPLES_MASK;
|
||||
// z.z_info.tile_mode_index = (buffer[0] >>
|
||||
// Pm4::DB_Z_INFO_TILE_MODE_INDEX_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_TILE_MODE_INDEX_MASK; z.z_info.tile_surface_enable =
|
||||
// KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
// z.z_info.zrange_precision = (buffer[0] >> Pm4::DB_Z_INFO_ZRANGE_PRECISION_SHIFT) &
|
||||
// Pm4::DB_Z_INFO_ZRANGE_PRECISION_MASK;
|
||||
|
||||
z.z_info.format = KYTY_PM4_GET(buffer[0], DB_Z_INFO, FORMAT);
|
||||
z.z_info.num_samples = KYTY_PM4_GET(buffer[0], DB_Z_INFO, NUM_SAMPLES);
|
||||
z.z_info.embedded_sample_locations =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, ITERATE_FLUSH) != 0;
|
||||
z.z_info.partially_resident =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
z.z_info.num_mip_levels = KYTY_PM4_GET(buffer[0], DB_Z_INFO, MAXMIP);
|
||||
z.z_info.tile_mode_index = KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_MODE_INDEX);
|
||||
z.z_info.plane_compression =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, DECOMPRESS_ON_N_ZPLANES);
|
||||
z.z_info.expclear_enabled = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
z.z_info.tile_surface_enable =
|
||||
KYTY_PM4_GET(buffer[0], DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
z.z_info.zrange_precision = KYTY_PM4_GET(buffer[0], DB_Z_INFO, ZRANGE_PRECISION);
|
||||
|
||||
// z.stencil_info.expclear_enabled = (buffer[1] & 0x08000000u) != 0;
|
||||
// z.stencil_info.tile_split = (buffer[1] >> 13u) & 0x7u;
|
||||
// z.stencil_info.format = KYTY_PM4_GET(buffer[1],
|
||||
// DB_STENCIL_INFO, FORMAT); z.stencil_info.tile_mode_index =
|
||||
// KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
// z.stencil_info.tile_stencil_disable = KYTY_PM4_GET(buffer[1],
|
||||
// DB_STENCIL_INFO, TILE_STENCIL_DISABLE);
|
||||
z.stencil_info.format = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, FORMAT);
|
||||
z.stencil_info.texture_compatible_stencil =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ITERATE_FLUSH) != 0;
|
||||
z.stencil_info.partially_resident =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
z.stencil_info.tile_split = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, RESERVED_FIELD_1);
|
||||
z.stencil_info.tile_mode_index =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
z.stencil_info.expclear_enabled =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
z.stencil_info.tile_stencil_disable =
|
||||
KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_STENCIL_DISABLE) != 0;
|
||||
z.z_info = HW::DepthZInfo::Decode(buffer[0]);
|
||||
z.stencil_info = HW::DepthStencilInfo::Decode(buffer[1]);
|
||||
|
||||
z.z_read_base_addr = static_cast<uint64_t>(buffer[2]) << 8u;
|
||||
z.stencil_read_base_addr = static_cast<uint64_t>(buffer[3]) << 8u;
|
||||
@@ -1253,26 +1186,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetStencilInfo) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
|
||||
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_STENCIL_INFO);
|
||||
|
||||
HW::DepthStencilInfo r;
|
||||
|
||||
// r.expclear_enabled = (buffer[0] & 0x08000000u) != 0;
|
||||
// r.tile_split = (buffer[0] >> 13u) & 0x7u;
|
||||
// r.format = (buffer[0] >> Pm4::DB_STENCIL_INFO_FORMAT_SHIFT) &
|
||||
// Pm4::DB_STENCIL_INFO_FORMAT_MASK; r.tile_mode_index = (buffer[0] >>
|
||||
// Pm4::DB_STENCIL_INFO_TILE_MODE_INDEX_SHIFT) & Pm4::DB_STENCIL_INFO_TILE_MODE_INDEX_MASK;
|
||||
// r.tile_stencil_disable =
|
||||
// ((buffer[0] >> Pm4::DB_STENCIL_INFO_TILE_STENCIL_DISABLE_SHIFT) &
|
||||
// Pm4::DB_STENCIL_INFO_TILE_STENCIL_DISABLE_MASK) != 0;
|
||||
|
||||
r.format = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, FORMAT);
|
||||
r.texture_compatible_stencil = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.tile_split = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, RESERVED_FIELD_1);
|
||||
r.tile_mode_index = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
r.expclear_enabled = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_stencil_disable = KYTY_PM4_GET(buffer[1], DB_STENCIL_INFO, TILE_STENCIL_DISABLE) != 0;
|
||||
|
||||
cp.GetCtx().SetDepthStencilInfo(r);
|
||||
cp.GetCtx().SetDepthStencilInfo(HW::DepthStencilInfo::Decode(buffer[0]));
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1764,231 +1678,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.BufferFlush();
|
||||
cp.BufferWait();
|
||||
}
|
||||
|
||||
return (custom ? 7 : 6);
|
||||
return (cmd_id == 0xc0061050 ? 7 : 6);
|
||||
}
|
||||
|
||||
KYTY_CP_OP_PARSER(CpOpDispatchDirect) {
|
||||
@@ -2166,10 +1860,10 @@ KYTY_CP_OP_PARSER(CpOpBranch) {
|
||||
reinterpret_cast<uint64_t>(else_buffer), else_num_dw);
|
||||
|
||||
if (take_then) {
|
||||
cp.Run(then_buffer, then_num_dw);
|
||||
cp.ProcessIndirectBuffer(then_buffer, then_num_dw);
|
||||
} else if (mode == 2 && else_num_dw != 0) {
|
||||
EXIT_NOT_IMPLEMENTED(else_buffer == nullptr);
|
||||
cp.Run(else_buffer, else_num_dw);
|
||||
cp.ProcessIndirectBuffer(else_buffer, else_num_dw);
|
||||
}
|
||||
|
||||
return payload_dw;
|
||||
@@ -2250,9 +1944,6 @@ KYTY_CP_OP_PARSER(CpOpDmaData) {
|
||||
const uint64_t dst = buffer[3] | (static_cast<uint64_t>(buffer[4]) << 32u);
|
||||
|
||||
if (control == 0x60000000 && dst == 0x0003022c && (control2 >> 21u) == 0x141u) {
|
||||
auto* addr = reinterpret_cast<void*>(src);
|
||||
|
||||
cp.PrefetchL2(addr, control2 & 0x1fffffu);
|
||||
return 6;
|
||||
}
|
||||
|
||||
@@ -2496,7 +2187,7 @@ KYTY_CP_OP_PARSER(CpOpIncrementCeCounter) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008400);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 1);
|
||||
|
||||
cp.IncremenetCe();
|
||||
cp.IncrementCe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -2507,7 +2198,7 @@ KYTY_CP_OP_PARSER(CpOpIncrementDeCounter) {
|
||||
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0008500);
|
||||
EXIT_NOT_IMPLEMENTED(buffer[0] != 0);
|
||||
|
||||
cp.IncremenetDe();
|
||||
cp.IncrementDe();
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -2607,7 +2298,7 @@ KYTY_CP_OP_PARSER(CpOpIndirectBuffer) {
|
||||
|
||||
GraphicsDbgDumpDcb("ci", indirect_num_dw, indirect_buffer);
|
||||
|
||||
cp.Run(indirect_buffer, indirect_num_dw);
|
||||
cp.ProcessIndirectBuffer(indirect_buffer, indirect_num_dw);
|
||||
|
||||
return 3;
|
||||
}
|
||||
@@ -2800,11 +2491,10 @@ KYTY_CP_OP_PARSER(CpOpMarker) {
|
||||
// EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0001000);
|
||||
|
||||
uint32_t id = buffer[0] & 0xfffu;
|
||||
uint32_t align = (buffer[0] >> 12u) & 0xfu;
|
||||
uint32_t len_dw = ((cmd_id >> 16u) & 0x3fffu);
|
||||
|
||||
switch (id) {
|
||||
case 0x0: cp.SetEmbeddedDataMarker(buffer + 1, len_dw, align); break;
|
||||
case 0x0: break;
|
||||
case 0x4: cp.SetUserDataMarker(HW::UserSgprType::Vsharp); break;
|
||||
case 0xd: cp.SetUserDataMarker(HW::UserSgprType::Region); break;
|
||||
case 0x777: {
|
||||
@@ -2876,8 +2566,6 @@ KYTY_CP_OP_PARSER(CpOpPopMarker) {
|
||||
LOGF("Pop marker\n");
|
||||
}
|
||||
|
||||
cp.PopMarker();
|
||||
|
||||
return dw_num + 1;
|
||||
}
|
||||
|
||||
@@ -2893,8 +2581,6 @@ KYTY_CP_OP_PARSER(CpOpPushMarker) {
|
||||
LOGF("Push marker: %s\n", str);
|
||||
}
|
||||
|
||||
cp.PushMarker(str);
|
||||
|
||||
return dw_num + 1;
|
||||
}
|
||||
|
||||
@@ -2939,7 +2625,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) {
|
||||
@@ -2953,7 +2639,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) {
|
||||
@@ -2966,7 +2652,7 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
|
||||
}
|
||||
|
||||
if (ReleaseMemGcrNeedsBarrier(eop_event_type, gcr_cntl)) {
|
||||
cp.MemoryBarrier();
|
||||
cp.EmitGlobalBarrier();
|
||||
}
|
||||
|
||||
auto cache_action = ReleaseMemCacheActionFromGcr(gcr_cntl);
|
||||
@@ -3986,18 +3672,7 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_Z_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HW::DepthZInfo r;
|
||||
r.format = KYTY_PM4_GET(value, DB_Z_INFO, FORMAT);
|
||||
r.num_samples = KYTY_PM4_GET(value, DB_Z_INFO, NUM_SAMPLES);
|
||||
r.embedded_sample_locations = KYTY_PM4_GET(value, DB_Z_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(value, DB_Z_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.num_mip_levels = KYTY_PM4_GET(value, DB_Z_INFO, MAXMIP);
|
||||
r.tile_mode_index = KYTY_PM4_GET(value, DB_Z_INFO, TILE_MODE_INDEX);
|
||||
r.plane_compression = KYTY_PM4_GET(value, DB_Z_INFO, DECOMPRESS_ON_N_ZPLANES);
|
||||
r.expclear_enabled = KYTY_PM4_GET(value, DB_Z_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_surface_enable = KYTY_PM4_GET(value, DB_Z_INFO, TILE_SURFACE_ENABLE) != 0;
|
||||
r.zrange_precision = KYTY_PM4_GET(value, DB_Z_INFO, ZRANGE_PRECISION);
|
||||
cp.GetCtx().SetDepthZInfo(r);
|
||||
cp.GetCtx().SetDepthZInfo(HW::DepthZInfo::Decode(value));
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_DEPTH_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
@@ -4025,15 +3700,7 @@ void GraphicsInitJmpTablesCxIndirect() {
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_STENCIL_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
HW::DepthStencilInfo r;
|
||||
r.format = KYTY_PM4_GET(value, DB_STENCIL_INFO, FORMAT);
|
||||
r.texture_compatible_stencil = KYTY_PM4_GET(value, DB_STENCIL_INFO, ITERATE_FLUSH) != 0;
|
||||
r.partially_resident = KYTY_PM4_GET(value, DB_STENCIL_INFO, PARTIALLY_RESIDENT) != 0;
|
||||
r.tile_split = KYTY_PM4_GET(value, DB_STENCIL_INFO, RESERVED_FIELD_1);
|
||||
r.tile_mode_index = KYTY_PM4_GET(value, DB_STENCIL_INFO, TILE_MODE_INDEX);
|
||||
r.expclear_enabled = KYTY_PM4_GET(value, DB_STENCIL_INFO, ALLOW_EXPCLEAR) != 0;
|
||||
r.tile_stencil_disable = KYTY_PM4_GET(value, DB_STENCIL_INFO, TILE_STENCIL_DISABLE) != 0;
|
||||
cp.GetCtx().SetDepthStencilInfo(r);
|
||||
cp.GetCtx().SetDepthStencilInfo(HW::DepthStencilInfo::Decode(value));
|
||||
};
|
||||
|
||||
g_hw_ctx_indirect_func[Pm4::DB_Z_READ_BASE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
|
||||
|
||||
@@ -142,6 +142,24 @@ enum class StencilFormat : uint32_t {
|
||||
k8UInt = 1,
|
||||
};
|
||||
|
||||
enum class TextureCompatiblePlaneCompression : uint32_t {
|
||||
kDisable = 0x00000000,
|
||||
kEnable = 0x02900800,
|
||||
kBitMask = 0x02900800,
|
||||
};
|
||||
|
||||
enum class TextureCompatibleStencil : uint32_t {
|
||||
kDisable = 0x00000000,
|
||||
kEnable = 0x00100800,
|
||||
kBitMask = 0x00100800,
|
||||
};
|
||||
|
||||
enum class ZCompareBase : uint32_t {
|
||||
kZMin = 0x00000000,
|
||||
kZMax = 0x80000000,
|
||||
kBitMask = 0x80000000,
|
||||
};
|
||||
|
||||
enum class TileMode : uint32_t {
|
||||
kLinear = 0x00,
|
||||
kStandard256B = 0x01,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,33 +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 GraphicsRunWait();
|
||||
void GraphicsRunDone();
|
||||
int GraphicsRunGetFrameNum();
|
||||
[[nodiscard]] bool GraphicsRunIsCommandProcessorThread() noexcept;
|
||||
[[nodiscard]] CommandProcessor* GraphicsRunCurrentCommandProcessor() noexcept;
|
||||
void GraphicsRunFinishCommandProcessors();
|
||||
[[nodiscard]] bool GraphicsRunSubmissionLockHeld() noexcept;
|
||||
[[nodiscard]] bool GraphicsRunGpuLockHeld() noexcept;
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
|
||||
namespace Libs::Graphics::HW {
|
||||
|
||||
@@ -131,26 +132,66 @@ struct RenderTarget {
|
||||
};
|
||||
|
||||
struct DepthZInfo {
|
||||
uint32_t format = 0;
|
||||
uint32_t tile_mode_index = 0;
|
||||
uint32_t num_samples = 0;
|
||||
uint32_t zrange_precision = 0;
|
||||
bool tile_surface_enable = false;
|
||||
bool expclear_enabled = false;
|
||||
bool embedded_sample_locations = false;
|
||||
bool partially_resident = false;
|
||||
uint8_t num_mip_levels = 0;
|
||||
uint8_t plane_compression = 0;
|
||||
uint32_t format = 0;
|
||||
uint32_t num_samples = 0;
|
||||
Prospero::TextureCompatiblePlaneCompression texture_compatibility =
|
||||
Prospero::TextureCompatiblePlaneCompression::kDisable;
|
||||
Prospero::ZCompareBase z_compare_base = Prospero::ZCompareBase::kZMin;
|
||||
bool htile_acceleration = false;
|
||||
bool expclear_enabled = false;
|
||||
bool partially_resident = false;
|
||||
uint8_t max_mip_level = 0;
|
||||
|
||||
[[nodiscard]] static DepthZInfo Decode(uint32_t value) {
|
||||
DepthZInfo info;
|
||||
info.format = value & 0x3u;
|
||||
info.num_samples = (value >> 2u) & 0x3u;
|
||||
info.texture_compatibility = static_cast<Prospero::TextureCompatiblePlaneCompression>(
|
||||
value & Prospero::GpuEnumValue(Prospero::TextureCompatiblePlaneCompression::kBitMask));
|
||||
info.partially_resident = (value & 0x00001000u) != 0;
|
||||
info.max_mip_level = static_cast<uint8_t>((value >> 16u) & 0x0fu);
|
||||
info.expclear_enabled = (value & 0x08000000u) != 0;
|
||||
info.htile_acceleration = (value & 0x20000000u) != 0;
|
||||
info.z_compare_base = static_cast<Prospero::ZCompareBase>(
|
||||
value & Prospero::GpuEnumValue(Prospero::ZCompareBase::kBitMask));
|
||||
return info;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasValidTextureCompatibility() const {
|
||||
switch (texture_compatibility) {
|
||||
case Prospero::TextureCompatiblePlaneCompression::kDisable:
|
||||
case Prospero::TextureCompatiblePlaneCompression::kEnable: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DepthStencilInfo {
|
||||
uint32_t format = 0;
|
||||
uint32_t tile_mode_index = 0;
|
||||
uint32_t tile_split = 0;
|
||||
bool expclear_enabled = false;
|
||||
bool tile_stencil_disable = false;
|
||||
bool texture_compatible_stencil = false;
|
||||
bool partially_resident = false;
|
||||
uint32_t format = 0;
|
||||
Prospero::TextureCompatibleStencil texture_compatibility =
|
||||
Prospero::TextureCompatibleStencil::kDisable;
|
||||
bool expclear_enabled = false;
|
||||
bool htile_stencil_disabled = false;
|
||||
bool partially_resident = false;
|
||||
|
||||
[[nodiscard]] static DepthStencilInfo Decode(uint32_t value) {
|
||||
DepthStencilInfo info;
|
||||
info.format = value & 0x1u;
|
||||
info.texture_compatibility = static_cast<Prospero::TextureCompatibleStencil>(
|
||||
value & Prospero::GpuEnumValue(Prospero::TextureCompatibleStencil::kBitMask));
|
||||
info.partially_resident = (value & 0x00001000u) != 0;
|
||||
info.expclear_enabled = (value & 0x08000000u) != 0;
|
||||
info.htile_stencil_disabled = (value & 0x20000000u) != 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasValidTextureCompatibility() const {
|
||||
switch (texture_compatibility) {
|
||||
case Prospero::TextureCompatibleStencil::kDisable:
|
||||
case Prospero::TextureCompatibleStencil::kEnable: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DepthRenderTargetDepthInfo {
|
||||
|
||||
@@ -406,8 +406,8 @@ struct TextureBlockLayout {
|
||||
|
||||
static bool GetTextureBlockLayout(uint32_t format, uint32_t tile, TextureBlockLayout& out) {
|
||||
uint32_t width_log2 = 0, height_log2 = 0;
|
||||
if (tile == 1 && Gen5Standard256BLayout(format, &out.bytes, &out.texel_width,
|
||||
&out.texel_height, &width_log2, &height_log2)) {
|
||||
if (tile == 1 && Gen5Standard256BLayout(format, &out.bytes, &out.texel_width, &out.texel_height,
|
||||
&width_log2, &height_log2)) {
|
||||
out.block_size = 256;
|
||||
} else if (tile == 5 && Gen5Standard4KBLayout(format, &out.bytes, &out.texel_width,
|
||||
&out.texel_height, &width_log2, &height_log2)) {
|
||||
@@ -524,7 +524,7 @@ static void SetMacroMipLayout(const TextureBlockLayout& block, uint32_t tile, ui
|
||||
}
|
||||
|
||||
bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& out) {
|
||||
uint32_t levels, uint32_t tile, TileVolumeLayout& out) {
|
||||
if (width == 0 || height == 0 || depth == 0 || levels == 0 || levels > 16) {
|
||||
return false;
|
||||
}
|
||||
@@ -543,21 +543,26 @@ bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height
|
||||
TileBlockLayout block {};
|
||||
if (!TileGetBlockLayout(family, element.bytes, block)) return false;
|
||||
|
||||
out = {};
|
||||
out.family = family;
|
||||
out.bytes_per_element = element.bytes;
|
||||
out.texel_width = element.texel_width;
|
||||
out.texel_height = element.texel_height;
|
||||
out.block_depth = block.block_depth;
|
||||
out.first_tail_level = levels;
|
||||
out = {};
|
||||
out.family = family;
|
||||
out.bytes_per_element = element.bytes;
|
||||
out.texel_width = element.texel_width;
|
||||
out.texel_height = element.texel_height;
|
||||
out.block_depth = block.block_depth;
|
||||
out.first_tail_level = levels;
|
||||
const uint32_t width0 = (width + element.texel_width - 1u) / element.texel_width;
|
||||
const uint32_t height0 = (height + element.texel_height - 1u) / element.texel_height;
|
||||
const bool thick4 = family == TileBlockFamily::Standard4KB3D;
|
||||
const bool thick64 =
|
||||
family == TileBlockFamily::Standard64KB3D || family == TileBlockFamily::Prt64KB3D;
|
||||
const uint32_t max_tail = thick4 ? 5u : (thick64 ? 10u : 12u);
|
||||
uint32_t tail_width = thick4 ? block.block_width : block.block_width >> 1u;
|
||||
uint32_t tail_height = thick4 ? block.block_height >> 1u : block.block_height;
|
||||
uint32_t max_tail = 12u;
|
||||
if (thick4) {
|
||||
max_tail = 5u;
|
||||
} else if (thick64) {
|
||||
max_tail = 10u;
|
||||
}
|
||||
uint32_t tail_width = thick4 ? block.block_width : block.block_width >> 1u;
|
||||
uint32_t tail_height = thick4 ? block.block_height >> 1u : block.block_height;
|
||||
if (family == TileBlockFamily::Depth64KB && element.bytes < 4) {
|
||||
tail_width = 64;
|
||||
tail_height = 128;
|
||||
@@ -574,18 +579,23 @@ bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height
|
||||
}
|
||||
out.level_widths[level] = AlignUp(mip_width, block.block_width);
|
||||
out.level_heights[level] = AlignUp(mip_height, block.block_height);
|
||||
out.level_sizes[level] = static_cast<uint64_t>(block.block_depth) *
|
||||
out.level_widths[level] * out.level_heights[level] *
|
||||
element.bytes;
|
||||
out.level_sizes[level] = static_cast<uint64_t>(block.block_depth) *
|
||||
out.level_widths[level] * out.level_heights[level] * element.bytes;
|
||||
out.block_slice_size += out.level_sizes[level];
|
||||
}
|
||||
|
||||
const auto bytes_log2 = std::countr_zero(element.bytes);
|
||||
const auto bytes_log2 = std::countr_zero(element.bytes);
|
||||
const Gen5MipTailLocation* tail_locations = nullptr;
|
||||
if (thick4) {
|
||||
tail_locations = GEN5_MIP_TAIL_LOCATIONS_THICK_4KB[bytes_log2];
|
||||
} else if (thick64) {
|
||||
tail_locations = GEN5_MIP_TAIL_LOCATIONS_THICK_64KB[bytes_log2];
|
||||
} else {
|
||||
tail_locations = GEN5_MIP_TAIL_LOCATIONS_THIN_64KB[bytes_log2];
|
||||
}
|
||||
for (uint32_t level = out.first_tail_level; level < levels; ++level) {
|
||||
const auto index = level - out.first_tail_level;
|
||||
const auto tail = thick4 ? GEN5_MIP_TAIL_LOCATIONS_THICK_4KB[bytes_log2][index]
|
||||
: (thick64 ? GEN5_MIP_TAIL_LOCATIONS_THICK_64KB[bytes_log2][index]
|
||||
: GEN5_MIP_TAIL_LOCATIONS_THIN_64KB[bytes_log2][index]);
|
||||
const auto index = level - out.first_tail_level;
|
||||
const auto tail = tail_locations[index];
|
||||
out.level_sizes[level] = block.block_size;
|
||||
out.level_widths[level] = block.block_width;
|
||||
out.level_heights[level] = block.block_height;
|
||||
@@ -1047,7 +1057,7 @@ static constexpr uint32_t Depth64KB64XOffsetBytes(uint32_t x);
|
||||
static constexpr uint32_t Depth64KB64YOffsetBytes(uint32_t y);
|
||||
|
||||
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
TileBlockLayout& layout) {
|
||||
TileBlockLayout& layout) {
|
||||
if (!std::has_single_bit(bytes_per_element) || bytes_per_element > 16) {
|
||||
return false;
|
||||
}
|
||||
@@ -1067,13 +1077,25 @@ bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
};
|
||||
switch (family) {
|
||||
case TileBlockFamily::Standard256B:
|
||||
result.block_size = 256;
|
||||
result.block_width = bytes_per_element <= 2 ? 16 : (bytes_per_element <= 8 ? 8 : 4);
|
||||
result.block_size = 256;
|
||||
if (bytes_per_element <= 2) {
|
||||
result.block_width = 16;
|
||||
} else if (bytes_per_element <= 8) {
|
||||
result.block_width = 8;
|
||||
} else {
|
||||
result.block_width = 4;
|
||||
}
|
||||
result.block_height = result.block_size / (result.block_width * bytes_per_element);
|
||||
break;
|
||||
case TileBlockFamily::Standard4KB:
|
||||
result.block_size = 4096;
|
||||
result.block_width = bytes_per_element <= 2 ? 64 : (bytes_per_element <= 8 ? 32 : 16);
|
||||
result.block_size = 4096;
|
||||
if (bytes_per_element <= 2) {
|
||||
result.block_width = 64;
|
||||
} else if (bytes_per_element <= 8) {
|
||||
result.block_width = 32;
|
||||
} else {
|
||||
result.block_width = 16;
|
||||
}
|
||||
result.block_height = result.block_size / (result.block_width * bytes_per_element);
|
||||
break;
|
||||
case TileBlockFamily::Standard4KB3D: {
|
||||
@@ -1126,7 +1148,7 @@ bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
|
||||
}
|
||||
|
||||
bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z,
|
||||
uint32_t& byte_offset) {
|
||||
uint32_t& byte_offset) {
|
||||
TileBlockLayout expected {};
|
||||
if (!TileGetBlockLayout(layout.family, layout.bytes_per_element, expected) ||
|
||||
layout.block_size != expected.block_size || layout.block_width != expected.block_width ||
|
||||
@@ -1226,12 +1248,12 @@ bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, u
|
||||
}
|
||||
|
||||
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
|
||||
uint32_t& byte_offset) {
|
||||
uint32_t& byte_offset) {
|
||||
return TileGetBlockXor(layout, block_x, block_y, 0, byte_offset);
|
||||
}
|
||||
|
||||
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
|
||||
uint32_t block_z, uint32_t& byte_offset) {
|
||||
uint32_t block_z, uint32_t& byte_offset) {
|
||||
TileBlockLayout expected {};
|
||||
if (!TileGetBlockLayout(layout.family, layout.bytes_per_element, expected) ||
|
||||
layout.block_size != expected.block_size || layout.block_width != expected.block_width ||
|
||||
@@ -1377,8 +1399,8 @@ bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& htile_size
|
||||
}
|
||||
|
||||
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
|
||||
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
|
||||
uint32_t num_fragments_log2) {
|
||||
EXIT_IF(pitch != 0);
|
||||
// Prospero derives uncompressed depth/stencil as independent 64 KiB block surfaces.
|
||||
@@ -1410,8 +1432,8 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
|
||||
if (depth_bytes_total <= UINT32_MAX && stencil_bytes_total <= UINT32_MAX && htile_valid) {
|
||||
depth_size = {static_cast<uint32_t>(depth_bytes_total), 65536};
|
||||
stencil_size = stencil_format == 1
|
||||
? TileSizeAlign {static_cast<uint32_t>(stencil_bytes_total), 65536}
|
||||
: TileSizeAlign {};
|
||||
? TileSizeAlign {static_cast<uint32_t>(stencil_bytes_total), 65536}
|
||||
: TileSizeAlign {};
|
||||
htile_size = calculated_htile;
|
||||
return true;
|
||||
}
|
||||
@@ -1441,7 +1463,7 @@ uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
|
||||
}
|
||||
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t bytes_per_element, TileSizeAlign& total_size,
|
||||
uint32_t num_fragments_log2) {
|
||||
total_size = {};
|
||||
uint32_t block_width = 0;
|
||||
@@ -1466,7 +1488,7 @@ bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size) {
|
||||
total_size = {};
|
||||
if (width == 0 || height == 0 || levels == 0 || levels > 16 ||
|
||||
@@ -1578,7 +1600,7 @@ void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32
|
||||
|
||||
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture,
|
||||
TileSizeAlign& total_size) {
|
||||
TileSizeAlign& total_size) {
|
||||
EXIT_NOT_IMPLEMENTED(depth == 0);
|
||||
if (volume_texture) {
|
||||
TileVolumeLayout volume {};
|
||||
|
||||
@@ -1,549 +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 GROUP_SIZE = 64;
|
||||
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;
|
||||
uint32_t first;
|
||||
uint32_t count;
|
||||
};
|
||||
static_assert(sizeof(Push) == 60);
|
||||
|
||||
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;
|
||||
uint32_t elements = 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;
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
!TileGetBlockLayout(info.family, info.bytes_per_element, block) || info.width == 0 ||
|
||||
info.height == 0 || info.depth == 0 || info.pitch < info.width ||
|
||||
(!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 elements = 0, pitch_bytes = 0;
|
||||
EXIT_NOT_IMPLEMENTED(!CheckedMultiply(info.width, info.height, elements) ||
|
||||
!CheckedMultiply(elements, info.depth, elements) ||
|
||||
!CheckedMultiply(info.pitch, info.bytes_per_element, pitch_bytes) ||
|
||||
elements > UINT32_MAX || 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.elements = static_cast<uint32_t>(elements);
|
||||
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, ©);
|
||||
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);
|
||||
const uint64_t limit =
|
||||
static_cast<uint64_t>(
|
||||
graphics.GetPhysicalDeviceProperties().limits.maxComputeWorkGroupCount[0]) *
|
||||
GROUP_SIZE;
|
||||
for (const auto& dispatch: dispatches) {
|
||||
vk_command.bindPipeline(vk::PipelineBindPoint::eCompute,
|
||||
resources.pipelines[dispatch.pipeline_slot]);
|
||||
for (uint32_t first = 0; first < dispatch.elements;) {
|
||||
auto push = dispatch.push;
|
||||
push.first = first;
|
||||
push.count =
|
||||
static_cast<uint32_t>(std::min<uint64_t>(dispatch.elements - first, limit));
|
||||
vk_command.pushConstants(resources.pipeline_layout, vk::ShaderStageFlagBits::eCompute,
|
||||
0, sizeof(push), &push);
|
||||
vk_command.dispatch((push.count - 1u) / GROUP_SIZE + 1u, 1, 1);
|
||||
first += push.count;
|
||||
}
|
||||
}
|
||||
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, ©);
|
||||
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
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -30,6 +30,16 @@ constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
|
||||
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
|
||||
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
constexpr uint32_t NO_ACCESS_PROTECTION = PAGE_NOACCESS;
|
||||
constexpr uint32_t READ_ONLY_PROTECTION = PAGE_READONLY;
|
||||
constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
|
||||
#else
|
||||
constexpr uint32_t NO_ACCESS_PROTECTION = 0;
|
||||
constexpr uint32_t READ_ONLY_PROTECTION = 1;
|
||||
constexpr uint32_t READ_WRITE_PROTECTION = 2;
|
||||
#endif
|
||||
|
||||
thread_local bool g_in_fault_resolution = false;
|
||||
|
||||
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
|
||||
@@ -184,21 +194,22 @@ struct PageManager::Impl {
|
||||
|
||||
static uint32_t WatcherProtection(const PageState& page) {
|
||||
if (page.access_watchers != 0) {
|
||||
return PAGE_NOACCESS;
|
||||
return NO_ACCESS_PROTECTION;
|
||||
}
|
||||
if (page.write_watchers != 0) {
|
||||
return PAGE_READONLY;
|
||||
return READ_ONLY_PROTECTION;
|
||||
}
|
||||
return page.original_protection;
|
||||
}
|
||||
|
||||
static void PublishDelayedFaults(PageState& page, uint32_t old_protection,
|
||||
uint32_t new_protection) {
|
||||
if (old_protection == PAGE_NOACCESS && new_protection != PAGE_NOACCESS) {
|
||||
if (old_protection == NO_ACCESS_PROTECTION && new_protection != NO_ACCESS_PROTECTION) {
|
||||
page.late_read_pending = true;
|
||||
}
|
||||
if ((old_protection == PAGE_NOACCESS || old_protection == PAGE_READONLY) &&
|
||||
new_protection == PAGE_READWRITE) {
|
||||
if ((old_protection == NO_ACCESS_PROTECTION ||
|
||||
old_protection == READ_ONLY_PROTECTION) &&
|
||||
new_protection == READ_WRITE_PROTECTION) {
|
||||
page.late_write_pending = true;
|
||||
}
|
||||
}
|
||||
@@ -297,8 +308,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 +373,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");
|
||||
}
|
||||
@@ -401,11 +408,11 @@ void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
switch (new_protection) {
|
||||
case PAGE_NOACCESS:
|
||||
case NO_ACCESS_PROTECTION:
|
||||
page.late_read_pending = false;
|
||||
page.late_write_pending = false;
|
||||
break;
|
||||
case PAGE_READONLY: page.late_write_pending = false; break;
|
||||
case READ_ONLY_PROTECTION: page.late_write_pending = false; break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
@@ -500,6 +507,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");
|
||||
@@ -539,7 +577,7 @@ void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (!page.resolving || page.backing_writer != writer) {
|
||||
FailFast("backing write ended without matching owner and resolving state");
|
||||
}
|
||||
const auto old_protection = PAGE_NOACCESS;
|
||||
const auto old_protection = NO_ACCESS_PROTECTION;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(address, new_protection, old_protection, false);
|
||||
|
||||
@@ -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
@@ -6,42 +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 {
|
||||
VulkanBuffer& buffer;
|
||||
uint64_t offset;
|
||||
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:
|
||||
@@ -50,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);
|
||||
|
||||
@@ -60,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]] VulkanBuffer& ObtainNullBuffer(CommandBuffer& command);
|
||||
[[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_
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
#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 = ®isters;
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
|
||||
if (!Active()) {
|
||||
for (auto& buffer: m_buffers) {
|
||||
buffer = std::make_unique<RenderCommandBuffer>(*this);
|
||||
}
|
||||
m_current = 0;
|
||||
}
|
||||
|
||||
BindCurrent();
|
||||
if (!m_recording) {
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScheduler::BeginRendering(const RenderState& state) {
|
||||
Current().BeginRendering(state);
|
||||
}
|
||||
|
||||
void CommandScheduler::EndRendering() {
|
||||
if (Active() && m_recording) {
|
||||
Current().EndRendering();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandScheduler::Flush() {
|
||||
SubmitInfo submit;
|
||||
Flush(submit);
|
||||
}
|
||||
|
||||
void CommandScheduler::Flush(SubmitInfo& submit) {
|
||||
SubmitCurrent(submit);
|
||||
BeginNext();
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::FlushAndGetSubmitted() {
|
||||
SubmitInfo submit;
|
||||
auto& submitted = SubmitCurrent(submit);
|
||||
BeginNext();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CommandScheduler::Finish() {
|
||||
CheckActive();
|
||||
const auto tick = CurrentTick();
|
||||
if (m_recording) {
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
|
||||
RenderCommandBuffer& CommandScheduler::Current() const {
|
||||
CheckActive();
|
||||
EXIT_IF(m_buffers[m_current] == nullptr);
|
||||
return *m_buffers[m_current];
|
||||
}
|
||||
|
||||
void CommandScheduler::BindCurrent() const {
|
||||
EXIT_IF(m_registers == nullptr || m_user_config == nullptr || m_shaders == nullptr);
|
||||
Current().Bind(*m_registers, *m_user_config, *m_shaders);
|
||||
}
|
||||
|
||||
CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
|
||||
CheckActive();
|
||||
EXIT_IF(!m_recording);
|
||||
auto& submitted = Current();
|
||||
submitted.End();
|
||||
const auto signal_tick = m_master.NextTick();
|
||||
submit.AddSignal(m_master.Handle(), signal_tick);
|
||||
submitted.Execute(submit);
|
||||
m_recording = false;
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CommandScheduler::BeginNext() {
|
||||
EXIT_IF(m_recording);
|
||||
m_current = (m_current + 1) % BufferCount;
|
||||
Current().WaitForFenceAndReset();
|
||||
PopPendingOperations();
|
||||
BindCurrent();
|
||||
Current().Begin();
|
||||
m_recording = true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,127 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
|
||||
#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(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]] 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:
|
||||
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;
|
||||
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
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_COMMANDSCHEDULER_H_
|
||||
@@ -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,87 +42,12 @@ void FenceResourceRetainer::ReleaseAfterFence() noexcept {
|
||||
m_resources.clear();
|
||||
}
|
||||
|
||||
void GraphicsRenderInit(GraphicContext& graphics) {
|
||||
g_render_ctx = new RenderContext(graphics);
|
||||
}
|
||||
CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
|
||||
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
|
||||
m_slot(scheduler.AllocateCommandBuffer()) {}
|
||||
|
||||
void GraphicsRenderReleaseThreadCommandPool() {
|
||||
g_command_pool.Destroy();
|
||||
}
|
||||
|
||||
CommandBuffer::CommandBuffer()
|
||||
: m_graphics(GetRenderContext().GetGraphics()), m_slot(g_command_pool.Allocate()),
|
||||
m_host_stream(m_graphics) {}
|
||||
|
||||
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;
|
||||
CommandBuffer::~CommandBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
bool CommandBuffer::IsInvalid() const {
|
||||
@@ -190,18 +69,19 @@ void CommandBuffer::Release() {
|
||||
|
||||
WaitForFence();
|
||||
|
||||
m_host_stream.Release();
|
||||
|
||||
m_slot->busy = false;
|
||||
ResetNativeCommandBuffer(m_slot->buffer);
|
||||
m_slot->Reset();
|
||||
ReleaseResourcesAfterFence();
|
||||
m_slot = nullptr;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsInvalid());
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteAfterFence(VulkanBuffer& buffer) {
|
||||
m_delete_after_fence.push_back(&buffer);
|
||||
void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
|
||||
if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
|
||||
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
|
||||
}
|
||||
m_retired_buffers.push_back(std::move(buffer));
|
||||
}
|
||||
|
||||
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
|
||||
@@ -217,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 {};
|
||||
@@ -237,6 +118,7 @@ void CommandBuffer::Begin() const {
|
||||
}
|
||||
|
||||
void CommandBuffer::End() const {
|
||||
EndRendering();
|
||||
auto buffer = Handle();
|
||||
|
||||
auto result = buffer.end();
|
||||
@@ -255,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);
|
||||
@@ -298,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);
|
||||
}
|
||||
|
||||
@@ -333,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
|
||||
@@ -357,13 +234,10 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
|
||||
m_execute = false;
|
||||
m_fence_waited = false;
|
||||
if (reset_recording) {
|
||||
ResetNativeCommandBuffer(m_slot->buffer);
|
||||
m_recording_generation++;
|
||||
Common::LockGuard lock(*m_slot->pool_mutex);
|
||||
m_slot->Reset();
|
||||
}
|
||||
}
|
||||
if (reset_recording) {
|
||||
m_host_stream.Reset();
|
||||
}
|
||||
if (was_executed) {
|
||||
ReleaseResourcesAfterFence();
|
||||
}
|
||||
@@ -376,144 +250,71 @@ void CommandBuffer::ReleaseResourcesAfterFence() {
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteBuffersAfterFence() {
|
||||
for (auto* buffer: m_delete_after_fence) {
|
||||
GetRenderContext().GetGraphics().DeleteBuffer(*buffer);
|
||||
delete buffer;
|
||||
for (const auto& buffer: m_retired_buffers) {
|
||||
m_graphics.DeleteBuffer(*buffer);
|
||||
}
|
||||
m_delete_after_fence.clear();
|
||||
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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <fmt/format.h>
|
||||
@@ -100,8 +101,6 @@ void sh_print(const char* func, const HW::Shader& /*uc*/) {
|
||||
LOGF("%s\n", func);
|
||||
}
|
||||
|
||||
void sh_check(const HW::Shader& /*uc*/) {}
|
||||
|
||||
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt) {
|
||||
std::vector<std::string> dst;
|
||||
dst.reserve(53);
|
||||
@@ -431,21 +430,17 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
LOGF("%s\n", func);
|
||||
|
||||
LOGF("\t z_info.format = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.tile_mode_index = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.num_samples = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.tile_surface_enable = %s\n"
|
||||
"\t z_info.texture_compatibility = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.htile_acceleration = %s\n"
|
||||
"\t z_info.expclear_enabled = %s\n"
|
||||
"\t z_info.zrange_precision = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.embedded_sample_locations = %s\n"
|
||||
"\t z_info.z_compare_base = 0x%08" PRIx32 "\n"
|
||||
"\t z_info.partially_resident = %s\n"
|
||||
"\t z_info.num_mip_levels = 0x%02" PRIx8 "\n"
|
||||
"\t z_info.plane_compression = 0x%02" PRIx8 "\n"
|
||||
"\t z_info.max_mip_level = 0x%02" PRIx8 "\n"
|
||||
"\t stencil_info.format = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.tile_stencil_disable = %s\n"
|
||||
"\t stencil_info.texture_compatibility = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.htile_stencil_disabled = %s\n"
|
||||
"\t stencil_info.expclear_enabled = %s\n"
|
||||
"\t stencil_info.tile_mode_index = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.tile_split = 0x%08" PRIx32 "\n"
|
||||
"\t stencil_info.texture_compatible_stencil = %s\n"
|
||||
"\t stencil_info.partially_resident = %s\n"
|
||||
"\t depth_info.addr5_swizzle_mask = 0x%08" PRIx32 "\n"
|
||||
"\t depth_info.array_mode = 0x%08" PRIx32 "\n"
|
||||
@@ -478,15 +473,15 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
"\t height = 0x%08" PRIx32 "\n"
|
||||
"\t size.x_max = 0x%04" PRIx16 "\n"
|
||||
"\t size.y_max = 0x%04" PRIx16 "\n",
|
||||
z.z_info.format, z.z_info.tile_mode_index, z.z_info.num_samples,
|
||||
z.z_info.tile_surface_enable ? "true" : "false",
|
||||
z.z_info.expclear_enabled ? "true" : "false", z.z_info.zrange_precision,
|
||||
z.z_info.embedded_sample_locations ? "true" : "false",
|
||||
z.z_info.partially_resident ? "true" : "false", z.z_info.num_mip_levels,
|
||||
z.z_info.plane_compression, z.stencil_info.format,
|
||||
z.stencil_info.tile_stencil_disable ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false", z.stencil_info.tile_mode_index,
|
||||
z.stencil_info.tile_split, z.stencil_info.texture_compatible_stencil ? "true" : "false",
|
||||
z.z_info.format, z.z_info.num_samples,
|
||||
Prospero::GpuEnumValue(z.z_info.texture_compatibility),
|
||||
z.z_info.htile_acceleration ? "true" : "false",
|
||||
z.z_info.expclear_enabled ? "true" : "false",
|
||||
Prospero::GpuEnumValue(z.z_info.z_compare_base),
|
||||
z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
|
||||
z.stencil_info.format, Prospero::GpuEnumValue(z.stencil_info.texture_compatibility),
|
||||
z.stencil_info.htile_stencil_disabled ? "true" : "false",
|
||||
z.stencil_info.expclear_enabled ? "true" : "false",
|
||||
z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask,
|
||||
z.depth_info.array_mode, z.depth_info.pipe_config, z.depth_info.bank_width,
|
||||
z.depth_info.bank_height, z.depth_info.macro_tile_aspect, z.depth_info.num_banks,
|
||||
@@ -503,36 +498,17 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
|
||||
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
|
||||
if (z.z_info.format == 0) {
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.tile_mode_index != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_samples != 0);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.htile_acceleration != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
|
||||
if (z.z_info.zrange_precision != 0) {
|
||||
LOGF("Warning: zrange_precision != 0\n");
|
||||
// z.z_info.zrange_precision = 0;
|
||||
}
|
||||
if (z.z_info.embedded_sample_locations) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring embedded sample locations\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_mip_levels != 0);
|
||||
if (z.z_info.plane_compression != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 plane_compression=0x%02" PRIx8 "\n",
|
||||
z.z_info.plane_compression);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0x00000001 && z.z_info.format != 0x00000003);
|
||||
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_mode_index != 0x00000002);
|
||||
if (z.z_info.num_samples != 0x00000000) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
@@ -541,56 +517,17 @@ static void ZCheck(const HW::DepthRenderTarget& z) {
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
// EXIT_NOT_IMPLEMENTED(z.z_info.tile_surface_enable != true);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.expclear_enabled != false);
|
||||
if (z.z_info.zrange_precision != 0x00000001) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring zrange_precision=0x%08" PRIx32 "\n",
|
||||
z.z_info.zrange_precision);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
if (z.z_info.embedded_sample_locations) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring embedded sample locations\n");
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.partially_resident != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.num_mip_levels != 0);
|
||||
if (z.z_info.plane_compression != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 plane_compression=0x%02" PRIx8 "\n",
|
||||
z.z_info.plane_compression);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
|
||||
}
|
||||
|
||||
if (z.stencil_info.format == 0) {
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_stencil_disable != false);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_mode_index != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_split != 0);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.texture_compatible_stencil != true);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
|
||||
} else {
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
|
||||
if (z.stencil_info.tile_stencil_disable != true) {
|
||||
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 16) {
|
||||
LOGF("DepthTarget: temporary: ignoring PS5 HTILE stencil acceleration\n");
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.format != 0x00000001);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.expclear_enabled != false);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_mode_index != 0x00000002);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.tile_split != 0x00000002);
|
||||
// EXIT_NOT_IMPLEMENTED(z.stencil_info.texture_compatible_stencil != true);
|
||||
EXIT_NOT_IMPLEMENTED(z.stencil_info.partially_resident != false);
|
||||
}
|
||||
|
||||
@@ -1135,10 +1072,10 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
|
||||
int max_right = static_cast<int>(width);
|
||||
int max_bottom = static_cast<int>(height);
|
||||
|
||||
r.left = (r.left < 0 ? 0 : (r.left > max_right ? max_right : r.left));
|
||||
r.right = (r.right < 0 ? 0 : (r.right > max_right ? max_right : r.right));
|
||||
r.top = (r.top < 0 ? 0 : (r.top > max_bottom ? max_bottom : r.top));
|
||||
r.bottom = (r.bottom < 0 ? 0 : (r.bottom > max_bottom ? max_bottom : r.bottom));
|
||||
r.left = std::clamp(r.left, 0, max_right);
|
||||
r.right = std::clamp(r.right, 0, max_right);
|
||||
r.top = std::clamp(r.top, 0, max_bottom);
|
||||
r.bottom = std::clamp(r.bottom, 0, max_bottom);
|
||||
|
||||
if (!ScissorRectValid(r)) {
|
||||
r.right = r.left;
|
||||
|
||||
@@ -33,7 +33,6 @@ bool graphics_debug_dump_enabled();
|
||||
void uc_print(const char* func, const HW::UserConfig& uc);
|
||||
void uc_check(const HW::UserConfig& uc);
|
||||
void sh_print(const char* func, const HW::Shader& uc);
|
||||
void sh_check(const HW::Shader& uc);
|
||||
std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt);
|
||||
bool RenderIsColorTileModeLinear(uint32_t tile_mode);
|
||||
void hw_print(const RenderCommandBuffer& buffer);
|
||||
|
||||
@@ -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();
|
||||
@@ -103,31 +102,34 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
if (!z.z_info.HasValidTextureCompatibility() ||
|
||||
!z.stencil_info.HasValidTextureCompatibility()) {
|
||||
DepthFatal("invalid PS5 depth texture-compatibility encoding");
|
||||
}
|
||||
const bool attachment_unbound =
|
||||
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
|
||||
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
|
||||
z.z_info.tile_mode_index == 0 && z.z_info.num_samples == 0 &&
|
||||
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.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.texture_compatible_stencil &&
|
||||
!z.stencil_info.partially_resident && z.depth_view.slice_start == 0 &&
|
||||
z.depth_view.slice_max == 0 && z.depth_view.current_mip_level == 0 &&
|
||||
!z.depth_view.depth_write_disable && !z.depth_view.stencil_write_disable &&
|
||||
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 && 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 &&
|
||||
z.z_read_base_addr == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.z_info.num_samples == 0 &&
|
||||
z.z_info.texture_compatibility == Prospero::TextureCompatiblePlaneCompression::kDisable &&
|
||||
!z.z_info.expclear_enabled && !z.z_info.partially_resident && z.z_info.max_mip_level == 0 &&
|
||||
z.stencil_info.texture_compatibility == Prospero::TextureCompatibleStencil::kDisable &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.partially_resident &&
|
||||
z.depth_view.slice_start == 0 && z.depth_view.slice_max == 0 &&
|
||||
z.depth_view.current_mip_level == 0 && !z.depth_view.depth_write_disable &&
|
||||
!z.depth_view.stencil_write_disable && 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 &&
|
||||
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 && z.z_read_base_addr == 0 &&
|
||||
z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 &&
|
||||
// DB_DEPTH_SIZE_XY is independent state and may remain programmed after the attachment
|
||||
// formats and addresses are unbound. A zero encoding is the valid 1x1 value, so its
|
||||
// presence alone must not manufacture a depth attachment.
|
||||
!z.z_info.tile_surface_enable && !z.width_height_valid && !z.pitch_height_valid &&
|
||||
!z.z_info.htile_acceleration && !z.width_height_valid && !z.pitch_height_valid &&
|
||||
z.size.x_max == 0 && z.size.y_max == 0 && z.pitch_div8_minus1 == 0 &&
|
||||
z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 && z.width == 0 && z.height == 0;
|
||||
if (attachment_unbound) {
|
||||
@@ -139,13 +141,13 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
}
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const bool has_htile = z.z_info.tile_surface_enable;
|
||||
const bool has_htile = z.z_info.htile_acceleration;
|
||||
const auto samples = render_sample_count(z.z_info.num_samples);
|
||||
if (samples == 0) {
|
||||
DepthFatal("unsupported depth fragment count: %u", z.z_info.num_samples);
|
||||
}
|
||||
const bool htile_stencil_compat = depth_htile_stencil_acceleration_compatible(
|
||||
has_stencil, has_htile, z.stencil_info.tile_stencil_disable);
|
||||
has_stencil, has_htile, z.stencil_info.htile_stencil_disabled);
|
||||
const auto view = ResolveTargetViewInfo(z.depth_view.slice_start, z.depth_view.slice_max);
|
||||
switch (view.type) {
|
||||
case TargetViewType::Image2D: break;
|
||||
@@ -156,14 +158,10 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
|
||||
z.depth_view.slice_max);
|
||||
}
|
||||
// Prospero defines the compression-disable bits as tile writeback policy. Vulkan attachments
|
||||
// expose the same logical depth/stencil values regardless of the driver's backing compression.
|
||||
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
|
||||
z.z_info.embedded_sample_locations || z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.plane_compression != 0 ||
|
||||
z.z_info.num_mip_levels != 0 || z.z_info.tile_mode_index != 0 ||
|
||||
z.z_info.zrange_precision > 1 || z.depth_view.current_mip_level != 0 ||
|
||||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
|
||||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
|
||||
z.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 ||
|
||||
@@ -177,27 +175,16 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
DepthFatal("unsupported depth register state");
|
||||
}
|
||||
if (has_stencil) {
|
||||
// Prospero defines Hi-Stencil as HTile-backed acceleration of the logical stencil plane.
|
||||
// Keep the plane native in Vulkan while tracking HTile separately.
|
||||
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil ||
|
||||
z.stencil_read_base_addr == 0 ||
|
||||
!htile_stencil_compat || z.stencil_read_base_addr == 0 ||
|
||||
z.stencil_write_base_addr != z.stencil_read_base_addr ||
|
||||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
|
||||
DepthFatal("unsupported stencil attachment state");
|
||||
}
|
||||
if (!z.stencil_info.tile_stencil_disable) {
|
||||
static std::atomic_bool logged = false;
|
||||
if (!logged.load(std::memory_order_relaxed) &&
|
||||
!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("DepthTarget: compatibility: using native stencil with PS5 HTILE "
|
||||
"acceleration\n");
|
||||
}
|
||||
}
|
||||
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil) {
|
||||
!htile_stencil_compat ||
|
||||
z.stencil_info.texture_compatibility !=
|
||||
Prospero::TextureCompatibleStencil::kDisable) {
|
||||
DepthFatal("stencil state without an active stencil attachment");
|
||||
}
|
||||
if (has_htile) {
|
||||
@@ -272,28 +259,19 @@ void ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandBuffer& buffer, R
|
||||
(has_htile && htile_backing_size > TRACKER_ADDRESS_SIZE - z.htile_data_base_addr)) {
|
||||
DepthFatal("layered depth backing range is invalid");
|
||||
}
|
||||
r.htile = has_htile;
|
||||
r.width = width;
|
||||
r.height = height;
|
||||
r.samples = samples;
|
||||
r.depth_buffer_size = depth_backing_size;
|
||||
r.depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
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.htile = has_htile;
|
||||
r.width = width;
|
||||
r.height = height;
|
||||
r.samples = samples;
|
||||
r.depth_buffer_size = depth_backing_size;
|
||||
r.depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r.stencil_buffer_size = has_stencil ? stencil_backing_size : 0;
|
||||
r.stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r.htile_buffer_size = has_htile ? htile_backing_size : 0;
|
||||
r.htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
r.depth_clear_enable = rc.depth_clear_enable;
|
||||
r.depth_meta_clear_enable = 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 +321,93 @@ 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 =
|
||||
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");
|
||||
}
|
||||
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.htile_stencil_disabled;
|
||||
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,14 +12,14 @@
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RenderCommandBuffer;
|
||||
struct DepthStencilVulkanImage;
|
||||
|
||||
inline constexpr bool depth_htile_stencil_acceleration_compatible(bool has_stencil, bool has_htile,
|
||||
bool acceleration_disabled) {
|
||||
return acceleration_disabled || (has_stencil && has_htile);
|
||||
bool htile_stencil_disabled) {
|
||||
return htile_stencil_disabled || (has_stencil && has_htile);
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -23,9 +22,13 @@ vk::ShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
|
||||
|
||||
bool IsSampledImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Sampled1D:
|
||||
case BindingKind::Sampled1DArray:
|
||||
case BindingKind::Sampled2D:
|
||||
case BindingKind::Sampled2DArray:
|
||||
case BindingKind::Sampled3D:
|
||||
case BindingKind::SampledUint1D:
|
||||
case BindingKind::SampledUint1DArray:
|
||||
case BindingKind::SampledUint2D:
|
||||
case BindingKind::SampledUint2DArray:
|
||||
case BindingKind::SampledUint3D: return true;
|
||||
@@ -35,9 +38,13 @@ bool IsSampledImage(BindingKind kind) {
|
||||
|
||||
bool IsStorageImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Storage1D:
|
||||
case BindingKind::Storage1DArray:
|
||||
case BindingKind::Storage2D:
|
||||
case BindingKind::Storage2DArray:
|
||||
case BindingKind::Storage3D:
|
||||
case BindingKind::StorageUint1D:
|
||||
case BindingKind::StorageUint1DArray:
|
||||
case BindingKind::StorageUint2D:
|
||||
case BindingKind::StorageUint2DArray:
|
||||
case BindingKind::StorageUint3D: return true;
|
||||
@@ -76,31 +83,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 +267,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,20 +6,27 @@
|
||||
#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"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
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 {
|
||||
@@ -29,9 +36,10 @@ struct VulkanDescriptorSet {
|
||||
};
|
||||
|
||||
struct BufferView {
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
std::shared_ptr<void> owner;
|
||||
vk::Buffer buffer = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
vk::DeviceSize range = VK_WHOLE_SIZE;
|
||||
};
|
||||
|
||||
class DescriptorCache {
|
||||
@@ -39,18 +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;
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -63,10 +63,21 @@ public:
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
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;
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -76,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
|
||||
@@ -95,11 +109,6 @@ private:
|
||||
std::map<std::vector<uint32_t>, vk::DescriptorSetLayout> m_descriptor_set_layouts;
|
||||
};
|
||||
|
||||
void BindDescriptors(uint64_t submit_id, CommandBuffer& buffer,
|
||||
vk::PipelineBindPoint pipeline_bind_point, vk::PipelineLayout layout,
|
||||
const ShaderStageRuntime& runtime, vk::ShaderStageFlags vk_stage,
|
||||
DescriptorCache::Stage stage);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,8 +13,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct VulkanImage;
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] T DecodeNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
@@ -35,13 +34,12 @@ 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);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor);
|
||||
const Image& image);
|
||||
[[nodiscard]] bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor,
|
||||
const Image& image);
|
||||
[[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
@@ -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
@@ -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,297 @@ 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:
|
||||
switch (info.type) {
|
||||
case vk::ImageViewType::e3D:
|
||||
return info.base_layer == 0 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2D:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1 && info.layer_count == 1;
|
||||
case vk::ImageViewType::e2DArray:
|
||||
return static_cast<bool>(
|
||||
image.flags & vk::ImageCreateFlagBits::e2DArrayCompatible) &&
|
||||
info.level_count == 1;
|
||||
default: return false;
|
||||
}
|
||||
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 +313,104 @@ 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 slice_view = image.image_type == vk::ImageType::e3D &&
|
||||
(normalized.type == vk::ImageViewType::e2D ||
|
||||
normalized.type == vk::ImageViewType::e2DArray);
|
||||
const bool levels_valid = normalized.level_count != 0 &&
|
||||
normalized.base_level < image.mip_levels &&
|
||||
normalized.level_count <= image.mip_levels - normalized.base_level;
|
||||
const auto view_layers = slice_view && levels_valid
|
||||
? std::max(image.extent.depth >> normalized.base_level, 1u)
|
||||
: image.layers;
|
||||
const bool ranges_valid = levels_valid &&
|
||||
normalized.layer_count != 0 && normalized.base_layer < view_layers &&
|
||||
normalized.layer_count <= view_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
|
||||
|
||||
@@ -2,91 +2,19 @@
|
||||
#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));
|
||||
}
|
||||
namespace ImageViewOps {
|
||||
|
||||
[[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]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
|
||||
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
|
||||
} // namespace ImageViewOps
|
||||
|
||||
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
|
||||
vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format BgraToRgbaSampledViewFormat(vk::Format image_format) noexcept {
|
||||
switch (image_format) {
|
||||
case vk::Format::eB8G8R8A8Unorm: return vk::Format::eR8G8B8A8Unorm;
|
||||
case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Srgb;
|
||||
case vk::Format::eA2R10G10B10UnormPack32: return vk::Format::eA2B10G10R10UnormPack32;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsBgraToRgbaSampledView(vk::Format image_format,
|
||||
vk::Format view_format) noexcept {
|
||||
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;
|
||||
}
|
||||
case vk::Format::eA2R10G10B10UnormPack32:
|
||||
return view_format == vk::Format::eA2B10G10R10UnormPack32;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format BgraSrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
return image_format == vk::Format::eB8G8R8A8Srgb ? vk::Format::eR8G8B8A8Unorm
|
||||
: vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
return image_format == vk::Format::eR8G8B8A8Srgb ? vk::Format::eR8G8B8A8Unorm
|
||||
: BgraSrgbStorageViewFormat(image_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsBgraSrgbStorageView(vk::Format image_format, vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
return view_format == BgraSrgbStorageViewFormat(image_format) && swizzle == DstSel(6, 5, 4, 7);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsValidSampledColorSwizzle(uint32_t swizzle) noexcept {
|
||||
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
|
||||
if ((swizzle & ~0xfffu) != 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -104,21 +32,25 @@ namespace Libs::Graphics {
|
||||
return true;
|
||||
}
|
||||
|
||||
[[noreturn]] inline void UnsupportedColorView(const char* usage, vk::Format image_format,
|
||||
vk::Format view_format, uint32_t swizzle) noexcept {
|
||||
EXIT("unsupported %s color image view: image_format=%d view_format=%d swizzle=0x%03x\n", usage,
|
||||
static_cast<int>(image_format), static_cast<int>(view_format), swizzle);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline vk::Format SrgbStorageViewFormat(vk::Format image_format) noexcept {
|
||||
switch (image_format) {
|
||||
case vk::Format::eR8G8B8A8Srgb:
|
||||
case vk::Format::eB8G8R8A8Srgb: return vk::Format::eR8G8B8A8Unorm;
|
||||
default: return vk::Format::eUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool IsSupportedSampledColorView(vk::Format image_format,
|
||||
vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
if (!IsValidSampledColorSwizzle(swizzle)) {
|
||||
return false;
|
||||
}
|
||||
if (image_format == view_format || IsRgba8SrgbReinterpretation(image_format, view_format)) {
|
||||
return true;
|
||||
}
|
||||
if ((IsRgba16UintFloatReinterpretation(image_format, view_format) ||
|
||||
IsRgba8UnormUintReinterpretation(image_format, view_format)) &&
|
||||
swizzle == DstSel(4, 5, 6, 7)) {
|
||||
return true;
|
||||
}
|
||||
return IsBgraToRgbaSampledView(image_format, view_format) && swizzle == DstSel(6, 5, 4, 7);
|
||||
return IsValidImageSwizzle(swizzle) &&
|
||||
ImageViewOps::FormatsCompatible(image_format, view_format);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline uint32_t
|
||||
@@ -169,33 +101,24 @@ 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));
|
||||
if ((image_format != view_format &&
|
||||
!IsBgraSrgbStorageView(image_format, view_format, swizzle)) ||
|
||||
!swizzle_ok) {
|
||||
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
|
||||
uint32_t swizzle) noexcept {
|
||||
const auto srgb_view = SrgbStorageViewFormat(image_format);
|
||||
const bool srgb_storage_view =
|
||||
srgb_view != vk::Format::eUndefined && view_format == srgb_view;
|
||||
if ((image_format != view_format && !srgb_storage_view) ||
|
||||
!IsValidImageSwizzle(swizzle)) {
|
||||
UnsupportedColorView("storage", image_format, view_format, swizzle);
|
||||
}
|
||||
return VulkanImage::VIEW_STORAGE;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
|
||||
return (resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
|
||||
@@ -213,18 +136,6 @@ 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);
|
||||
|
||||
} // namespace ImageViewOps
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGEVIEW_H_
|
||||
|
||||
@@ -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_
|
||||
@@ -1,13 +1,14 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_MULTILEVELPAGETABLE_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
|
||||
[[nodiscard]] Entry& GetOrCreate(size_t page) {
|
||||
if (!IsValidPage(page)) {
|
||||
throw std::out_of_range("MultiLevelPageTable page is outside the guest address space");
|
||||
EXIT("MultiLevelPageTable page is outside the guest address space");
|
||||
}
|
||||
auto& bucket = m_first_level[FirstLevelIndex(page)];
|
||||
if (bucket == nullptr) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,10 +63,36 @@ 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();
|
||||
~CommandBuffer() { Release(); }
|
||||
explicit CommandBuffer(CommandScheduler& scheduler);
|
||||
~CommandBuffer();
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandBuffer);
|
||||
|
||||
@@ -77,95 +100,144 @@ 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();
|
||||
void DeleteAfterFence(VulkanBuffer& buffer);
|
||||
void RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer);
|
||||
void RetainResourceUntilFence(std::shared_ptr<void> resource);
|
||||
void RecycleDescriptorAfterFence(VulkanDescriptorSet& set);
|
||||
|
||||
[[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; }
|
||||
[[nodiscard]] uint64_t GetRecordingGeneration() const { return m_recording_generation; }
|
||||
|
||||
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();
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
CommandSlot* m_slot = nullptr;
|
||||
bool m_execute = false;
|
||||
bool m_fence_waited = false;
|
||||
uint64_t m_submit_seq = 0;
|
||||
uint64_t m_recording_generation = 0;
|
||||
uint32_t m_debug_op = 0;
|
||||
uint64_t m_debug_submit_id = 0;
|
||||
uint32_t m_debug_arg0 = 0;
|
||||
uint32_t m_debug_arg1 = 0;
|
||||
uint32_t m_debug_arg2 = 0;
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
std::vector<VulkanBuffer*> m_delete_after_fence;
|
||||
FenceResourceRetainer m_fence_resources;
|
||||
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
|
||||
HostStreamBuffer m_host_stream;
|
||||
RenderContext& m_context;
|
||||
CommandScheduler& m_scheduler;
|
||||
GraphicContext& m_graphics;
|
||||
CommandSlot* m_slot = nullptr;
|
||||
bool m_execute = false;
|
||||
bool m_fence_waited = false;
|
||||
uint64_t m_submit_seq = 0;
|
||||
uint32_t m_debug_op = 0;
|
||||
uint64_t m_debug_submit_id = 0;
|
||||
uint32_t m_debug_arg0 = 0;
|
||||
uint32_t m_debug_arg1 = 0;
|
||||
uint32_t m_debug_arg2 = 0;
|
||||
uint32_t m_debug_arg3 = 0;
|
||||
uint64_t m_debug_arg4 = 0;
|
||||
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
|
||||
FenceResourceRetainer m_fence_resources;
|
||||
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
|
||||
mutable RenderState m_render_state;
|
||||
mutable bool m_rendering = false;
|
||||
};
|
||||
|
||||
class RenderCommandBuffer final: public CommandBuffer {
|
||||
public:
|
||||
RenderCommandBuffer(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders)
|
||||
: m_registers(registers), m_user_config(user_config), m_shaders(shaders) {}
|
||||
explicit RenderCommandBuffer(CommandScheduler& scheduler): CommandBuffer(scheduler) {}
|
||||
|
||||
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return m_registers; }
|
||||
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return m_user_config; }
|
||||
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return m_shaders; }
|
||||
void Bind(HW::Context& registers, HW::UserConfig& user_config, HW::Shader& shaders) noexcept {
|
||||
m_registers = ®isters;
|
||||
m_user_config = &user_config;
|
||||
m_shaders = &shaders;
|
||||
}
|
||||
|
||||
[[nodiscard]] HW::Context& GetRegisters() const noexcept { return *m_registers; }
|
||||
[[nodiscard]] HW::UserConfig& GetUserConfig() const noexcept { return *m_user_config; }
|
||||
[[nodiscard]] HW::Shader& GetShaders() const noexcept { return *m_shaders; }
|
||||
|
||||
private:
|
||||
HW::Context& m_registers;
|
||||
HW::UserConfig& m_user_config;
|
||||
HW::Shader& m_shaders;
|
||||
HW::Context* m_registers = nullptr;
|
||||
HW::UserConfig* m_user_config = nullptr;
|
||||
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
|
||||
|
||||
|
||||
@@ -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,38 +315,32 @@ void RenderDispatchDirect(uint64_t submit_id, RenderCommandBuffer& buffer, uint3
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const auto recording_generation = buffer.GetRecordingGeneration();
|
||||
auto vk_buffer = buffer.Handle();
|
||||
auto& pipeline = GetRenderContext().GetPipelineCache().CreateComputePipeline(
|
||||
input_info, sh_ctx.GetCs(), cs_shader);
|
||||
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);
|
||||
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
auto vk_buffer = buffer.Handle();
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
|
||||
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
|
||||
|
||||
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eCompute,
|
||||
pipeline.pipeline_layout, input_info.stage,
|
||||
vk::ShaderStageFlagBits::eCompute, DescriptorCache::Stage::Compute);
|
||||
if (buffer.GetRecordingGeneration() != recording_generation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
|
||||
|
||||
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);
|
||||
}) ||
|
||||
has_storage_writes;
|
||||
if (has_storage_writes) {
|
||||
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
|
||||
}
|
||||
break;
|
||||
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);
|
||||
}) ||
|
||||
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();
|
||||
|
||||
@@ -6,38 +6,47 @@
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#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);
|
||||
@@ -45,26 +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;
|
||||
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_
|
||||
|
||||
@@ -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,9 +33,12 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -70,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";
|
||||
}
|
||||
@@ -105,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);
|
||||
}
|
||||
|
||||
@@ -181,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;
|
||||
}
|
||||
|
||||
@@ -197,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(
|
||||
@@ -207,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],
|
||||
@@ -224,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++) {
|
||||
@@ -303,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,
|
||||
@@ -325,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);
|
||||
@@ -410,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) {
|
||||
@@ -486,8 +477,7 @@ struct DrawRenderState {
|
||||
RenderColorInfo color_info[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
bool ps_active = true;
|
||||
VulkanFramebuffer* framebuffer = nullptr;
|
||||
vk::CommandBuffer vk_buffer = nullptr;
|
||||
RenderState rendering;
|
||||
ShaderVertexInputInfo vs_input_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
std::span<const uint32_t> vs_shader;
|
||||
@@ -503,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);
|
||||
@@ -510,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;
|
||||
}
|
||||
@@ -558,6 +665,16 @@ struct DrawIndexBufferSource {
|
||||
vk::IndexType type = vk::IndexType::eUint16;
|
||||
};
|
||||
|
||||
struct PreparedIndexBuffer {
|
||||
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) {
|
||||
return (buffer.stride != 0 ? static_cast<uint64_t>(buffer.stride) * buffer.num_records
|
||||
: buffer.num_records);
|
||||
@@ -613,24 +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)) {
|
||||
MarkRenderTargetGpuWritten(state.depth_info);
|
||||
return false;
|
||||
}
|
||||
MarkRenderTargetGpuWritten(state.depth_info);
|
||||
|
||||
if (log_setup_phases) {
|
||||
LogDrawPhase(draw.name, "ResolveRenderColorTarget");
|
||||
}
|
||||
@@ -639,15 +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) {
|
||||
MarkRenderTargetGpuWritten(state.color_info[state.color_count]);
|
||||
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);
|
||||
@@ -655,22 +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);
|
||||
|
||||
state.vk_buffer = buffer.Handle();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -714,56 +810,107 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
|
||||
}
|
||||
}
|
||||
|
||||
static void BindDrawVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
|
||||
const DrawCallInfo& draw, vk::CommandBuffer vk_buffer,
|
||||
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;
|
||||
|
||||
LogDrawPhase(draw.name, "BindVertexBuffers");
|
||||
LogDrawPhase(draw.name, "PrepareVertexBuffers");
|
||||
std::vector<BufferBinding> bindings;
|
||||
bindings.reserve(vs_input_info.buffers_num);
|
||||
for (int i = 0; i < vs_input_info.buffers_num; i++) {
|
||||
const auto& b = vs_input_info.buffers[i];
|
||||
uint64_t addr = b.addr;
|
||||
uint64_t size = VertexBufferDescriptorSize(b);
|
||||
VulkanBuffer* vertices = nullptr;
|
||||
vk::DeviceSize offset = 0;
|
||||
|
||||
const auto& b = vs_input_info.buffers[i];
|
||||
const auto size = VertexBufferDescriptorSize(b);
|
||||
if (size == 0) {
|
||||
vertices = &GetRenderContext().GetBufferCache().ObtainNullBuffer(buffer);
|
||||
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
|
||||
bindings.push_back({owner, owner->Handle(), 0});
|
||||
} else {
|
||||
auto binding = GetRenderContext().GetBufferCache().ObtainBuffer(buffer, addr, size);
|
||||
vertices = &binding.buffer;
|
||||
offset = binding.offset;
|
||||
bindings.push_back(
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(vertices == nullptr);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
vk_buffer.bindVertexBuffers(i, 1, &vertices->buffer, &offset);
|
||||
static void RebindVertexBuffers(RenderCommandBuffer& buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info,
|
||||
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);
|
||||
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 void BindDrawIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const DrawIndexBufferSource& source) {
|
||||
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
|
||||
const DrawIndexBufferSource& source) {
|
||||
PreparedIndexBuffer prepared;
|
||||
if (!source.enabled) {
|
||||
return;
|
||||
return prepared;
|
||||
}
|
||||
EXIT_IF(source.size == 0);
|
||||
|
||||
VulkanBuffer* index_buffer = nullptr;
|
||||
vk::DeviceSize index_offset = 0;
|
||||
prepared.address = source.address;
|
||||
prepared.size = source.size;
|
||||
prepared.type = source.type;
|
||||
if (source.host_data != nullptr) {
|
||||
vk::DeviceSize range = 0;
|
||||
if (!GetRenderContext().GetBufferCache().UploadHostData(
|
||||
buffer, source.host_data, source.size, 16, index_buffer, index_offset, range)) {
|
||||
EXIT("failed to upload host index buffer\n");
|
||||
}
|
||||
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);
|
||||
index_buffer = &binding.buffer;
|
||||
index_offset = binding.offset;
|
||||
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size);
|
||||
prepared.owner = std::move(binding.owner);
|
||||
prepared.buffer = binding.buffer;
|
||||
prepared.offset = binding.offset;
|
||||
}
|
||||
EXIT_IF(index_buffer == nullptr);
|
||||
vk_buffer.bindIndexBuffer(index_buffer->buffer, index_offset, source.type);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
static void RebindIndexBuffer(RenderCommandBuffer& buffer, PreparedIndexBuffer& prepared) {
|
||||
if (prepared.size == 0 || prepared.streamed) {
|
||||
return;
|
||||
}
|
||||
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];
|
||||
if (binding.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(binding.owner);
|
||||
}
|
||||
EXIT_IF(binding.buffer == nullptr);
|
||||
vk_buffer.bindVertexBuffers(slot, 1, &binding.buffer, &binding.offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
|
||||
const PreparedIndexBuffer& prepared) {
|
||||
if (prepared.size == 0) {
|
||||
return;
|
||||
}
|
||||
if (prepared.owner != nullptr) {
|
||||
buffer.RetainResourceUntilFence(prepared.owner);
|
||||
}
|
||||
EXIT_IF(prepared.buffer == nullptr);
|
||||
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
|
||||
}
|
||||
|
||||
static void LogDrawStateIfNeeded(const RenderCommandBuffer& buffer, const DrawCallInfo& draw,
|
||||
@@ -784,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);
|
||||
}
|
||||
@@ -858,102 +1005,94 @@ 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();
|
||||
|
||||
for (;;) {
|
||||
const auto recording_generation = buffer.GetRecordingGeneration();
|
||||
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, "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 (set_bind_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
|
||||
}
|
||||
state.vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||
const auto dynamic_params = BuildGraphicsDynamicParams(buffer, state.color_info,
|
||||
state.color_count, state.depth_info);
|
||||
SetDynamicParams(buffer, state.vk_buffer, dynamic_params);
|
||||
if (log_pipeline_phase) {
|
||||
LogDrawPhase(draw.name, "CreatePipeline");
|
||||
}
|
||||
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);
|
||||
|
||||
// EXIT_NOT_IMPLEMENTED(vs_input_info.buffers_num > 1);
|
||||
BindDrawVertexBuffers(submit_id, buffer, draw, state.vk_buffer, state.vs_input_info);
|
||||
|
||||
LogDrawPhase(draw.name, "BindDescriptorsVS");
|
||||
// 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.
|
||||
auto vk_buffer = buffer.Handle();
|
||||
if (set_bind_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x100u);
|
||||
}
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
|
||||
}
|
||||
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings);
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
|
||||
bindings.vertex);
|
||||
if (bindings.pixel.has_value()) {
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics,
|
||||
pipeline.pipeline_layout, state.vs_input_info.stage,
|
||||
vk::ShaderStageFlagBits::eVertex, DescriptorCache::Stage::Vertex);
|
||||
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
|
||||
*bindings.pixel);
|
||||
}
|
||||
CommitIndexBuffer(buffer, vk_buffer, index_binding);
|
||||
|
||||
if (state.ps_active) {
|
||||
LogDrawPhase(draw.name, "BindDescriptorsPS");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x300u);
|
||||
}
|
||||
BindDescriptors(submit_id, buffer, vk::PipelineBindPoint::eGraphics,
|
||||
pipeline.pipeline_layout, state.ps_input_info.stage,
|
||||
vk::ShaderStageFlagBits::eFragment, DescriptorCache::Stage::Pixel);
|
||||
}
|
||||
if (buffer.GetRecordingGeneration() != recording_generation) {
|
||||
continue;
|
||||
}
|
||||
// Index data may use this command buffer's host stream. Resolve it only after the other
|
||||
// fault-capable bindings, and rebuild it whenever a fault reset changes the generation.
|
||||
BindDrawIndexBuffer(buffer, state.vk_buffer, index_source);
|
||||
if (buffer.GetRecordingGeneration() != recording_generation) {
|
||||
continue;
|
||||
}
|
||||
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");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
|
||||
}
|
||||
buffer.BeginRenderPass(*state.framebuffer, state.color_info, state.color_count,
|
||||
state.depth_info);
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||
}
|
||||
LogDrawPhase(draw.name, "BeginRendering");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x400u);
|
||||
}
|
||||
m_context.GetCommandScheduler().BeginRendering(state.rendering);
|
||||
vk_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.pipeline);
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||
}
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, state.vs_input_info, draw, emit);
|
||||
|
||||
EmitDrawPrimitives(ucfg, state.vk_buffer, state.vs_input_info, draw, emit);
|
||||
|
||||
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;
|
||||
}
|
||||
if (state.ps_active) {
|
||||
if (HasShaderBufferWrites(state.ps_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
}
|
||||
if (shader_write_stages) {
|
||||
ShaderWriteBarrier(state.vk_buffer, shader_write_stages);
|
||||
}
|
||||
LogDrawPhase(draw.name, "EndRenderPass");
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x700u);
|
||||
}
|
||||
break;
|
||||
if (set_auto_debug) {
|
||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x600u);
|
||||
}
|
||||
vk::PipelineStageFlags shader_write_stages = {};
|
||||
if (HasShaderBufferWrites(state.vs_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eVertexShader;
|
||||
}
|
||||
if (state.ps_active && HasShaderBufferWrites(state.ps_input_info.stage)) {
|
||||
shader_write_stages |= vk::PipelineStageFlagBits::eFragmentShader;
|
||||
}
|
||||
if (shader_write_stages) {
|
||||
m_context.GetCommandScheduler().EndRendering();
|
||||
ShaderWriteBarrier(vk_buffer, shader_write_stages);
|
||||
}
|
||||
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());
|
||||
@@ -964,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;
|
||||
}
|
||||
|
||||
@@ -1001,7 +1140,6 @@ void RenderDrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t i
|
||||
instance_count, render_target_slice_offset, static_cast<uint32_t>(vertex_offset_add),
|
||||
first_instance);
|
||||
}
|
||||
sh_check(sh_ctx);
|
||||
|
||||
uc_check(ucfg);
|
||||
|
||||
@@ -1063,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;
|
||||
}
|
||||
|
||||
@@ -1081,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());
|
||||
@@ -1096,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;
|
||||
}
|
||||
|
||||
@@ -1129,7 +1270,6 @@ void RenderDrawIndexAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32
|
||||
index_count, flags, render_target_slice_offset, instance_count, first_vertex,
|
||||
first_instance);
|
||||
}
|
||||
sh_check(sh_ctx);
|
||||
|
||||
uc_check(ucfg);
|
||||
|
||||
@@ -1145,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;
|
||||
}
|
||||
|
||||
@@ -1154,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 =
|
||||
@@ -1170,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;
|
||||
}
|
||||
|
||||
@@ -1189,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;
|
||||
@@ -1224,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -620,10 +622,13 @@ void CreatePipelineInternal(PipelineCache::GraphicsPipeline& pipeline, vk::Rende
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected =
|
||||
(attr_size == 1 ? DstSel(4, 0, 0)
|
||||
: (attr_size == 2 ? DstSel(4, 5, 0) : DstSel(4, 5, 6)));
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected = DstSel(4, 5, 6);
|
||||
switch (attr_size) {
|
||||
case 1: expected = DstSel(4, 0, 0); break;
|
||||
case 2: expected = DstSel(4, 5, 0); break;
|
||||
default: break;
|
||||
}
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
@@ -820,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);
|
||||
}
|
||||
@@ -899,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;
|
||||
@@ -914,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;
|
||||
@@ -949,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 {};
|
||||
@@ -982,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] = {};
|
||||
@@ -992,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);
|
||||
|
||||
|
||||
@@ -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, ©);
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -3,173 +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]] 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
layout(local_size_x = 64) in;
|
||||
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;
|
||||
@@ -17,8 +17,6 @@ layout(push_constant) uniform Push {
|
||||
uint tail_x;
|
||||
uint tail_y;
|
||||
uint tail;
|
||||
uint first;
|
||||
uint count;
|
||||
} params;
|
||||
|
||||
void copy_element(uint src, uint dst) {
|
||||
@@ -29,24 +27,18 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
if (index >= params.count) {
|
||||
uvec3 p = gl_GlobalInvocationID;
|
||||
if (p.x >= params.width || p.y >= params.height || p.z >= params.depth) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint element = params.first + index;
|
||||
uint plane = params.width * params.height;
|
||||
uvec3 p;
|
||||
p.z = element / plane;
|
||||
element -= p.z * plane;
|
||||
p.y = element / params.width;
|
||||
p.x = element - p.y * params.width;
|
||||
|
||||
uvec3 extent = block_extent();
|
||||
uvec3 swizzle = p;
|
||||
uvec3 block = uvec3(0);
|
||||
|
||||
@@ -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
@@ -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_
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -187,10 +187,6 @@ void RenderDocInit() {
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderDocIsLoaded() {
|
||||
return g_api != nullptr;
|
||||
}
|
||||
|
||||
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) {
|
||||
if (g_api == nullptr) {
|
||||
return;
|
||||
@@ -281,9 +277,6 @@ void RenderDocOnPresent() {
|
||||
#else
|
||||
|
||||
void RenderDocInit() {}
|
||||
bool RenderDocIsLoaded() {
|
||||
return false;
|
||||
}
|
||||
void RenderDocSetActiveWindow(vk::Instance /*instance*/, SDL_Window* /*window*/) {}
|
||||
void RenderDocRequestCapture() {}
|
||||
void RenderDocOnPresent() {}
|
||||
|
||||
@@ -9,7 +9,6 @@ struct SDL_Window;
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void RenderDocInit();
|
||||
bool RenderDocIsLoaded();
|
||||
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window);
|
||||
void RenderDocRequestCapture();
|
||||
void RenderDocOnPresent();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user