From c01b6f839781be08a2e675c172cac6d172a4d1d1 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Sun, 14 Apr 2024 17:09:51 +0300 Subject: [PATCH] video_core: Rewrite vulkan and videoout --- .gitmodules | 15 + CMakeLists.txt | 66 ++- externals/CMakeLists.txt | 28 ++ externals/ext-boost | 1 + externals/glslang | 1 + externals/robin-map | 1 + externals/vma | 1 + externals/vulkan-headers | 1 + src/common/alignment.h | 31 ++ src/common/enum.h | 60 +++ src/common/types.h | 3 + src/core/PS4/GPU/gpu_memory.cpp | 203 ---------- src/core/PS4/GPU/gpu_memory.h | 94 ----- src/core/PS4/GPU/tile_manager.h | 12 - src/core/PS4/GPU/video_out_buffer.cpp | 146 ------- src/core/PS4/GPU/video_out_buffer.h | 41 -- .../HLE/Graphics/Objects/video_out_ctx.cpp | 149 ------- .../PS4/HLE/Graphics/Objects/video_out_ctx.h | 90 ----- src/core/PS4/HLE/Graphics/graphics_ctx.h | 77 ---- src/core/PS4/HLE/Graphics/graphics_render.cpp | 207 ---------- src/core/PS4/HLE/Graphics/graphics_render.h | 81 ---- src/core/PS4/HLE/Graphics/video_out.cpp | 382 ------------------ src/core/libraries/gnmdriver/gnmdriver.cpp | 3 - src/core/libraries/kernel/event_queue.cpp | 39 +- src/core/libraries/kernel/event_queue.h | 15 +- .../libraries/kernel/memory_management.cpp | 23 +- src/core/libraries/kernel/physical_memory.cpp | 17 +- src/core/libraries/kernel/physical_memory.h | 6 +- src/core/libraries/libc/libc.cpp | 6 +- src/core/libraries/libc/libc.h | 6 +- src/core/libraries/libc/libc_cxa.cpp | 4 +- src/core/libraries/libc/libc_cxa.h | 4 +- src/core/libraries/libc/libc_math.cpp | 4 +- src/core/libraries/libc/libc_math.h | 4 +- src/core/libraries/libc/libc_stdio.cpp | 4 +- src/core/libraries/libc/libc_stdio.h | 4 +- src/core/libraries/libc/libc_stdlib.cpp | 4 +- src/core/libraries/libc/libc_stdlib.h | 4 +- src/core/libraries/libc/libc_string.cpp | 4 +- src/core/libraries/libc/libc_string.h | 4 +- src/core/libraries/libc/printf.h | 4 +- src/core/libraries/libc/va_ctx.h | 4 +- src/core/libraries/libs.cpp | 6 +- src/core/libraries/videoout/buffer.h | 74 ++++ src/core/libraries/videoout/driver.cpp | 236 +++++++++++ src/core/libraries/videoout/driver.h | 82 ++++ src/core/libraries/videoout/video_out.cpp | 236 +++++++++++ .../videoout}/video_out.h | 63 +-- src/emulator.cpp | 353 ---------------- src/emulator.h | 92 ----- src/input/controller.cpp | 2 +- src/input/controller.h | 2 +- src/main.cpp | 34 +- src/sdl_window.cpp | 135 +++++++ src/sdl_window.h | 77 ++++ src/video_core/pixel_format.h | 86 ++++ .../renderer_vulkan/renderer_vulkan.cpp | 354 ++++++++++++++++ .../renderer_vulkan/renderer_vulkan.h | 57 +++ src/video_core/renderer_vulkan/vk_common.cpp | 11 + src/video_core/renderer_vulkan/vk_common.h | 15 + .../vk_descriptor_update_queue.cpp | 108 +++++ .../vk_descriptor_update_queue.h | 51 +++ .../renderer_vulkan/vk_instance.cpp | 271 +++++++++++++ src/video_core/renderer_vulkan/vk_instance.h | 228 +++++++++++ .../renderer_vulkan/vk_master_semaphore.cpp | 105 +++++ .../renderer_vulkan/vk_master_semaphore.h | 60 +++ .../renderer_vulkan/vk_platform.cpp | 232 +++++++++++ src/video_core/renderer_vulkan/vk_platform.h | 49 +++ .../renderer_vulkan/vk_resource_pool.cpp | 190 +++++++++ .../renderer_vulkan/vk_resource_pool.h | 93 +++++ .../renderer_vulkan/vk_scheduler.cpp | 181 +++++++++ src/video_core/renderer_vulkan/vk_scheduler.h | 192 +++++++++ .../renderer_vulkan/vk_shader_util.cpp | 230 +++++++++++ .../renderer_vulkan/vk_shader_util.h | 28 ++ .../renderer_vulkan/vk_stream_buffer.cpp | 234 +++++++++++ .../renderer_vulkan/vk_stream_buffer.h | 86 ++++ .../renderer_vulkan/vk_swapchain.cpp | 225 +++++++++++ src/video_core/renderer_vulkan/vk_swapchain.h | 113 ++++++ src/video_core/texture_cache/image.cpp | 151 +++++++ src/video_core/texture_cache/image.h | 116 ++++++ src/video_core/texture_cache/image_view.cpp | 61 +++ src/video_core/texture_cache/image_view.h | 58 +++ src/video_core/texture_cache/slot_vector.h | 176 ++++++++ .../texture_cache/texture_cache.cpp | 210 ++++++++++ src/video_core/texture_cache/texture_cache.h | 120 ++++++ .../texture_cache}/tile_manager.cpp | 30 +- src/video_core/texture_cache/tile_manager.h | 13 + src/video_core/texture_cache/types.h | 86 ++++ src/vulkan_util.cpp | 63 +-- 89 files changed, 5378 insertions(+), 2150 deletions(-) create mode 160000 externals/ext-boost create mode 160000 externals/glslang create mode 160000 externals/robin-map create mode 160000 externals/vma create mode 160000 externals/vulkan-headers create mode 100644 src/common/alignment.h create mode 100644 src/common/enum.h delete mode 100644 src/core/PS4/GPU/gpu_memory.cpp delete mode 100644 src/core/PS4/GPU/gpu_memory.h delete mode 100644 src/core/PS4/GPU/tile_manager.h delete mode 100644 src/core/PS4/GPU/video_out_buffer.cpp delete mode 100644 src/core/PS4/GPU/video_out_buffer.h delete mode 100644 src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp delete mode 100644 src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h delete mode 100644 src/core/PS4/HLE/Graphics/graphics_ctx.h delete mode 100644 src/core/PS4/HLE/Graphics/graphics_render.cpp delete mode 100644 src/core/PS4/HLE/Graphics/graphics_render.h delete mode 100644 src/core/PS4/HLE/Graphics/video_out.cpp create mode 100644 src/core/libraries/videoout/buffer.h create mode 100644 src/core/libraries/videoout/driver.cpp create mode 100644 src/core/libraries/videoout/driver.h create mode 100644 src/core/libraries/videoout/video_out.cpp rename src/core/{PS4/HLE/Graphics => libraries/videoout}/video_out.h (67%) delete mode 100644 src/emulator.cpp delete mode 100644 src/emulator.h create mode 100644 src/sdl_window.cpp create mode 100644 src/sdl_window.h create mode 100644 src/video_core/pixel_format.h create mode 100644 src/video_core/renderer_vulkan/renderer_vulkan.cpp create mode 100644 src/video_core/renderer_vulkan/renderer_vulkan.h create mode 100644 src/video_core/renderer_vulkan/vk_common.cpp create mode 100644 src/video_core/renderer_vulkan/vk_common.h create mode 100644 src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp create mode 100644 src/video_core/renderer_vulkan/vk_descriptor_update_queue.h create mode 100644 src/video_core/renderer_vulkan/vk_instance.cpp create mode 100644 src/video_core/renderer_vulkan/vk_instance.h create mode 100644 src/video_core/renderer_vulkan/vk_master_semaphore.cpp create mode 100644 src/video_core/renderer_vulkan/vk_master_semaphore.h create mode 100644 src/video_core/renderer_vulkan/vk_platform.cpp create mode 100644 src/video_core/renderer_vulkan/vk_platform.h create mode 100644 src/video_core/renderer_vulkan/vk_resource_pool.cpp create mode 100644 src/video_core/renderer_vulkan/vk_resource_pool.h create mode 100644 src/video_core/renderer_vulkan/vk_scheduler.cpp create mode 100644 src/video_core/renderer_vulkan/vk_scheduler.h create mode 100644 src/video_core/renderer_vulkan/vk_shader_util.cpp create mode 100644 src/video_core/renderer_vulkan/vk_shader_util.h create mode 100644 src/video_core/renderer_vulkan/vk_stream_buffer.cpp create mode 100644 src/video_core/renderer_vulkan/vk_stream_buffer.h create mode 100644 src/video_core/renderer_vulkan/vk_swapchain.cpp create mode 100644 src/video_core/renderer_vulkan/vk_swapchain.h create mode 100644 src/video_core/texture_cache/image.cpp create mode 100644 src/video_core/texture_cache/image.h create mode 100644 src/video_core/texture_cache/image_view.cpp create mode 100644 src/video_core/texture_cache/image_view.h create mode 100644 src/video_core/texture_cache/slot_vector.h create mode 100644 src/video_core/texture_cache/texture_cache.cpp create mode 100644 src/video_core/texture_cache/texture_cache.h rename src/{core/PS4/GPU => video_core/texture_cache}/tile_manager.cpp (87%) create mode 100644 src/video_core/texture_cache/tile_manager.h create mode 100644 src/video_core/texture_cache/types.h diff --git a/.gitmodules b/.gitmodules index 51f34f95..6f104ff0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -46,3 +46,18 @@ [submodule "externals/fmt"] path = externals/fmt url = https://github.com/shadps4-emu/ext-fmt.git +[submodule "externals/vulkan-headers"] + path = externals/vulkan-headers + url = https://github.com/KhronosGroup/Vulkan-Headers +[submodule "externals/vma"] + path = externals/vma + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator +[submodule "externals/glslang"] + path = externals/glslang + url = https://github.com/KhronosGroup/glslang +[submodule "externals/robin-map"] + path = externals/robin-map + url = https://github.com/Tessil/robin-map +[submodule "externals/ext-boost"] + path = externals/ext-boost + url = https://github.com/raphaelthegreat/ext-boost diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a2e246b..9ee7198a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,13 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/system/userservice.h ) +set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h + src/core/libraries/videoout/driver.cpp + src/core/libraries/videoout/driver.h + src/core/libraries/videoout/video_out.cpp + src/core/libraries/videoout/video_out.h +) + set(LIBC_SOURCES src/core/libraries/libc/libc.cpp src/core/libraries/libc/libc.h src/core/libraries/libc/printf.h @@ -185,6 +192,7 @@ set(COMMON src/common/logging/backend.cpp src/common/logging/text_formatter.cpp src/common/logging/text_formatter.h src/common/logging/types.h + src/common/alignment.h src/common/assert.cpp src/common/assert.h src/common/bounded_threadsafe_queue.h @@ -197,6 +205,7 @@ set(COMMON src/common/logging/backend.cpp src/common/discord.cpp src/common/discord.h src/common/endian.h + src/common/enum.h src/common/io_file.cpp src/common/io_file.h src/common/error.cpp @@ -249,6 +258,7 @@ set(CORE src/core/aerolib/stubs.cpp ${SYSTEM_LIBS} ${LIBC_SOURCES} ${PAD_LIB} + ${VIDEOOUT_LIB} src/core/linker.cpp src/core/linker.h src/core/tls.cpp @@ -257,21 +267,39 @@ set(CORE src/core/aerolib/stubs.cpp src/core/virtual_memory.h ) -set(VIDEO_CORE src/core/PS4/HLE/Graphics/video_out.cpp - src/core/PS4/HLE/Graphics/video_out.h - src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp - src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h - src/core/PS4/HLE/Graphics/graphics_ctx.h - src/core/PS4/GPU/gpu_memory.cpp - src/core/PS4/GPU/gpu_memory.h - src/core/PS4/GPU/video_out_buffer.cpp - src/core/PS4/GPU/video_out_buffer.h - src/core/PS4/HLE/Graphics/graphics_render.cpp - src/core/PS4/HLE/Graphics/graphics_render.h - src/core/PS4/GPU/tile_manager.cpp - src/core/PS4/GPU/tile_manager.h - src/vulkan_util.cpp - src/vulkan_util.h +set(VIDEO_CORE src/video_core/pixel_format.h + src/video_core/renderer_vulkan/renderer_vulkan.cpp + src/video_core/renderer_vulkan/renderer_vulkan.h + src/video_core/renderer_vulkan/vk_common.cpp + src/video_core/renderer_vulkan/vk_common.h + src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp + src/video_core/renderer_vulkan/vk_descriptor_update_queue.h + src/video_core/renderer_vulkan/vk_instance.cpp + src/video_core/renderer_vulkan/vk_instance.h + src/video_core/renderer_vulkan/vk_master_semaphore.cpp + src/video_core/renderer_vulkan/vk_master_semaphore.h + src/video_core/renderer_vulkan/vk_platform.cpp + src/video_core/renderer_vulkan/vk_platform.h + src/video_core/renderer_vulkan/vk_resource_pool.cpp + src/video_core/renderer_vulkan/vk_resource_pool.h + src/video_core/renderer_vulkan/vk_scheduler.cpp + src/video_core/renderer_vulkan/vk_scheduler.h + src/video_core/renderer_vulkan/vk_shader_util.cpp + src/video_core/renderer_vulkan/vk_shader_util.h + src/video_core/renderer_vulkan/vk_stream_buffer.cpp + src/video_core/renderer_vulkan/vk_stream_buffer.h + src/video_core/renderer_vulkan/vk_swapchain.cpp + src/video_core/renderer_vulkan/vk_swapchain.h + src/video_core/texture_cache/image.cpp + src/video_core/texture_cache/image.h + src/video_core/texture_cache/image_view.cpp + src/video_core/texture_cache/image_view.h + src/video_core/texture_cache/slot_vector.h + src/video_core/texture_cache/texture_cache.cpp + src/video_core/texture_cache/texture_cache.h + src/video_core/texture_cache/tile_manager.cpp + src/video_core/texture_cache/tile_manager.h + src/video_core/texture_cache/types.h ) set(INPUT src/input/controller.cpp @@ -329,15 +357,15 @@ add_executable(shadps4 ${CORE} ${VIDEO_CORE} src/main.cpp - src/emulator.cpp - src/emulator.h + src/sdl_window.h + src/sdl_window.cpp ) endif() create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 SDL3-shared) -target_link_libraries(shadps4 PRIVATE discord-rpc vulkan-1 xxhash Zydis) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map) +target_link_libraries(shadps4 PRIVATE discord-rpc boost vma vulkan-headers xxhash Zydis SPIRV glslang SDL3-shared) if(NOT ENABLE_QT_GUI) target_link_libraries(shadps4 PRIVATE SDL3-shared) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 2eec1998..3334ccb8 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -6,6 +6,13 @@ if (MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS) endif() +# Boost +set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "") +set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost" CACHE STRING "") +set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "") +add_library(boost INTERFACE) +target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR}) + # fmtlib add_subdirectory(fmt EXCLUDE_FROM_ALL) @@ -35,3 +42,24 @@ add_subdirectory(zlib-ng) # SDL3 add_subdirectory(sdl3 EXCLUDE_FROM_ALL) + +# vulkan-headers +add_library(vulkan-headers INTERFACE) +target_include_directories(vulkan-headers SYSTEM INTERFACE ./vulkan-headers/include) + +# VMA +add_library(vma INTERFACE) +target_include_directories(vma SYSTEM INTERFACE ./vma/include) + +# glslang +set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "") +set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "") +set(ENABLE_SPVREMAPPER OFF CACHE BOOL "") +set(ENABLE_CTEST OFF CACHE BOOL "") +set(ENABLE_HLSL OFF CACHE BOOL "") +set(BUILD_EXTERNAL OFF CACHE BOOL "") +set(ENABLE_OPT OFF CACHE BOOL "") +add_subdirectory(glslang) + +# Robin-map +add_subdirectory(robin-map) diff --git a/externals/ext-boost b/externals/ext-boost new file mode 160000 index 00000000..dfb313f8 --- /dev/null +++ b/externals/ext-boost @@ -0,0 +1 @@ +Subproject commit dfb313f8357b8f6601fa7420be1a39a51ba86f77 diff --git a/externals/glslang b/externals/glslang new file mode 160000 index 00000000..2db79056 --- /dev/null +++ b/externals/glslang @@ -0,0 +1 @@ +Subproject commit 2db79056b418587e61122a8a820cd832b2abdf83 diff --git a/externals/robin-map b/externals/robin-map new file mode 160000 index 00000000..048eb144 --- /dev/null +++ b/externals/robin-map @@ -0,0 +1 @@ +Subproject commit 048eb1442a76ab81ecb3c4ab0495f15a68e54a6d diff --git a/externals/vma b/externals/vma new file mode 160000 index 00000000..5677097b --- /dev/null +++ b/externals/vma @@ -0,0 +1 @@ +Subproject commit 5677097bafb8477097c6e3354ce68b7a44fd01a4 diff --git a/externals/vulkan-headers b/externals/vulkan-headers new file mode 160000 index 00000000..cfebfc96 --- /dev/null +++ b/externals/vulkan-headers @@ -0,0 +1 @@ +Subproject commit cfebfc96b2b0bce93da7d12f2c14cc01793ae25c diff --git a/src/common/alignment.h b/src/common/alignment.h new file mode 100644 index 00000000..b55a54cf --- /dev/null +++ b/src/common/alignment.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2014 Jannik Vogel +// SPDX-License-Identifier: CC0-1.0 + +#pragma once + +#include +#include + +namespace Common { + +template +[[nodiscard]] constexpr T alignUp(T value, std::size_t size) { + static_assert(std::is_unsigned_v, "T must be an unsigned value."); + auto mod{static_cast(value % size)}; + value -= mod; + return static_cast(mod == T{0} ? value : value + size); +} + +template +[[nodiscard]] constexpr T alignDown(T value, std::size_t size) { + static_assert(std::is_unsigned_v, "T must be an unsigned value."); + return static_cast(value - value % size); +} + +template + requires std::is_integral_v +[[nodiscard]] constexpr bool is16KBAligned(T value) { + return (value & 0x3FFF) == 0; +} + +} // namespace Common diff --git a/src/common/enum.h b/src/common/enum.h new file mode 100644 index 00000000..1c1b5d40 --- /dev/null +++ b/src/common/enum.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#define DECLARE_ENUM_FLAG_OPERATORS(type) \ + [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) ^ static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) << static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) >> static_cast(b)); \ + } \ + constexpr type& operator|=(type& a, type b) noexcept { \ + a = a | b; \ + return a; \ + } \ + constexpr type& operator&=(type& a, type b) noexcept { \ + a = a & b; \ + return a; \ + } \ + constexpr type& operator^=(type& a, type b) noexcept { \ + a = a ^ b; \ + return a; \ + } \ + constexpr type& operator<<=(type& a, type b) noexcept { \ + a = a << b; \ + return a; \ + } \ + constexpr type& operator>>=(type& a, type b) noexcept { \ + a = a >> b; \ + return a; \ + } \ + [[nodiscard]] constexpr type operator~(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(~static_cast(key)); \ + } \ + [[nodiscard]] constexpr bool True(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) != 0; \ + } \ + [[nodiscard]] constexpr bool False(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) == 0; \ + } diff --git a/src/common/types.h b/src/common/types.h index 313d2fa2..c2b7bd35 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -22,6 +22,9 @@ using f64 = double; using u128 = std::array; static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide"); +using VAddr = uintptr_t; +using PAddr = uintptr_t; + #define PS4_SYSV_ABI __attribute__((sysv_abi)) // UDLs for memory size values diff --git a/src/core/PS4/GPU/gpu_memory.cpp b/src/core/PS4/GPU/gpu_memory.cpp deleted file mode 100644 index 21b9ea79..00000000 --- a/src/core/PS4/GPU/gpu_memory.cpp +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include "common/singleton.h" -#include "core/PS4/GPU/gpu_memory.h" - -void* GPU::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, - void* todo /*CommandBuffer?*/, u64 virtual_addr, u64 size, - const GPUObject& info) { - auto* gpumemory = Common::Singleton::Instance(); - - return gpumemory->memoryCreateObj(submit_id, ctx, nullptr, &virtual_addr, &size, 1, info); -} - -void GPU::memorySetAllocArea(u64 virtual_addr, u64 size) { - auto* gpumemory = Common::Singleton::Instance(); - - std::scoped_lock lock{gpumemory->m_mutex}; - - MemoryHeap h; - h.allocated_virtual_addr = virtual_addr; - h.allocated_size = size; - - gpumemory->m_heaps.push_back(h); -} - -u64 GPU::calculate_hash(const u8* buf, u64 size) { - return (size > 0 && buf != nullptr ? XXH3_64bits(buf, size) : 0); -} - -bool GPU::vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx, - HLE::Libs::Graphics::VulkanMemory* mem) { - static std::atomic_uint64_t unique_id = 0; - - VkPhysicalDeviceMemoryProperties memory_properties{}; - vkGetPhysicalDeviceMemoryProperties(ctx->m_physical_device, &memory_properties); - - u32 index = 0; - for (; index < memory_properties.memoryTypeCount; index++) { - if ((mem->requirements.memoryTypeBits & (static_cast(1) << index)) != 0 && - (memory_properties.memoryTypes[index].propertyFlags & mem->property) == mem->property) { - break; - } - } - - mem->type = index; - mem->offset = 0; - - VkMemoryAllocateInfo alloc_info{}; - alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - alloc_info.pNext = nullptr; - alloc_info.allocationSize = mem->requirements.size; - alloc_info.memoryTypeIndex = index; - - mem->unique_id = ++unique_id; - - auto result = vkAllocateMemory(ctx->m_device, &alloc_info, nullptr, &mem->memory); - - if (result == VK_SUCCESS) { - return true; - } - return false; -} - -void GPU::flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx) { - auto* gpumemory = Common::Singleton::Instance(); - gpumemory->flushAllHeaps(ctx); -} - -int GPU::GPUMemory::getHeapId(u64 virtual_addr, u64 size) { - int index = 0; - for (const auto& heap : m_heaps) { - if ((virtual_addr >= heap.allocated_virtual_addr && - virtual_addr < heap.allocated_virtual_addr + heap.allocated_size) || - ((virtual_addr + size - 1) >= heap.allocated_virtual_addr && - (virtual_addr + size - 1) < heap.allocated_virtual_addr + heap.allocated_size)) { - return index; - } - index++; - } - return -1; -} - -void* GPU::GPUMemory::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, - void* todo, const u64* virtual_addr, const u64* size, - int virtual_addr_num, const GPUObject& info) { - auto* gpumemory = Common::Singleton::Instance(); - - std::scoped_lock lock{gpumemory->m_mutex}; - - int heap_id = gpumemory->getHeapId(virtual_addr[0], size[0]); - - if (heap_id < 0) { - return nullptr; - } - auto& heap = m_heaps[heap_id]; - - ObjInfo objInfo = {}; - - // copy parameters from info to obj - for (int i = 0; i < 8; i++) { - objInfo.obj_params[i] = info.obj_params[i]; - } - - objInfo.gpu_object.objectType = info.objectType; - objInfo.gpu_object.obj = nullptr; - - for (int h = 0; h < virtual_addr_num; h++) { - if (info.check_hash) { - objInfo.hash[h] = - GPU::calculate_hash(reinterpret_cast(virtual_addr[h]), size[h]); - } else { - objInfo.hash[h] = 0; - } - } - objInfo.submit_id = submit_id; - objInfo.check_hash = info.check_hash; - - objInfo.gpu_object.obj = info.getCreateFunc()(ctx, objInfo.obj_params, virtual_addr, size, - virtual_addr_num, &objInfo.mem); - - objInfo.update_func = info.getUpdateFunc(); - int index = static_cast(heap.objects.size()); - - HeapObject hobj{}; - hobj.block = createHeapBlock(virtual_addr, size, virtual_addr_num, heap_id, index); - hobj.info = objInfo; - hobj.free = false; - heap.objects.push_back(hobj); - - return objInfo.gpu_object.obj; -} - -GPU::HeapBlock GPU::GPUMemory::createHeapBlock(const u64* virtual_addr, const u64* size, - int virtual_addr_num, int heap_id, int obj_id) { - auto& heap = m_heaps[heap_id]; - - GPU::HeapBlock heapBlock{}; - heapBlock.virtual_addr_num = virtual_addr_num; - for (int vi = 0; vi < virtual_addr_num; vi++) { - heapBlock.virtual_addr[vi] = virtual_addr[vi]; - heapBlock.size[vi] = size[vi]; - } - return heapBlock; -} - -void GPU::GPUMemory::update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, - int obj_id) { - auto& heap = m_heaps[heap_id]; - - auto& heapObj = heap.objects[obj_id]; - auto& objInfo = heapObj.info; - bool need_update = false; - - if (submit_id > objInfo.submit_id) { - uint64_t hash[3] = {}; - - for (int i = 0; i < heapObj.block.virtual_addr_num; i++) { - if (objInfo.check_hash) { - hash[i] = GPU::calculate_hash( - reinterpret_cast(heapObj.block.virtual_addr[i]), - heapObj.block.size[i]); - } else { - hash[i] = 0; - } - } - - for (int i = 0; i < heapObj.block.virtual_addr_num; i++) { - if (objInfo.hash[i] != hash[i]) { - need_update = true; - objInfo.hash[i] = hash[i]; - } - } - - if (submit_id != UINT64_MAX) { - objInfo.submit_id = submit_id; - } - } - - if (need_update) { - objInfo.update_func(ctx, objInfo.obj_params, objInfo.gpu_object.obj, - heapObj.block.virtual_addr, heapObj.block.size, - heapObj.block.virtual_addr_num); - } -} - -void GPU::GPUMemory::flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx) { - std::scoped_lock lock{m_mutex}; - - int heap_id = 0; - for (auto& heap : m_heaps) { - int index = 0; - for (auto& heapObj : heap.objects) { - if (!heapObj.free) { - update(UINT64_MAX, ctx, heap_id, index); - } - index++; - } - heap_id++; - } -} diff --git a/src/core/PS4/GPU/gpu_memory.h b/src/core/PS4/GPU/gpu_memory.h deleted file mode 100644 index 9897ae96..00000000 --- a/src/core/PS4/GPU/gpu_memory.h +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "common/types.h" -#include "core/PS4/HLE/Graphics/graphics_ctx.h" - -namespace GPU { - -class GPUObject; - -enum class MemoryMode : u32 { NoAccess = 0, Read = 1, Write = 2, ReadWrite = 3 }; -enum class MemoryObjectType : u64 { InvalidObj, VideoOutBufferObj }; - -struct GpuMemoryObject { - MemoryObjectType objectType = MemoryObjectType::InvalidObj; - void* obj = nullptr; -}; - -struct HeapBlock { - u64 virtual_addr[3] = {}; - u64 size[3] = {}; - int virtual_addr_num = 0; -}; - -class GPUObject { -public: - GPUObject() = default; - virtual ~GPUObject() = default; - u64 obj_params[8] = {}; - bool check_hash = false; - bool isReadOnly = false; - MemoryObjectType objectType = MemoryObjectType::InvalidObj; - - using create_func_t = void* (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, - const u64* virtual_addr, const u64* size, int virtual_addr_num, - HLE::Libs::Graphics::VulkanMemory* mem); - using update_func_t = void (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, - void* obj, const u64* virtual_addr, const u64* size, - int virtual_addr_num); - - virtual create_func_t getCreateFunc() const = 0; - virtual update_func_t getUpdateFunc() const = 0; -}; - -struct ObjInfo { - u64 obj_params[8] = {}; - GpuMemoryObject gpu_object; - u64 hash[3] = {}; - u64 submit_id = 0; - bool check_hash = false; - HLE::Libs::Graphics::VulkanMemory mem; - GPU::GPUObject::update_func_t update_func = nullptr; -}; - -struct HeapObject { - HeapBlock block; - ObjInfo info; - bool free = true; -}; -struct MemoryHeap { - u64 allocated_virtual_addr = 0; - u64 allocated_size = 0; - std::vector objects; -}; - -class GPUMemory { -public: - GPUMemory() {} - virtual ~GPUMemory() {} - int getHeapId(u64 vaddr, u64 size); - std::mutex m_mutex; - std::vector m_heaps; - void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, - /*CommandBuffer* buffer*/ void* todo, const u64* virtual_addr, - const u64* size, int virtual_addr_num, const GPUObject& info); - HeapBlock createHeapBlock(const u64* virtual_addr, const u64* size, int virtual_addr_num, - int heap_id, int obj_id); - void update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, int obj_id); - void flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx); -}; - -void memorySetAllocArea(u64 virtual_addr, u64 size); -void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, - /*CommandBuffer* buffer*/ void* todo, u64 virtual_addr, u64 size, - const GPUObject& info); -u64 calculate_hash(const u8* buf, u64 size); -bool vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx, - HLE::Libs::Graphics::VulkanMemory* mem); -void flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx); -} // namespace GPU diff --git a/src/core/PS4/GPU/tile_manager.h b/src/core/PS4/GPU/tile_manager.h deleted file mode 100644 index 2e848232..00000000 --- a/src/core/PS4/GPU/tile_manager.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace GPU { - -void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool neo); - -} // namespace GPU diff --git a/src/core/PS4/GPU/video_out_buffer.cpp b/src/core/PS4/GPU/video_out_buffer.cpp deleted file mode 100644 index 87283997..00000000 --- a/src/core/PS4/GPU/video_out_buffer.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/PS4/GPU/tile_manager.h" -#include "core/PS4/GPU/video_out_buffer.h" -#include "vulkan_util.h" - -static void update_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, void* obj, - const u64* virtual_addr, const u64* size, int virtual_addr_num) { - - auto pitch = params[GPU::VideoOutBufferObj::PITCH_PARAM]; - bool tiled = (params[GPU::VideoOutBufferObj::IS_TILE_PARAM] != 0); - bool neo = (params[GPU::VideoOutBufferObj::IS_NEO_PARAM] != 0); - auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM]; - auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM]; - - auto* vk_obj = static_cast(obj); - - vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED; - - if (tiled) { - std::vector tempbuff(*size); - GPU::convertTileToLinear(tempbuff.data(), reinterpret_cast(*virtual_addr), width, - height, neo); - Graphics::Vulkan::vulkanFillImage( - ctx, vk_obj, tempbuff.data(), *size, pitch, - static_cast(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)); - } else { - Graphics::Vulkan::vulkanFillImage( - ctx, vk_obj, reinterpret_cast(*virtual_addr), *size, pitch, - static_cast(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)); - } -} - -static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, - const u64* virtual_addr, const u64* size, int virtual_addr_num, - HLE::Libs::Graphics::VulkanMemory* mem) { - auto pixel_format = params[GPU::VideoOutBufferObj::PIXEL_FORMAT_PARAM]; - auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM]; - auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM]; - - auto* vk_obj = new HLE::Libs::Graphics::VideoOutVulkanImage; - - VkFormat vk_format = VK_FORMAT_UNDEFINED; - - switch (pixel_format) { - case static_cast(GPU::VideoOutBufferFormat::R8G8B8A8Srgb): - vk_format = VK_FORMAT_R8G8B8A8_SRGB; - break; - case static_cast(GPU::VideoOutBufferFormat::B8G8R8A8Srgb): - vk_format = VK_FORMAT_B8G8R8A8_SRGB; - break; - default: - UNREACHABLE_MSG("Unknown pixelFormat = {}", pixel_format); - } - - vk_obj->extent.width = width; - vk_obj->extent.height = height; - vk_obj->format = vk_format; - vk_obj->image = nullptr; - vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED; - - for (auto& view : vk_obj->image_view) { - view = nullptr; - } - - VkImageCreateInfo image_info{}; - image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - image_info.pNext = nullptr; - image_info.flags = 0; - image_info.imageType = VK_IMAGE_TYPE_2D; - image_info.extent.width = vk_obj->extent.width; - image_info.extent.height = vk_obj->extent.height; - image_info.extent.depth = 1; - image_info.mipLevels = 1; - image_info.arrayLayers = 1; - image_info.format = vk_obj->format; - image_info.tiling = VK_IMAGE_TILING_OPTIMAL; - image_info.initialLayout = vk_obj->layout; - image_info.usage = static_cast(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT) | - VK_IMAGE_USAGE_TRANSFER_DST_BIT; - image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - image_info.samples = VK_SAMPLE_COUNT_1_BIT; - - vkCreateImage(ctx->m_device, &image_info, nullptr, &vk_obj->image); - - if (vk_obj->image == nullptr) { - UNREACHABLE_MSG("vk_obj->image is null"); - } - - vkGetImageMemoryRequirements(ctx->m_device, vk_obj->image, &mem->requirements); - - mem->property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - - bool allocated = GPU::vulkanAllocateMemory(ctx, mem); - - if (!allocated) { - UNREACHABLE_MSG("Can't allocate vulkan memory"); - } - - vkBindImageMemory(ctx->m_device, vk_obj->image, mem->memory, mem->offset); - - vk_obj->memory = *mem; - - LOG_INFO(Lib_VideoOut, "videoOutBuffer create object width = {}, height = {}, size = {}", width, - height, *size); - - update_func(ctx, params, vk_obj, virtual_addr, size, virtual_addr_num); - - VkImageViewCreateInfo create_info{}; - create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - create_info.pNext = nullptr; - create_info.flags = 0; - create_info.image = vk_obj->image; - create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; - create_info.format = vk_obj->format; - create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - create_info.subresourceRange.baseArrayLayer = 0; - create_info.subresourceRange.baseMipLevel = 0; - create_info.subresourceRange.layerCount = 1; - create_info.subresourceRange.levelCount = 1; - - vkCreateImageView(ctx->m_device, &create_info, nullptr, - &vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT]); - - if (vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT] == nullptr) { - UNREACHABLE_MSG("vk_obj->image_view is null"); - } - - return vk_obj; -} - -GPU::GPUObject::create_func_t GPU::VideoOutBufferObj::getCreateFunc() const { - return create_func; -} - -GPU::GPUObject::update_func_t GPU::VideoOutBufferObj::getUpdateFunc() const { - return update_func; -} diff --git a/src/core/PS4/GPU/video_out_buffer.h b/src/core/PS4/GPU/video_out_buffer.h deleted file mode 100644 index 6b0e3225..00000000 --- a/src/core/PS4/GPU/video_out_buffer.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" -#include "core/PS4/GPU/gpu_memory.h" - -namespace GPU { - -enum class VideoOutBufferFormat : u64 { - Unknown, - R8G8B8A8Srgb, - B8G8R8A8Srgb, -}; - -class VideoOutBufferObj : public GPUObject { -public: - static constexpr int PIXEL_FORMAT_PARAM = 0; - static constexpr int WIDTH_PARAM = 1; - static constexpr int HEIGHT_PARAM = 2; - static constexpr int IS_TILE_PARAM = 3; - static constexpr int IS_NEO_PARAM = 4; - static constexpr int PITCH_PARAM = 5; - - explicit VideoOutBufferObj(VideoOutBufferFormat pixel_format, u32 width, u32 height, - bool is_tiled, bool is_neo, u32 pitch) { - obj_params[PIXEL_FORMAT_PARAM] = static_cast(pixel_format); - obj_params[WIDTH_PARAM] = width; - obj_params[HEIGHT_PARAM] = height; - obj_params[IS_TILE_PARAM] = is_tiled ? 1 : 0; - obj_params[IS_NEO_PARAM] = is_neo ? 1 : 0; - obj_params[PITCH_PARAM] = pitch; - check_hash = true; - objectType = GPU::MemoryObjectType::VideoOutBufferObj; - } - - create_func_t getCreateFunc() const override; - update_func_t getUpdateFunc() const override; -}; -} // namespace GPU diff --git a/src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp b/src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp deleted file mode 100644 index 68f49afe..00000000 --- a/src/core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/debug.h" -#include "core/PS4/HLE/Graphics/Objects/video_out_ctx.h" -#include "core/libraries/kernel/time_management.h" - -namespace HLE::Graphics::Objects { - -void VideoOutCtx::Init(u32 width, u32 height) { - m_video_out_ctx.m_resolution.fullWidth = width; - m_video_out_ctx.m_resolution.fullHeight = height; - m_video_out_ctx.m_resolution.paneWidth = width; - m_video_out_ctx.m_resolution.paneHeight = height; -} - -int VideoOutCtx::Open() { - std::scoped_lock lock{m_mutex}; - - int handle = -1; - - if (!m_video_out_ctx.isOpened) { - handle = 1; // positive return , should be more than 1 ? - } - - m_video_out_ctx.isOpened = true; - m_video_out_ctx.m_flip_status = SceVideoOutFlipStatus(); - m_video_out_ctx.m_flip_status.flipArg = -1; - m_video_out_ctx.m_flip_status.currentBuffer = -1; - m_video_out_ctx.m_flip_status.count = 0; - m_video_out_ctx.m_vblank_status = SceVideoOutVblankStatus(); - - return handle; -} -void VideoOutCtx::Close(s32 handle) { - std::scoped_lock lock{m_mutex}; - - m_video_out_ctx.isOpened = false; - - if (m_video_out_ctx.m_flip_evtEq.size() > 0) { - BREAKPOINT(); // we need to clear all events if they have been created - } - - m_video_out_ctx.m_flip_rate = 0; - - // clear buffers - for (auto& buffer : m_video_out_ctx.buffers) { - buffer.buffer = nullptr; - buffer.buffer_render = nullptr; - buffer.buffer_size = 0; - buffer.set_id = 0; - } - - m_video_out_ctx.buffers_sets.clear(); - - m_video_out_ctx.buffers_registration_index = 0; -} - -void VideoOutCtx::Vblank() { - std::scoped_lock lock{m_mutex}; - - if (m_video_out_ctx.isOpened) { - m_video_out_ctx.m_mutex.lock(); - m_video_out_ctx.m_vblank_status.count++; - m_video_out_ctx.m_vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); - m_video_out_ctx.m_vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc(); - m_video_out_ctx.m_mutex.unlock(); - } -} -VideoConfigInternal* VideoOutCtx::getCtx(int handle) { - if (handle != 1) [[unlikely]] { - return nullptr; - } - return &m_video_out_ctx; // assuming that it's the only ctx TODO check if we need more -} - -void FlipQueue::getFlipStatus(VideoConfigInternal* cfg, SceVideoOutFlipStatus* out) { - std::scoped_lock lock(m_mutex); - *out = cfg->m_flip_status; -} - -bool FlipQueue::submitFlip(VideoConfigInternal* cfg, s32 index, s64 flip_arg) { - std::scoped_lock lock{m_mutex}; - - if (m_requests.size() >= 2) { - return false; - } - - Request r{}; - r.cfg = cfg; - r.index = index; - r.flip_arg = flip_arg; - r.submit_tsc = Libraries::Kernel::sceKernelReadTsc(); - - m_requests.push_back(r); - - cfg->m_flip_status.flipPendingNum = static_cast(m_requests.size()); - cfg->m_flip_status.gcQueueNum = 0; - - m_submit_cond.notify_one(); - - return true; -} - -bool FlipQueue::flip(u32 micros) { - const auto request = [&]() -> Request* { - std::unique_lock lock{m_mutex}; - m_submit_cond.wait_for(lock, std::chrono::microseconds(micros), - [&] { return !m_requests.empty(); }); - if (m_requests.empty()) { - return nullptr; - } - return &m_requests.at(0); // Process first request - }(); - - if (!request) { - return false; - } - - const auto buffer = request->cfg->buffers[request->index].buffer_render; - Emu::DrawBuffer(buffer); - - std::scoped_lock lock{m_mutex}; - - { - std::scoped_lock cfg_lock{request->cfg->m_mutex}; - for (auto& flip_eq : request->cfg->m_flip_evtEq) { - if (flip_eq != nullptr) { - flip_eq->triggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Libraries::Kernel::EVFILT_VIDEO_OUT, - reinterpret_cast(request->flip_arg)); - } - } - } - - m_requests.erase(m_requests.begin()); - m_done_cond.notify_one(); - - request->cfg->m_flip_status.count++; - request->cfg->m_flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); - request->cfg->m_flip_status.tsc = Libraries::Kernel::sceKernelReadTsc(); - request->cfg->m_flip_status.submitTsc = request->submit_tsc; - request->cfg->m_flip_status.flipArg = request->flip_arg; - request->cfg->m_flip_status.currentBuffer = request->index; - request->cfg->m_flip_status.flipPendingNum = static_cast(m_requests.size()); - - return true; -} - -} // namespace HLE::Graphics::Objects diff --git a/src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h b/src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h deleted file mode 100644 index 36a783fd..00000000 --- a/src/core/PS4/HLE/Graphics/Objects/video_out_ctx.h +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "core/PS4/HLE/Graphics/graphics_ctx.h" -#include "core/PS4/HLE/Graphics/video_out.h" -#include "emulator.h" - -using namespace HLE::Libs::Graphics::VideoOut; - -namespace HLE::Graphics::Objects { - -struct VideoOutBufferInfo { - const void* buffer = nullptr; - HLE::Libs::Graphics::VideoOutVulkanImage* buffer_render = nullptr; - u64 buffer_size = 0; - u64 buffer_pitch = 0; - int set_id = 0; -}; - -struct VideoConfigInternal { - std::mutex m_mutex; - SceVideoOutResolutionStatus m_resolution; - bool isOpened = false; - SceVideoOutFlipStatus m_flip_status; - SceVideoOutVblankStatus m_vblank_status; - std::vector m_flip_evtEq; - int m_flip_rate = 0; - VideoOutBufferInfo buffers[16]; - std::vector buffers_sets; - int buffers_registration_index = 0; -}; - -class FlipQueue { -public: - FlipQueue() {} - virtual ~FlipQueue() {} - - void getFlipStatus(VideoConfigInternal* cfg, SceVideoOutFlipStatus* out); - bool submitFlip(VideoConfigInternal* cfg, s32 index, s64 flip_arg); - bool flip(u32 micros); - -private: - struct Request { - VideoConfigInternal* cfg; - int index; - int64_t flip_arg; - uint64_t submit_tsc; - }; - - std::mutex m_mutex; - std::condition_variable m_submit_cond; - std::condition_variable m_done_cond; - std::vector m_requests; -}; - -class VideoOutCtx { - -public: - VideoOutCtx() {} - virtual ~VideoOutCtx() {} - void Init(u32 width, u32 height); - int Open(); - void Close(s32 handle); - VideoConfigInternal* getCtx(int handle); - FlipQueue& getFlipQueue() { - return m_flip_queue; - } - HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() { - std::scoped_lock lock{m_mutex}; - - if (!m_graphic_ctx) { - m_graphic_ctx = Emu::getGraphicCtx(); - } - - return m_graphic_ctx; - } - void Vblank(); - -private: - std::mutex m_mutex; - VideoConfigInternal m_video_out_ctx; - FlipQueue m_flip_queue; - HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr; -}; - -} // namespace HLE::Graphics::Objects diff --git a/src/core/PS4/HLE/Graphics/graphics_ctx.h b/src/core/PS4/HLE/Graphics/graphics_ctx.h deleted file mode 100644 index 52821065..00000000 --- a/src/core/PS4/HLE/Graphics/graphics_ctx.h +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include "common/types.h" - -namespace HLE::Libs::Graphics { - -struct VulkanCommandPool { - std::mutex mutex; - VkCommandPool pool = nullptr; - std::vector buffers; - std::vector fences; - std::vector semaphores; - std::vector busy; - u32 buffers_count = 0; -}; - -struct VulkanQueueInfo { - std::unique_ptr mutex{}; - u32 family = static_cast(-1); - u32 index = static_cast(-1); - VkQueue vk_queue = nullptr; -}; - -struct GraphicCtx { - u32 screen_width = 0; - u32 screen_height = 0; - VkInstance m_instance = nullptr; - VkPhysicalDevice m_physical_device = nullptr; - VkDevice m_device = nullptr; - VulkanQueueInfo queues[11]; // VULKAN_QUEUES_NUM -}; - -enum class VulkanImageType { Unknown, VideoOut }; - -struct VulkanMemory { - VkMemoryRequirements requirements = {}; - VkMemoryPropertyFlags property = 0; - VkDeviceMemory memory = nullptr; - VkDeviceSize offset = 0; - u32 type = 0; - u64 unique_id = 0; -}; - -struct VulkanBuffer { - VkBuffer buffer = nullptr; - VulkanMemory memory; - VkBufferUsageFlags usage = 0; -}; -struct VulkanImage { - static constexpr int VIEW_MAX = 4; - static constexpr int VIEW_DEFAULT = 0; - static constexpr int VIEW_BGRA = 1; - static constexpr int VIEW_DEPTH_TEXTURE = 2; - - explicit VulkanImage(VulkanImageType type) : type(type) {} - - VulkanImageType type = VulkanImageType::Unknown; - VkFormat format = VK_FORMAT_UNDEFINED; - VkExtent2D extent = {}; - VkImage image = nullptr; - VkImageView image_view[VIEW_MAX] = {}; - VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; - VulkanMemory memory; -}; - -struct VideoOutVulkanImage : public VulkanImage { - VideoOutVulkanImage() : VulkanImage(VulkanImageType::VideoOut) {} -}; - -} // namespace HLE::Libs::Graphics diff --git a/src/core/PS4/HLE/Graphics/graphics_render.cpp b/src/core/PS4/HLE/Graphics/graphics_render.cpp deleted file mode 100644 index d3d19333..00000000 --- a/src/core/PS4/HLE/Graphics/graphics_render.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/singleton.h" -#include "core/PS4/HLE/Graphics/graphics_render.h" -#include "emulator.h" - -static thread_local GPU::CommandPool g_command_pool; - -void GPU::renderCreateCtx() { - auto* render_ctx = Common::Singleton::Instance(); - - render_ctx->setGraphicCtx(Emu::getGraphicCtx()); -} - -void GPU::CommandBuffer::allocateBuffer() { - m_pool = g_command_pool.getPool(m_queue); - - std::scoped_lock lock{m_pool->mutex}; - - for (uint32_t i = 0; i < m_pool->buffers_count; i++) { - if (!m_pool->busy[i]) { - m_pool->busy[i] = true; - vkResetCommandBuffer(m_pool->buffers[i], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); - m_index = i; - break; - } - } -} - -void GPU::CommandBuffer::freeBuffer() { - std::scoped_lock lock{m_pool->mutex}; - - waitForFence(); - - m_pool->busy[m_index] = false; - vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); - m_index = static_cast(-1); -} - -void GPU::CommandBuffer::waitForFence() { - auto* render_ctx = Common::Singleton::Instance(); - - if (m_execute) { - auto* device = render_ctx->getGraphicCtx()->m_device; - - vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX); - vkResetFences(device, 1, &m_pool->fences[m_index]); - - m_execute = false; - } -} -void GPU::CommandBuffer::begin() const { - auto* buffer = m_pool->buffers[m_index]; - - VkCommandBufferBeginInfo begin_info{}; - begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - begin_info.pNext = nullptr; - begin_info.flags = 0; - begin_info.pInheritanceInfo = nullptr; - - auto result = vkBeginCommandBuffer(buffer, &begin_info); - - if (result != VK_SUCCESS) { - fmt::print("vkBeginCommandBuffer failed\n"); - std::exit(0); - } -} -void GPU::CommandBuffer::end() const { - auto* buffer = m_pool->buffers[m_index]; - - auto result = vkEndCommandBuffer(buffer); - - if (result != VK_SUCCESS) { - fmt::print("vkEndCommandBuffer failed\n"); - std::exit(0); - } -} -void GPU::CommandBuffer::executeWithSemaphore() { - auto* buffer = m_pool->buffers[m_index]; - auto* fence = m_pool->fences[m_index]; - - VkSubmitInfo submit_info{}; - submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submit_info.pNext = nullptr; - submit_info.waitSemaphoreCount = 0; - submit_info.pWaitSemaphores = nullptr; - submit_info.pWaitDstStageMask = nullptr; - submit_info.commandBufferCount = 1; - submit_info.pCommandBuffers = &buffer; - submit_info.signalSemaphoreCount = 1; - submit_info.pSignalSemaphores = &m_pool->semaphores[m_index]; - - auto* render_ctx = Common::Singleton::Instance(); - const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue]; - auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence); - - m_execute = true; - - if (result != VK_SUCCESS) { - fmt::print("vkQueueSubmit failed\n"); - std::exit(0); - } -} -void GPU::CommandBuffer::execute() { - auto* buffer = m_pool->buffers[m_index]; - auto* fence = m_pool->fences[m_index]; - - VkSubmitInfo submit_info{}; - submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submit_info.pNext = nullptr; - submit_info.waitSemaphoreCount = 0; - submit_info.pWaitSemaphores = nullptr; - submit_info.pWaitDstStageMask = nullptr; - submit_info.commandBufferCount = 1; - submit_info.pCommandBuffers = &buffer; - submit_info.signalSemaphoreCount = 0; - submit_info.pSignalSemaphores = nullptr; - - auto* render_ctx = Common::Singleton::Instance(); - const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue]; - auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence); - - m_execute = true; - - if (result != VK_SUCCESS) { - fmt::print("vkQueueSubmit failed\n"); - std::exit(0); - } -} -void GPU::CommandPool::createPool(int id) { - auto* render_ctx = Common::Singleton::Instance(); - auto* ctx = render_ctx->getGraphicCtx(); - - VkCommandPoolCreateInfo pool_info{}; - pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - pool_info.pNext = nullptr; - pool_info.queueFamilyIndex = ctx->queues[id].family; - pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - - vkCreateCommandPool(ctx->m_device, &pool_info, nullptr, &m_pool[id].pool); - - if (!m_pool[id].pool) { - fmt::print("pool is nullptr"); - std::exit(0); - } - - m_pool[id].buffers_count = 4; - m_pool[id].buffers.resize(m_pool[id].buffers_count); - m_pool[id].fences.resize(m_pool[id].buffers_count); - m_pool[id].semaphores.resize(m_pool[id].buffers_count); - m_pool[id].busy.resize(m_pool[id].buffers_count); - - VkCommandBufferAllocateInfo alloc_info{}; - alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - alloc_info.commandPool = m_pool[id].pool; - alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - alloc_info.commandBufferCount = m_pool[id].buffers_count; - - if (vkAllocateCommandBuffers(ctx->m_device, &alloc_info, m_pool[id].buffers.data()) != - VK_SUCCESS) { - fmt::print("Can't allocate command buffers\n"); - std::exit(0); - } - - for (u32 i = 0; i < m_pool[id].buffers_count; i++) { - m_pool[id].busy[i] = false; - - VkFenceCreateInfo fence_info{}; - fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fence_info.pNext = nullptr; - fence_info.flags = 0; - - if (vkCreateFence(ctx->m_device, &fence_info, nullptr, &m_pool[id].fences[i]) != - VK_SUCCESS) { - fmt::print("Can't create fence\n"); - std::exit(0); - } - - VkSemaphoreCreateInfo semaphore_info{}; - semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphore_info.pNext = nullptr; - semaphore_info.flags = 0; - - if (vkCreateSemaphore(ctx->m_device, &semaphore_info, nullptr, &m_pool[id].semaphores[i]) != - VK_SUCCESS) { - fmt::print("Can't create semas\n"); - std::exit(0); - } - } -} - -void GPU::CommandPool::deleteAllPool() { - auto* render_ctx = Common::Singleton::Instance(); - auto* ctx = render_ctx->getGraphicCtx(); - - for (auto& pool : m_pool) { - if (pool.pool) { - for (u32 i = 0; i < pool.buffers_count; i++) { - vkDestroySemaphore(ctx->m_device, pool.semaphores[i], nullptr); - vkDestroyFence(ctx->m_device, pool.fences[i], nullptr); - } - vkDestroyCommandPool(ctx->m_device, pool.pool, nullptr); - } - } -} diff --git a/src/core/PS4/HLE/Graphics/graphics_render.h b/src/core/PS4/HLE/Graphics/graphics_render.h deleted file mode 100644 index 15c375f5..00000000 --- a/src/core/PS4/HLE/Graphics/graphics_render.h +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "core/PS4/HLE/Graphics/graphics_ctx.h" - -namespace GPU { - -class CommandPool { -public: - CommandPool() = default; - ~CommandPool() {} - - HLE::Libs::Graphics::VulkanCommandPool* getPool(int id) { - if (!m_pool[id].pool) { - createPool(id); - } - return &m_pool[id]; - } - -private: - void createPool(int id); - void deleteAllPool(); - - std::array m_pool{}; -}; -class CommandBuffer { -public: - explicit CommandBuffer(int queue) : m_queue(queue) { - allocateBuffer(); - } - virtual ~CommandBuffer() { - freeBuffer(); - } - void allocateBuffer(); - void freeBuffer(); - void waitForFence(); - void begin() const; - void end() const; - void executeWithSemaphore(); - void execute(); - u32 getIndex() const { - return m_index; - } - HLE::Libs::Graphics::VulkanCommandPool* getPool() { - return m_pool; - } - -private: - int m_queue = -1; - u32 m_index = static_cast(-1); - HLE::Libs::Graphics::VulkanCommandPool* m_pool = nullptr; - bool m_execute = false; -}; - -class Framebuffer { -public: - Framebuffer() {} - virtual ~Framebuffer() {} -}; -class RenderCtx { -public: - RenderCtx() = default; - - virtual ~RenderCtx() {} - void setGraphicCtx(HLE::Libs::Graphics::GraphicCtx* ctx) { - m_graphic_ctx = ctx; - } - HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() { - return m_graphic_ctx; - } - -private: - Framebuffer m_framebuffer{}; - HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr; -}; - -void renderCreateCtx(); -}; // namespace GPU diff --git a/src/core/PS4/HLE/Graphics/video_out.cpp b/src/core/PS4/HLE/Graphics/video_out.cpp deleted file mode 100644 index a734ce54..00000000 --- a/src/core/PS4/HLE/Graphics/video_out.cpp +++ /dev/null @@ -1,382 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "Objects/video_out_ctx.h" -#include "common/config.h" -#include "common/debug.h" -#include "common/logging/log.h" -#include "common/singleton.h" -#include "core/PS4/GPU/gpu_memory.h" -#include "core/PS4/GPU/video_out_buffer.h" -#include "core/PS4/HLE/Graphics/graphics_render.h" -#include "core/PS4/HLE/Graphics/video_out.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/gnmdriver/gnmdriver.h" -#include "core/libraries/libs.h" -#include "core/loader/symbols_resolver.h" -#include "emulator.h" -#include "src/core/libraries/system/userservice.h" - -namespace HLE::Libs::Graphics::VideoOut { - -constexpr bool log_file_videoout = true; // disable it to disable logging - -void videoOutInit(u32 width, u32 height) { - auto* videoOut = Common::Singleton::Instance(); - videoOut->Init(width, height); -} - -bool videoOutFlip(u32 micros) { - auto* videoOut = Common::Singleton::Instance(); - return videoOut->getFlipQueue().flip(micros); -} -void VideoOutVblank() { - auto* videoOut = Common::Singleton::Instance(); - return videoOut->Vblank(); -} - -std::string getPixelFormatString(s32 format) { - switch (format) { - case SCE_VIDEO_OUT_PIXEL_FORMAT_A8R8G8B8_SRGB: - return "PIXEL_FORMAT_A8R8G8B8_SRGB"; - case SCE_VIDEO_OUT_PIXEL_FORMAT_A8B8G8R8_SRGB: - return "PIXEL_FORMAT_A8B8G8R8_SRGB"; - case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10: - return "PIXEL_FORMAT_A2R10G10B10"; - case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_SRGB: - return "PIXEL_FORMAT_A2R10G10B10_SRGB"; - case SCE_VIDEO_OUT_PIXEL_FORMAT_A2R10G10B10_BT2020_PQ: - return "PIXEL_FORMAT_A2R10G10B10_BT2020_PQ"; - case SCE_VIDEO_OUT_PIXEL_FORMAT_A16R16G16B16_FLOAT: - return "PIXEL_FORMAT_A16R16G16B16_FLOAT"; - case SCE_VIDEO_OUT_PIXEL_FORMAT_YCBCR420_BT709: - return "PIXEL_FORMAT_YCBCR420_BT709"; - default: - return "Unknown pixel format"; - } -} - -void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute, - u32 pixelFormat, u32 tilingMode, u32 aspectRatio, - u32 width, u32 height, u32 pitchInPixel) { - LOG_INFO(Lib_VideoOut, - "pixelFormat = {}, tilingMode = {}, aspectRatio = {}, width = {}, height = {}, " - "pitchInPixel = {}", - getPixelFormatString(pixelFormat), tilingMode, aspectRatio, width, height, - pitchInPixel); - - std::memset(attribute, 0, sizeof(SceVideoOutBufferAttribute)); - - attribute->pixelFormat = pixelFormat; - attribute->tilingMode = tilingMode; - attribute->aspectRatio = aspectRatio; - attribute->width = width; - attribute->height = height; - attribute->pitchInPixel = pitchInPixel; - attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE; -} - -static void flip_reset_event_func(Libraries::Kernel::EqueueEvent* event) { - event->isTriggered = false; - event->event.fflags = 0; - event->event.data = 0; -} - -static void flip_trigger_event_func(Libraries::Kernel::EqueueEvent* event, void* trigger_data) { - event->isTriggered = true; - event->event.fflags++; - event->event.data = reinterpret_cast(trigger_data); -} - -static void flip_delete_event_func(Libraries::Kernel::SceKernelEqueue eq, - Libraries::Kernel::EqueueEvent* event) { - BREAKPOINT(); // TODO -} - -s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Libraries::Kernel::SceKernelEqueue eq, s32 handle, - void* udata) { - LOG_INFO(Lib_VideoOut, "handle = {}", handle); - - auto* videoOut = Common::Singleton::Instance(); - - auto* ctx = videoOut->getCtx(handle); - - if (ctx == nullptr) { - return SCE_VIDEO_OUT_ERROR_INVALID_HANDLE; - } - std::scoped_lock lock(ctx->m_mutex); - - if (eq == nullptr) { - return SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; - } - - Libraries::Kernel::EqueueEvent event{}; - event.isTriggered = false; - event.event.ident = SCE_VIDEO_OUT_EVENT_FLIP; - event.event.filter = Libraries::Kernel::EVFILT_VIDEO_OUT; - event.event.udata = udata; - event.event.fflags = 0; - event.event.data = 0; - event.filter.delete_event_func = flip_delete_event_func; - event.filter.reset_event_func = flip_reset_event_func; - event.filter.trigger_event_func = flip_trigger_event_func; - event.filter.data = ctx; - - int result = eq->addEvent(event); - - ctx->m_flip_evtEq.push_back(eq); - - return result; -} - -s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus* status) { - if (status == nullptr) { - return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS; - } - - auto* videoOut = Common::Singleton::Instance(); - auto* ctx = videoOut->getCtx(handle); - - ctx->m_mutex.lock(); - *status = ctx->m_vblank_status; - ctx->m_mutex.unlock(); - return SCE_OK; -} - -s32 sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, - const SceVideoOutBufferAttribute* attribute) { - auto* videoOut = Common::Singleton::Instance(); - auto* ctx = videoOut->getCtx(handle); - - if (handle == 1) { // main port - if (startIndex < 0 || startIndex > 15) { - LOG_ERROR(Lib_VideoOut, "Invalid startIndex = {}", startIndex); - return SCE_VIDEO_OUT_ERROR_INVALID_VALUE; - } - if (bufferNum < 1 || bufferNum > 16) { - LOG_ERROR(Lib_VideoOut, "Invalid bufferNum = {}", bufferNum); - return SCE_VIDEO_OUT_ERROR_INVALID_VALUE; - } - } - if (addresses == nullptr) { - LOG_ERROR(Lib_VideoOut, "Addresses are null"); - return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS; - } - - if (attribute == nullptr) { - LOG_ERROR(Lib_VideoOut, "Attribute is null"); - return SCE_VIDEO_OUT_ERROR_INVALID_OPTION; - } - if (attribute->aspectRatio != 0) { - LOG_ERROR(Lib_VideoOut, "Invalid aspect ratio = {}", attribute->aspectRatio); - return SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO; - } - if (attribute->tilingMode < 0 || attribute->tilingMode > 1) { - LOG_ERROR(Lib_VideoOut, "Invalid tilingMode = {}", attribute->tilingMode); - return SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE; - } - - LOG_INFO(Lib_VideoOut, - "handle = {}, startIndex = {}, bufferNum = {}, pixelFormat = {:#x}, aspectRatio = {}, " - "tilingMode = {}, width = {}, height = {}, pitchInPixel = {}, option = {:#x}", - handle, startIndex, bufferNum, attribute->pixelFormat, attribute->aspectRatio, - attribute->tilingMode, attribute->width, attribute->height, attribute->pitchInPixel, - attribute->option); - - int registration_index = ctx->buffers_registration_index++; - - Emu::checkAndWaitForGraphicsInit(); - GPU::renderCreateCtx(); - - // try to calculate buffer size - u64 buffer_size = 0; // still calculation is probably partial or wrong :D - if (attribute->tilingMode == 0) { - buffer_size = 1920 * 1088 * 4; - } else { - buffer_size = 1920 * 1080 * 4; - } - u64 buffer_pitch = attribute->pitchInPixel; - - VideoOutBufferSetInternal buf{}; - - buf.start_index = startIndex; - buf.num = bufferNum; - buf.set_id = registration_index; - buf.attr = *attribute; - - ctx->buffers_sets.push_back(buf); - - GPU::VideoOutBufferFormat format = GPU::VideoOutBufferFormat::Unknown; - - if (attribute->pixelFormat == 0x80000000) { - format = GPU::VideoOutBufferFormat::B8G8R8A8Srgb; - } else if (attribute->pixelFormat == 0x80002200) { - format = GPU::VideoOutBufferFormat::R8G8B8A8Srgb; - } - - GPU::VideoOutBufferObj buffer_info(format, attribute->width, attribute->height, - attribute->tilingMode == 0, Config::isNeoMode(), - buffer_pitch); - - for (int i = 0; i < bufferNum; i++) { - if (ctx->buffers[i + startIndex].buffer != nullptr) { - LOG_ERROR(Lib_VideoOut, "Buffer slot {} is occupied!", i + startIndex); - return SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED; - } - - ctx->buffers[i + startIndex].set_id = registration_index; - ctx->buffers[i + startIndex].buffer = addresses[i]; - ctx->buffers[i + startIndex].buffer_size = buffer_size; - ctx->buffers[i + startIndex].buffer_pitch = buffer_pitch; - ctx->buffers[i + startIndex].buffer_render = - static_cast(GPU::memoryCreateObj( - 0, videoOut->getGraphicCtx(), nullptr, reinterpret_cast(addresses[i]), - buffer_size, buffer_info)); - - LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex, - reinterpret_cast(addresses[i])); - } - - return registration_index; -} - -s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) { - LOG_INFO(Lib_VideoOut, "called"); - auto* videoOut = Common::Singleton::Instance(); - videoOut->getCtx(handle)->m_flip_rate = rate; - return SCE_OK; -} - -s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) { - LOG_INFO(Lib_VideoOut, "called"); - auto* videoOut = Common::Singleton::Instance(); - s32 pending = videoOut->getCtx(handle)->m_flip_status.flipPendingNum; - return pending; -} - -s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) { - auto* videoOut = Common::Singleton::Instance(); - auto* ctx = videoOut->getCtx(handle); - - if (flipMode != 1) { - // BREAKPOINT(); // only flipmode==1 is supported - LOG_WARNING(Lib_VideoOut, "flipmode = {}", - flipMode); // openBOR needs 2 but seems to work - } - if (bufferIndex == -1) { - BREAKPOINT(); // blank output not supported - } - if (bufferIndex < -1 || bufferIndex > 15) { - LOG_ERROR(Lib_VideoOut, "Invalid bufferIndex = {}", bufferIndex); - return SCE_VIDEO_OUT_ERROR_INVALID_INDEX; - } - - LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode, - flipArg); - - if (!videoOut->getFlipQueue().submitFlip(ctx, bufferIndex, flipArg)) { - LOG_ERROR(Lib_VideoOut, "Flip queue is full"); - return SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; - } - Libraries::GnmDriver::sceGnmFlushGarlic(); // hackish should be done that neccesary - // for niko's homebrew - return SCE_OK; -} - -s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status) { - auto* videoOut = Common::Singleton::Instance(); - auto* ctx = videoOut->getCtx(handle); - videoOut->getFlipQueue().getFlipStatus(ctx, status); - - LOG_INFO(Lib_VideoOut, - "count = {}, processTime = {}, tsc = {}, submitTsc = {}, flipArg = {}, gcQueueNum = " - "{}, flipPendingNum = {}, currentBuffer = {}", - status->count, status->processTime, status->tsc, status->submitTsc, status->flipArg, - status->gcQueueNum, status->flipPendingNum, status->currentBuffer); - return 0; -} - -s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) { - LOG_INFO(Lib_VideoOut, "called"); - auto* videoOut = Common::Singleton::Instance(); - *status = videoOut->getCtx(handle)->m_resolution; - return SCE_OK; -} - -s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, - const void* param) { - LOG_INFO(Lib_VideoOut, "called"); - if (userId != Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM && userId != 0) { - BREAKPOINT(); - } - if (busType != SCE_VIDEO_OUT_BUS_TYPE_MAIN) { - BREAKPOINT(); - } - if (index != 0) { - LOG_ERROR(Lib_VideoOut, "Index != 0"); - return SCE_VIDEO_OUT_ERROR_INVALID_VALUE; - } - if (param != nullptr) { - BREAKPOINT(); - } - auto* videoOut = Common::Singleton::Instance(); - int handle = videoOut->Open(); - - if (handle < 0) { - LOG_ERROR(Lib_VideoOut, "All available handles are open"); - return SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY; // it is alreadyOpened - } - - return handle; -} - -s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle) { - auto* videoOut = Common::Singleton::Instance(); - videoOut->Close(handle); - return SCE_OK; -} - -s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) { - BREAKPOINT(); - return 0; -} - -void videoOutRegisterLib(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutGetFlipStatus); - LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip); - LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutRegisterBuffers); - LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutAddFlipEvent); - LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutSetFlipRate); - LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutSetBufferAttribute); - LIB_FUNCTION("6kPnj51T62Y", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutGetResolutionStatus); - LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutOpen); - LIB_FUNCTION("zgXifHT9ErY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutIsFlipPending); - LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutUnregisterBuffers); - LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose); - LIB_FUNCTION("1FZBKy8HeNU", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, - sceVideoOutGetVblankStatus); - - // openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1 - LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen); - LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, - sceVideoOutSetFlipRate); - LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, - sceVideoOutAddFlipEvent); - LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, - sceVideoOutSetBufferAttribute); - LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, - sceVideoOutRegisterBuffers); - LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutSubmitFlip); - LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, - sceVideoOutGetFlipStatus); -} -} // namespace HLE::Libs::Graphics::VideoOut diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 24f84025..bc69b7a2 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -2,11 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" -#include "core/PS4/GPU/gpu_memory.h" #include "core/libraries/error_codes.h" #include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/libs.h" -#include "emulator.h" namespace Libraries::GnmDriver { @@ -293,7 +291,6 @@ int PS4_SYSV_ABI sceGnmFindResourcesPublic() { void PS4_SYSV_ABI sceGnmFlushGarlic() { LOG_WARNING(Lib_GnmDriver, "(STUBBED) called"); - GPU::flushGarlic(Emu::getGraphicCtx()); } int PS4_SYSV_ABI sceGnmGetCoredumpAddress() { diff --git a/src/core/libraries/kernel/event_queue.cpp b/src/core/libraries/kernel/event_queue.cpp index b03f270c..ab53d53c 100644 --- a/src/core/libraries/kernel/event_queue.cpp +++ b/src/core/libraries/kernel/event_queue.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/assert.h" #include "common/debug.h" #include "core/libraries/kernel/event_queue.h" @@ -11,16 +12,11 @@ EqueueInternal::~EqueueInternal() = default; int EqueueInternal::addEvent(const EqueueEvent& event) { std::scoped_lock lock{m_mutex}; - if (m_events.size() > 0) { - BREAKPOINT(); - } + ASSERT(m_events.empty()); + ASSERT(!event.isTriggered); + // TODO check if event is already exists and return it. Currently we just add in m_events array m_events.push_back(event); - - if (event.isTriggered) { - BREAKPOINT(); // we don't support that either yet - } - return 0; } @@ -33,28 +29,26 @@ int EqueueInternal::waitForEvents(SceKernelEvent* ev, int num, u32 micros) { return ret > 0; }; + char buf[128]; + pthread_getname_np(pthread_self(), buf, 128); + fmt::print("Thread {} waiting for events (micros = {})\n", buf, micros); + if (micros == 0) { m_cond.wait(lock, predicate); } else { m_cond.wait_for(lock, std::chrono::microseconds(micros), predicate); } + fmt::print("Wait done\n"); return ret; } bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) { std::scoped_lock lock{m_mutex}; - if (m_events.size() > 1) { - BREAKPOINT(); // we currently support one event - } + ASSERT(m_events.size() <= 1); + auto& event = m_events[0]; - - if (event.filter.trigger_event_func != nullptr) { - event.filter.trigger_event_func(&event, trigger_data); - } else { - event.isTriggered = true; - } - + event.trigger(trigger_data); m_cond.notify_one(); return true; @@ -63,17 +57,12 @@ bool EqueueInternal::triggerEvent(u64 ident, s16 filter, void* trigger_data) { int EqueueInternal::getTriggeredEvents(SceKernelEvent* ev, int num) { int ret = 0; - if (m_events.size() > 1) { - BREAKPOINT(); // we currently support one event - } + ASSERT(m_events.size() <= 1); auto& event = m_events[0]; if (event.isTriggered) { ev[ret++] = event.event; - - if (event.filter.reset_event_func != nullptr) { - event.filter.reset_event_func(&event); - } + event.reset(); } return ret; diff --git a/src/core/libraries/kernel/event_queue.h b/src/core/libraries/kernel/event_queue.h index e825a5c4..12151a0c 100644 --- a/src/core/libraries/kernel/event_queue.h +++ b/src/core/libraries/kernel/event_queue.h @@ -52,15 +52,24 @@ struct SceKernelEvent { struct Filter { void* data = nullptr; - TriggerFunc trigger_event_func = nullptr; - ResetFunc reset_event_func = nullptr; - DeleteFunc delete_event_func = nullptr; }; struct EqueueEvent { bool isTriggered = false; SceKernelEvent event; Filter filter; + + void reset() { + isTriggered = false; + event.fflags = 0; + event.data = 0; + } + + void trigger(void* data) { + isTriggered = true; + event.fflags++; + event.data = reinterpret_cast(data); + } }; class EqueueInternal { diff --git a/src/core/libraries/kernel/memory_management.cpp b/src/core/libraries/kernel/memory_management.cpp index 384312fb..91b64446 100644 --- a/src/core/libraries/kernel/memory_management.cpp +++ b/src/core/libraries/kernel/memory_management.cpp @@ -2,10 +2,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/singleton.h" -#include "core/PS4/GPU/gpu_memory.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/memory_management.h" #include "core/libraries/kernel/physical_memory.h" @@ -13,10 +13,6 @@ namespace Libraries::Kernel { -bool is16KBAligned(u64 n) { - return ((n % (16ull * 1024) == 0)); -} - u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() { LOG_WARNING(Kernel_Vmm, "called"); return SCE_KERNEL_MAIN_DMEM_SIZE; @@ -34,11 +30,11 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u return SCE_KERNEL_ERROR_EINVAL; } const bool is_in_range = (searchStart < len && searchEnd > len); - if (len <= 0 || !is16KBAligned(len) || !is_in_range) { + if (len <= 0 || !Common::is16KBAligned(len) || !is_in_range) { LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } - if ((alignment != 0 || is16KBAligned(alignment)) && !std::has_single_bit(alignment)) { + if ((alignment != 0 || Common::is16KBAligned(alignment)) && !std::has_single_bit(alignment)) { LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } @@ -66,23 +62,22 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl "len = {:#x}, prot = {:#x}, flags = {:#x}, directMemoryStart = {:#x}, alignment = {:#x}", len, prot, flags, directMemoryStart, alignment); - if (len == 0 || !is16KBAligned(len)) { + if (len == 0 || !Common::is16KBAligned(len)) { LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!"); return SCE_KERNEL_ERROR_EINVAL; } - if (!is16KBAligned(directMemoryStart)) { + if (!Common::is16KBAligned(directMemoryStart)) { LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!"); return SCE_KERNEL_ERROR_EINVAL; } if (alignment != 0) { - if ((!std::has_single_bit(alignment) && !is16KBAligned(alignment))) { + if ((!std::has_single_bit(alignment) && !Common::is16KBAligned(alignment))) { LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!"); return SCE_KERNEL_ERROR_EINVAL; } } VirtualMemory::MemoryMode cpu_mode = VirtualMemory::MemoryMode::NoAccess; - GPU::MemoryMode gpu_mode = GPU::MemoryMode::NoAccess; switch (prot) { case 0x03: @@ -91,7 +86,6 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl case 0x32: case 0x33: // SCE_KERNEL_PROT_CPU_READ|SCE_KERNEL_PROT_CPU_WRITE|SCE_KERNEL_PROT_GPU_READ|SCE_KERNEL_PROT_GPU_ALL cpu_mode = VirtualMemory::MemoryMode::ReadWrite; - gpu_mode = GPU::MemoryMode::ReadWrite; break; default: UNREACHABLE(); @@ -112,13 +106,10 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl } auto* physical_memory = Common::Singleton::Instance(); - if (!physical_memory->Map(out_addr, directMemoryStart, len, prot, cpu_mode, gpu_mode)) { + if (!physical_memory->Map(out_addr, directMemoryStart, len, prot, cpu_mode)) { UNREACHABLE(); } - if (gpu_mode != GPU::MemoryMode::NoAccess) { - GPU::memorySetAllocArea(out_addr, len); - } return SCE_OK; } diff --git a/src/core/libraries/kernel/physical_memory.cpp b/src/core/libraries/kernel/physical_memory.cpp index 0ec1c5fa..82884a63 100644 --- a/src/core/libraries/kernel/physical_memory.cpp +++ b/src/core/libraries/kernel/physical_memory.cpp @@ -1,20 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/alignment.h" #include "core/libraries/kernel/physical_memory.h" namespace Libraries::Kernel { -static u64 AlignUp(u64 pos, u64 align) { - return (align != 0 ? (pos + (align - 1)) & ~(align - 1) : pos); -} - bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut, int memoryType) { std::scoped_lock lock{m_mutex}; u64 find_free_pos = 0; - // iterate through allocated blocked and find the next free position + // Iterate through allocated blocked and find the next free position for (const auto& block : m_allocatedBlocks) { u64 n = block.start_addr + block.size; if (n > find_free_pos) { @@ -22,16 +19,15 @@ bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignmen } } - // align free position - find_free_pos = AlignUp(find_free_pos, alignment); + // Align free position + find_free_pos = Common::alignUp(find_free_pos, alignment); - // if the new position is between searchStart - searchEnd , allocate a new block + // If the new position is between searchStart - searchEnd , allocate a new block if (find_free_pos >= searchStart && find_free_pos + len <= searchEnd) { AllocatedBlock block{}; block.size = len; block.start_addr = find_free_pos; block.memoryType = memoryType; - block.gpu_mode = GPU::MemoryMode::NoAccess; block.map_size = 0; block.map_virtual_addr = 0; block.prot = 0; @@ -47,7 +43,7 @@ bool PhysicalMemory::Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignmen } bool PhysicalMemory::Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, - VirtualMemory::MemoryMode cpu_mode, GPU::MemoryMode gpu_mode) { + VirtualMemory::MemoryMode cpu_mode) { std::scoped_lock lock{m_mutex}; for (auto& b : m_allocatedBlocks) { if (phys_addr >= b.start_addr && phys_addr < b.start_addr + b.size) { @@ -59,7 +55,6 @@ bool PhysicalMemory::Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, b.map_size = len; b.prot = prot; b.cpu_mode = cpu_mode; - b.gpu_mode = gpu_mode; return true; } diff --git a/src/core/libraries/kernel/physical_memory.h b/src/core/libraries/kernel/physical_memory.h index 65f42167..27ef0666 100644 --- a/src/core/libraries/kernel/physical_memory.h +++ b/src/core/libraries/kernel/physical_memory.h @@ -6,7 +6,6 @@ #include #include #include "common/types.h" -#include "core/PS4/GPU/gpu_memory.h" #include "core/virtual_memory.h" namespace Libraries::Kernel { @@ -21,7 +20,6 @@ public: u64 map_size; int prot; VirtualMemory::MemoryMode cpu_mode; - GPU::MemoryMode gpu_mode; }; PhysicalMemory() {} virtual ~PhysicalMemory() {} @@ -29,8 +27,8 @@ public: public: bool Alloc(u64 searchStart, u64 searchEnd, u64 len, u64 alignment, u64* physAddrOut, int memoryType); - bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, VirtualMemory::MemoryMode cpu_mode, - GPU::MemoryMode gpu_mode); + bool Map(u64 virtual_addr, u64 phys_addr, u64 len, int prot, + VirtualMemory::MemoryMode cpu_mode); private: std::vector m_allocatedBlocks; diff --git a/src/core/libraries/libc/libc.cpp b/src/core/libraries/libc/libc.cpp index fc2e14ce..5de84738 100644 --- a/src/core/libraries/libc/libc.cpp +++ b/src/core/libraries/libc/libc.cpp @@ -13,7 +13,7 @@ #include "core/libraries/libc/libc_string.h" #include "core/libraries/libs.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { constexpr bool log_file_libc = true; // disable it to disable logging static u32 g_need_sceLibc = 1; @@ -421,7 +421,7 @@ const PS4_SYSV_ABI u16* ps4__Getpctype() { return &characterTypeTable[0]; } -void libcSymbolsRegister(Loader::SymbolsResolver* sym) { +void libcSymbolsRegister(Core::Loader::SymbolsResolver* sym) { // cxa functions LIB_FUNCTION("3GPpjQdAMTw", "libc", 1, "libc", 1, 1, ps4___cxa_guard_acquire); LIB_FUNCTION("9rAeANT2tyE", "libc", 1, "libc", 1, 1, ps4___cxa_guard_release); @@ -490,4 +490,4 @@ void libcSymbolsRegister(Loader::SymbolsResolver* sym) { LIB_FUNCTION("sUP1hBaouOw", "libc", 1, "libc", 1, 1, ps4__Getpctype); } -}; // namespace Core::Libraries::LibC +}; // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc.h b/src/core/libraries/libc/libc.h index c740feaf..6504b8d7 100644 --- a/src/core/libraries/libc/libc.h +++ b/src/core/libraries/libc/libc.h @@ -7,8 +7,8 @@ namespace Core::Loader { class SymbolsResolver; } -namespace Core::Libraries::LibC { +namespace Libraries::LibC { -void libcSymbolsRegister(Loader::SymbolsResolver* sym); +void libcSymbolsRegister(Core::Loader::SymbolsResolver* sym); -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_cxa.cpp b/src/core/libraries/libc/libc_cxa.cpp index eb6aaa4e..ff10d3f0 100644 --- a/src/core/libraries/libc/libc_cxa.cpp +++ b/src/core/libraries/libc/libc_cxa.cpp @@ -7,7 +7,7 @@ // adapted from // https://opensource.apple.com/source/libcppabi/libcppabi-14/src/cxa_guard.cxx.auto.html -namespace Core::Libraries::LibC { +namespace Libraries::LibC { // This file implements the __cxa_guard_* functions as defined at: // http://www.codesourcery.com/public/cxx-abi/abi.html @@ -158,4 +158,4 @@ void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object) { setNotInUse(guard_object); } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_cxa.h b/src/core/libraries/libc/libc_cxa.h index e33dd32f..2594493e 100644 --- a/src/core/libraries/libc/libc_cxa.h +++ b/src/core/libraries/libc/libc_cxa.h @@ -6,10 +6,10 @@ #include #include "common/types.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { int PS4_SYSV_ABI ps4___cxa_guard_acquire(u64* guard_object); void PS4_SYSV_ABI ps4___cxa_guard_release(u64* guard_object); void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object); -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_math.cpp b/src/core/libraries/libc/libc_math.cpp index 935fc82d..89acb4d7 100644 --- a/src/core/libraries/libc/libc_math.cpp +++ b/src/core/libraries/libc/libc_math.cpp @@ -4,7 +4,7 @@ #include #include "core/libraries/libc/libc_math.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { float PS4_SYSV_ABI ps4_atan2f(float y, float x) { return atan2f(y, x); @@ -38,4 +38,4 @@ double PS4_SYSV_ABI ps4_exp2(double arg) { return exp2(arg); } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_math.h b/src/core/libraries/libc/libc_math.h index 85633bcb..3f49e6a5 100644 --- a/src/core/libraries/libc/libc_math.h +++ b/src/core/libraries/libc/libc_math.h @@ -5,7 +5,7 @@ #include "common/types.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { float PS4_SYSV_ABI ps4_atan2f(float y, float x); float PS4_SYSV_ABI ps4_acosf(float num); @@ -16,4 +16,4 @@ double PS4_SYSV_ABI ps4__Sin(double x); float PS4_SYSV_ABI ps4__Fsin(float arg); double PS4_SYSV_ABI ps4_exp2(double arg); -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_stdio.cpp b/src/core/libraries/libc/libc_stdio.cpp index 97715025..abbe072c 100644 --- a/src/core/libraries/libc/libc_stdio.cpp +++ b/src/core/libraries/libc/libc_stdio.cpp @@ -6,7 +6,7 @@ #include "core/file_sys/fs.h" #include "core/libraries/libc/libc_stdio.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode) { auto* mnt = Common::Singleton::Instance(); @@ -70,4 +70,4 @@ int PS4_SYSV_ABI ps4_puts(const char* s) { return std::puts(s); } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_stdio.h b/src/core/libraries/libc/libc_stdio.h index a14e2f67..15cbe06a 100644 --- a/src/core/libraries/libc/libc_stdio.h +++ b/src/core/libraries/libc/libc_stdio.h @@ -6,7 +6,7 @@ #include "common/types.h" #include "core/libraries/libc/printf.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode); int PS4_SYSV_ABI ps4_printf(VA_ARGS); @@ -19,4 +19,4 @@ int PS4_SYSV_ABI ps4_fseek(FILE* stream, long offset, int whence); int PS4_SYSV_ABI ps4_fgetpos(FILE* stream, fpos_t* pos); std::size_t PS4_SYSV_ABI ps4_fread(void* ptr, size_t size, size_t nmemb, FILE* stream); -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_stdlib.cpp b/src/core/libraries/libc/libc_stdlib.cpp index 2d0e4b63..e7183878 100644 --- a/src/core/libraries/libc/libc_stdlib.cpp +++ b/src/core/libraries/libc/libc_stdlib.cpp @@ -5,7 +5,7 @@ #include "common/assert.h" #include "core/libraries/libc/libc_stdlib.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { void PS4_SYSV_ABI ps4_exit(int code) { std::exit(code); @@ -42,4 +42,4 @@ int PS4_SYSV_ABI ps4_rand() { return std::rand(); } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_stdlib.h b/src/core/libraries/libc/libc_stdlib.h index 872e7a59..4bb5f6c1 100644 --- a/src/core/libraries/libc/libc_stdlib.h +++ b/src/core/libraries/libc/libc_stdlib.h @@ -6,7 +6,7 @@ #include #include "common/types.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { void PS4_SYSV_ABI ps4_exit(int code); int PS4_SYSV_ABI ps4_atexit(void (*func)()); @@ -16,4 +16,4 @@ void PS4_SYSV_ABI ps4_qsort(void* ptr, size_t count, size_t size, int(PS4_SYSV_ABI* comp)(const void*, const void*)); int PS4_SYSV_ABI ps4_rand(); -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_string.cpp b/src/core/libraries/libc/libc_string.cpp index 71ee9ff8..af7524e6 100644 --- a/src/core/libraries/libc/libc_string.cpp +++ b/src/core/libraries/libc/libc_string.cpp @@ -5,7 +5,7 @@ #include #include "core/libraries/libc/libc_string.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { int PS4_SYSV_ABI ps4_memcmp(const void* s1, const void* s2, size_t n) { return std::memcmp(s1, s2, n); @@ -58,4 +58,4 @@ char* PS4_SYSV_ABI ps4_strdup(const char* str1) { return dup; } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/libc_string.h b/src/core/libraries/libc/libc_string.h index d446146b..3e575fa3 100644 --- a/src/core/libraries/libc/libc_string.h +++ b/src/core/libraries/libc/libc_string.h @@ -6,7 +6,7 @@ #include #include "common/types.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { int PS4_SYSV_ABI ps4_memcmp(const void* s1, const void* s2, size_t n); void* PS4_SYSV_ABI ps4_memcpy(void* dest, const void* src, size_t n); @@ -21,4 +21,4 @@ char* PS4_SYSV_ABI ps4_strrchr(const char* s, int c); int PS4_SYSV_ABI ps4_strncmp(const char* s1, const char* s2, size_t n); char* PS4_SYSV_ABI ps4_strdup(const char* str1); -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/printf.h b/src/core/libraries/libc/printf.h index d63e649c..e8465427 100644 --- a/src/core/libraries/libc/printf.h +++ b/src/core/libraries/libc/printf.h @@ -64,7 +64,7 @@ #include "va_ctx.h" -namespace Core::Libraries::LibC { +namespace Libraries::LibC { // ntoa conversion buffer size, this must be big enough to hold // one converted numeric number including padded zeros (dynamically created on stack) // 32 byte is a good default @@ -750,4 +750,4 @@ static int vsnprintf_ctx(char* s, size_t n, const char* format, VaList* arg) { std::strcpy(s, buffer); return result; } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libc/va_ctx.h b/src/core/libraries/libc/va_ctx.h index 8f7d7164..1c031495 100644 --- a/src/core/libraries/libc/va_ctx.h +++ b/src/core/libraries/libc/va_ctx.h @@ -30,7 +30,7 @@ (ctx).va_list.fp_offset = offsetof(VaRegSave, fp); \ (ctx).va_list.overflow_arg_area = &overflow_arg_area; -namespace Core::Libraries::LibC { +namespace Libraries::LibC { // https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure @@ -107,4 +107,4 @@ T* vaArgPtr(VaList* l) { return vaArgOverflowArgArea(l); } -} // namespace Core::Libraries::LibC +} // namespace Libraries::LibC diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 5995a652..5e0e513a 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" -#include "core/PS4/HLE/Graphics/video_out.h" #include "core/libraries/audio/audioin.h" #include "core/libraries/audio/audioout.h" #include "core/libraries/gnmdriver/gnmdriver.h" @@ -22,16 +21,17 @@ #include "core/libraries/system/sysmodule.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" +#include "core/libraries/videoout/video_out.h" namespace Libraries { void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Kernel::LibKernel_Register(sym); - HLE::Libs::Graphics::VideoOut::videoOutRegisterLib(sym); + Libraries::VideoOut::RegisterLib(sym); Libraries::GnmDriver::RegisterlibSceGnmDriver(sym); Libraries::LibPad::padSymbolsRegister(sym); if (!Config::isLleLibc()) { - Core::Libraries::LibC::libcSymbolsRegister(sym); + Libraries::LibC::libcSymbolsRegister(sym); } // New libraries folder from autogen diff --git a/src/core/libraries/videoout/buffer.h b/src/core/libraries/videoout/buffer.h new file mode 100644 index 00000000..88dad852 --- /dev/null +++ b/src/core/libraries/videoout/buffer.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/assert.h" +#include "common/types.h" + +namespace Libraries::VideoOut { + +constexpr std::size_t MaxDisplayBuffers = 16; +constexpr std::size_t MaxDisplayBufferGroups = 4; + +enum class PixelFormat : u32 { + Unknown, + A8R8G8B8Srgb = 0x80000000, + A8B8G8R8Srgb = 0x80002200, + A2R10G10B10 = 0x88060000, + A2R10G10B10Srgb = 0x88000000, + A2R10G10B10Bt2020Pq = 0x88740000, + A16R16G16B16Float = 0xC1060000, +}; + +enum class TilingMode : s32 { + Tile = 0, + Linear = 1, +}; + +constexpr std::string_view GetPixelFormatString(PixelFormat format) { + switch (format) { + case PixelFormat::A8R8G8B8Srgb: + return "A8R8G8B8Srgb"; + case PixelFormat::A8B8G8R8Srgb: + return "A8B8G8R8Srgb"; + case PixelFormat::A2R10G10B10: + return "A2R10G10B10"; + case PixelFormat::A2R10G10B10Srgb: + return "A2R10G10B10Srgb"; + case PixelFormat::A2R10G10B10Bt2020Pq: + return "A2R10G10B10Bt2020Pq"; + case PixelFormat::A16R16G16B16Float: + return "A16R16G16B16Float"; + default: + UNREACHABLE_MSG("Unknown pixel format {}", static_cast(format)); + return ""; + } +} + +struct BufferAttribute { + PixelFormat pixel_format; + TilingMode tiling_mode; + s32 aspect_ratio; + u32 width; + u32 height; + u32 pitch_in_pixel; + u32 option; + u32 reserved0; + u64 reserved1; +}; + +struct BufferAttributeGroup { + bool is_occupied; + BufferAttribute attrib; + u32 size_in_bytes; +}; + +struct VideoOutBuffer { + s32 group_index{-1}; + uintptr_t address_left; + uintptr_t address_right; +}; + +} // namespace Libraries::VideoOut diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp new file mode 100644 index 00000000..e1a8b0e9 --- /dev/null +++ b/src/core/libraries/videoout/driver.cpp @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/assert.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/kernel/time_management.h" +#include "core/libraries/videoout/driver.h" + +#include "video_core/renderer_vulkan/renderer_vulkan.h" + +extern Frontend::WindowSDL* g_window; + +namespace Libraries::VideoOut { + +constexpr static bool Is32BppPixelFormat(PixelFormat format) { + switch (format) { + case PixelFormat::A8R8G8B8Srgb: + case PixelFormat::A8B8G8R8Srgb: + case PixelFormat::A2R10G10B10: + case PixelFormat::A2R10G10B10Srgb: + case PixelFormat::A2R10G10B10Bt2020Pq: + return true; + default: + return false; + } +} + +constexpr u32 PixelFormatBpp(PixelFormat pixel_format) { + switch (pixel_format) { + case PixelFormat::A16R16G16B16Float: + return 8; + default: + return 4; + } +} + +VideoOutDriver::VideoOutDriver(u32 width, u32 height) { + main_port.resolution.fullWidth = width; + main_port.resolution.fullHeight = height; + main_port.resolution.paneWidth = width; + main_port.resolution.paneHeight = height; + + renderer = std::make_unique(*g_window); +} + +VideoOutDriver::~VideoOutDriver() = default; + +int VideoOutDriver::Open(const ServiceThreadParams* params) { + std::scoped_lock lock{mutex}; + + if (main_port.is_open) { + return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY; + } + + int handle = 1; + main_port.is_open = true; + return handle; +} + +void VideoOutDriver::Close(s32 handle) { + std::scoped_lock lock{mutex}; + + main_port.is_open = false; + main_port.flip_rate = 0; + ASSERT(main_port.flip_events.empty()); +} + +VideoOutPort* VideoOutDriver::GetPort(int handle) { + if (handle != 1) [[unlikely]] { + return nullptr; + } + return &main_port; +} + +int VideoOutDriver::RegisterBuffers(VideoOutPort* port, s32 startIndex, void* const* addresses, + s32 bufferNum, const BufferAttribute* attribute) { + const s32 group_index = port->FindFreeGroup(); + if (group_index >= MaxDisplayBufferGroups) { + return ORBIS_VIDEO_OUT_ERROR_NO_EMPTY_SLOT; + } + + if (startIndex + bufferNum > MaxDisplayBuffers || startIndex > MaxDisplayBuffers || + bufferNum > MaxDisplayBuffers) { + LOG_ERROR(Lib_VideoOut, + "Attempted to register too many buffers startIndex = {}, bufferNum = {}", + startIndex, bufferNum); + return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE; + } + + const s32 end_index = startIndex + bufferNum; + if (bufferNum > 0 && + std::any_of(port->buffer_slots.begin() + startIndex, port->buffer_slots.begin() + end_index, + [](auto& buffer) { return buffer.group_index != -1; })) { + return ORBIS_VIDEO_OUT_ERROR_SLOT_OCCUPIED; + } + + if (attribute->reserved0 != 0 || attribute->reserved1 != 0) { + LOG_ERROR(Lib_VideoOut, "Invalid reserved memebers"); + return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE; + } + if (attribute->aspect_ratio != 0) { + LOG_ERROR(Lib_VideoOut, "Invalid aspect ratio = {}", attribute->aspect_ratio); + return ORBIS_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO; + } + if (attribute->width > attribute->pitch_in_pixel) { + LOG_ERROR(Lib_VideoOut, "Buffer width {} is larger than pitch {}", attribute->width, + attribute->pitch_in_pixel); + return ORBIS_VIDEO_OUT_ERROR_INVALID_PITCH; + } + if (attribute->tiling_mode < TilingMode::Tile || attribute->tiling_mode > TilingMode::Linear) { + LOG_ERROR(Lib_VideoOut, "Invalid tilingMode = {}", + static_cast(attribute->tiling_mode)); + return ORBIS_VIDEO_OUT_ERROR_INVALID_TILING_MODE; + } + + LOG_INFO(Lib_VideoOut, + "startIndex = {}, bufferNum = {}, pixelFormat = {}, aspectRatio = {}, " + "tilingMode = {}, width = {}, height = {}, pitchInPixel = {}, option = {:#x}", + startIndex, bufferNum, GetPixelFormatString(attribute->pixel_format), + attribute->aspect_ratio, static_cast(attribute->tiling_mode), attribute->width, + attribute->height, attribute->pitch_in_pixel, attribute->option); + + auto& group = port->groups[group_index]; + std::memcpy(&group.attrib, attribute, sizeof(BufferAttribute)); + group.size_in_bytes = + attribute->height * attribute->pitch_in_pixel * PixelFormatBpp(attribute->pixel_format); + group.is_occupied = true; + + for (u32 i = 0; i < bufferNum; i++) { + const uintptr_t address = reinterpret_cast(addresses[i]); + port->buffer_slots[startIndex + i] = VideoOutBuffer{ + .group_index = group_index, + .address_left = address, + .address_right = 0, + }; + + LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex, address); + } + + return group_index; +} + +int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { + if (attributeIndex >= MaxDisplayBufferGroups || !port->groups[attributeIndex].is_occupied) { + LOG_ERROR(Lib_VideoOut, "Invalid attribute index {}", attributeIndex); + return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE; + } + + auto& group = port->groups[attributeIndex]; + group.is_occupied = false; + + for (auto& buffer : port->buffer_slots) { + if (buffer.group_index != attributeIndex) { + continue; + } + buffer.group_index = -1; + } + + return ORBIS_OK; +} + +void VideoOutDriver::Flip(std::chrono::microseconds timeout) { + Request req; + { + std::unique_lock lock{mutex}; + submit_cond.wait_for(lock, timeout, [&] { return !requests.empty(); }); + if (requests.empty()) { + return; + } + + // Retrieve the request. + req = requests.front(); + requests.pop(); + } + + // Present the frame. + renderer->Present(req.frame); + + std::scoped_lock lock{mutex}; + + // Update flip status. + auto& flip_status = req.port->flip_status; + flip_status.count++; + flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); + flip_status.tsc = Libraries::Kernel::sceKernelReadTsc(); + flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc(); + flip_status.flipArg = req.flip_arg; + flip_status.currentBuffer = req.index; + flip_status.flipPendingNum = static_cast(requests.size()); + + // Trigger flip events for the port. + for (auto& event : req.port->flip_events) { + if (event != nullptr) { + event->triggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Kernel::EVFILT_VIDEO_OUT, + reinterpret_cast(req.flip_arg)); + } + } +} + +bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg) { + const auto& buffer = port->buffer_slots[index]; + const auto& group = port->groups[buffer.group_index]; + auto* frame = renderer->PrepareFrame(group, buffer.address_left); + + std::scoped_lock lock{mutex}; + + if (requests.size() >= 2) { + return false; + } + + requests.push({ + .frame = frame, + .port = port, + .index = index, + .flip_arg = flip_arg, + .submit_tsc = Libraries::Kernel::sceKernelReadTsc(), + }); + + port->flip_status.flipPendingNum = static_cast(requests.size()); + port->flip_status.gcQueueNum = 0; + submit_cond.notify_one(); + + return true; +} + +void VideoOutDriver::Vblank() { + std::scoped_lock lock{mutex}; + + auto& vblank_status = main_port.vblank_status; + vblank_status.count++; + vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime(); + vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc(); +} + +} // namespace Libraries::VideoOut diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h new file mode 100644 index 00000000..fac12135 --- /dev/null +++ b/src/core/libraries/videoout/driver.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "core/libraries/videoout/video_out.h" + +namespace Vulkan { +struct Frame; +class RendererVulkan; +} // namespace Vulkan + +namespace Libraries::VideoOut { + +struct VideoOutPort { + bool is_open = false; + SceVideoOutResolutionStatus resolution; + std::array buffer_slots; + std::array groups; + FlipStatus flip_status; + SceVideoOutVblankStatus vblank_status; + std::vector flip_events; + int flip_rate = 0; + + s32 FindFreeGroup() const { + s32 index = 0; + while (index < groups.size() && groups[index].is_occupied) { + index++; + } + return index; + } +}; + +struct ServiceThreadParams { + u32 unknown; + bool set_priority; + u32 priority; + bool set_affinity; + u64 affinity; +}; + +class VideoOutDriver { +public: + explicit VideoOutDriver(u32 width, u32 height); + ~VideoOutDriver(); + + int Open(const ServiceThreadParams* params); + void Close(s32 handle); + + VideoOutPort* GetPort(s32 handle); + + int RegisterBuffers(VideoOutPort* port, s32 startIndex, void* const* addresses, s32 bufferNum, + const BufferAttribute* attribute); + int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex); + + void Flip(std::chrono::microseconds timeout); + bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg); + + void Vblank(); + +private: + struct Request { + Vulkan::Frame* frame; + VideoOutPort* port; + s32 index; + s64 flip_arg; + u64 submit_tsc; + }; + + std::mutex mutex; + VideoOutPort main_port{}; + std::condition_variable_any submit_cond; + std::condition_variable done_cond; + std::queue requests; + std::unique_ptr renderer; + bool is_neo{}; +}; + +} // namespace Libraries::VideoOut diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp new file mode 100644 index 00000000..d60aaaa9 --- /dev/null +++ b/src/core/libraries/videoout/video_out.cpp @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/config.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/system/userservice.h" +#include "core/libraries/videoout/driver.h" +#include "core/libraries/videoout/video_out.h" +#include "core/loader/symbols_resolver.h" + +namespace Libraries::VideoOut { + +static std::unique_ptr driver; + +void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat, + u32 tilingMode, u32 aspectRatio, u32 width, + u32 height, u32 pitchInPixel) { + LOG_INFO(Lib_VideoOut, + "pixelFormat = {}, tilingMode = {}, aspectRatio = {}, width = {}, height = {}, " + "pitchInPixel = {}", + GetPixelFormatString(pixelFormat), tilingMode, aspectRatio, width, height, + pitchInPixel); + + std::memset(attribute, 0, sizeof(BufferAttribute)); + attribute->pixel_format = static_cast(pixelFormat); + attribute->tiling_mode = static_cast(tilingMode); + attribute->aspect_ratio = aspectRatio; + attribute->width = width; + attribute->height = height; + attribute->pitch_in_pixel = pitchInPixel; + attribute->option = SCE_VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE; +} + +s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata) { + LOG_INFO(Lib_VideoOut, "handle = {}", handle); + + auto* port = driver->GetPort(handle); + if (port == nullptr) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + if (eq == nullptr) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; + } + + Kernel::EqueueEvent event{}; + event.isTriggered = false; + event.event.ident = SCE_VIDEO_OUT_EVENT_FLIP; + event.event.filter = Kernel::EVFILT_VIDEO_OUT; + event.event.udata = udata; + event.event.fflags = 0; + event.event.data = 0; + event.filter.data = port; + + port->flip_events.push_back(eq); + return eq->addEvent(event); +} + +s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, + s32 bufferNum, const BufferAttribute* attribute) { + if (!addresses || !attribute) { + LOG_ERROR(Lib_VideoOut, "Addresses are null"); + return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; + } + + auto* port = driver->GetPort(handle); + if (!port || !port->is_open) { + LOG_ERROR(Lib_VideoOut, "Invalid handle = {}", handle); + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + return driver->RegisterBuffers(port, startIndex, addresses, bufferNum, attribute); +} + +s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) { + LOG_INFO(Lib_VideoOut, "called"); + driver->GetPort(handle)->flip_rate = rate; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) { + LOG_INFO(Lib_VideoOut, "called"); + s32 pending = driver->GetPort(handle)->flip_status.flipPendingNum; + return pending; +} + +s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) { + auto* port = driver->GetPort(handle); + if (!port) { + LOG_ERROR(Lib_VideoOut, "Invalid handle = {}", handle); + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + if (flipMode != 1) { + LOG_WARNING(Lib_VideoOut, "flipmode = {}", flipMode); + } + + ASSERT_MSG(bufferIndex != -1, "Blank output not supported"); + + if (bufferIndex < -1 || bufferIndex > 15) { + LOG_ERROR(Lib_VideoOut, "Invalid bufferIndex = {}", bufferIndex); + return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX; + } + + if (port->buffer_slots[bufferIndex].group_index < 0) { + LOG_ERROR(Lib_VideoOut, "Slot in bufferIndex = {} is not registered", bufferIndex); + return ORBIS_VIDEO_OUT_ERROR_INVALID_INDEX; + } + + LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode, + flipArg); + + if (!driver->SubmitFlip(port, bufferIndex, flipArg)) { + LOG_ERROR(Lib_VideoOut, "Flip queue is full"); + return ORBIS_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; + } + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) { + if (!status) { + LOG_ERROR(Lib_VideoOut, "Flip status is null"); + return ORBIS_VIDEO_OUT_ERROR_INVALID_ADDRESS; + } + + auto* port = driver->GetPort(handle); + if (!port) { + LOG_ERROR(Lib_VideoOut, "Invalid port handle = {}", handle); + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + *status = port->flip_status; + + LOG_INFO(Lib_VideoOut, + "count = {}, processTime = {}, tsc = {}, submitTsc = {}, flipArg = {}, gcQueueNum = " + "{}, flipPendingNum = {}, currentBuffer = {}", + status->count, status->processTime, status->tsc, status->submitTsc, status->flipArg, + status->gcQueueNum, status->flipPendingNum, status->currentBuffer); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) { + LOG_INFO(Lib_VideoOut, "called"); + *status = driver->GetPort(handle)->resolution; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, + const void* param) { + LOG_INFO(Lib_VideoOut, "called"); + ASSERT(userId == UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || userId == 0); + ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN); + ASSERT(param == nullptr); + + if (index != 0) { + LOG_ERROR(Lib_VideoOut, "Index != 0"); + return ORBIS_VIDEO_OUT_ERROR_INVALID_VALUE; + } + + auto* params = reinterpret_cast(param); + int handle = driver->Open(params); + + if (handle < 0) { + LOG_ERROR(Lib_VideoOut, "All available handles are open"); + return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY; + } + + return handle; +} + +s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle) { + driver->Close(handle); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) { + auto* port = driver->GetPort(handle); + if (!port || !port->is_open) { + return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE; + } + + return driver->UnregisterBuffers(port, attributeIndex); +} + +void Flip(std::chrono::microseconds micros) { + return driver->Flip(micros); +} + +void Vblank() { + return driver->Vblank(); +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + driver = std::make_unique(Config::getScreenWidth(), Config::getScreenHeight()); + + LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutGetFlipStatus); + LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip); + LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutRegisterBuffers); + LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutAddFlipEvent); + LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutSetFlipRate); + LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutSetBufferAttribute); + LIB_FUNCTION("6kPnj51T62Y", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutGetResolutionStatus); + LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutOpen); + LIB_FUNCTION("zgXifHT9ErY", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutIsFlipPending); + LIB_FUNCTION("N5KDtkIjjJ4", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, + sceVideoOutUnregisterBuffers); + LIB_FUNCTION("uquVH4-Du78", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutClose); + + // openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1 + LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen); + LIB_FUNCTION("CBiu4mCE1DA", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, + sceVideoOutSetFlipRate); + LIB_FUNCTION("HXzjK9yI30k", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, + sceVideoOutAddFlipEvent); + LIB_FUNCTION("i6-sR91Wt-4", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, + sceVideoOutSetBufferAttribute); + LIB_FUNCTION("w3BY+tAEiQY", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, + sceVideoOutRegisterBuffers); + LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutSubmitFlip); + LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, + sceVideoOutGetFlipStatus); +} + +} // namespace Libraries::VideoOut diff --git a/src/core/PS4/HLE/Graphics/video_out.h b/src/core/libraries/videoout/video_out.h similarity index 67% rename from src/core/PS4/HLE/Graphics/video_out.h rename to src/core/libraries/videoout/video_out.h index 5def9e75..951eb6a5 100644 --- a/src/core/PS4/HLE/Graphics/video_out.h +++ b/src/core/libraries/videoout/video_out.h @@ -3,14 +3,14 @@ #pragma once -#include #include "core/libraries/kernel/event_queues.h" +#include "core/libraries/videoout/buffer.h" namespace Core::Loader { class SymbolsResolver; } -namespace HLE::Libs::Graphics::VideoOut { +namespace Libraries::VideoOut { using SceUserServiceUserId = s32; // TODO move it to proper place @@ -46,35 +46,20 @@ enum SceVideoOutEventId : s16 { SCE_VIDEO_OUT_EVENT_PRE_VBLANK_START = 2 }; -enum SceVideoOutTilingMode : s32 { - SCE_VIDEO_OUT_TILING_MODE_TILE = 0, - SCE_VIDEO_OUT_TILING_MODE_LINEAR = 1 +enum AspectRatioMode : s32 { + SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0, }; -enum AspectRatioMode : s32 { SCE_VIDEO_OUT_ASPECT_RATIO_16_9 = 0 }; - -struct SceVideoOutBufferAttribute { - s32 pixelFormat; - s32 tilingMode; - s32 aspectRatio; - u32 width; - u32 height; - u32 pitchInPixel; - u32 option; - u32 reserved0; - u64 reserved1; -}; - -struct SceVideoOutFlipStatus { +struct FlipStatus { u64 count = 0; u64 processTime = 0; u64 tsc = 0; - s64 flipArg = 0; + s64 flipArg = -1; u64 submitTsc = 0; u64 reserved0 = 0; s32 gcQueueNum = 0; s32 flipPendingNum = 0; - s32 currentBuffer = 0; + s32 currentBuffer = -1; u32 reserved1 = 0; }; @@ -99,34 +84,24 @@ struct SceVideoOutVblankStatus { u8 pad1[7] = {}; }; -struct VideoOutBufferSetInternal { - SceVideoOutBufferAttribute attr; - int start_index = 0; - int num = 0; - int set_id = 0; -}; - -void videoOutInit(u32 width, u32 height); -std::string getPixelFormatString(s32 format); -void videoOutRegisterLib(Core::Loader::SymbolsResolver* sym); -bool videoOutFlip(u32 micros); -void VideoOutVblank(); - -void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute, - u32 pixelFormat, u32 tilingMode, u32 aspectRatio, - u32 width, u32 height, u32 pitchInPixel); -s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Libraries::Kernel::SceKernelEqueue eq, s32 handle, - void* udata); +void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, PixelFormat pixelFormat, + u32 tilingMode, u32 aspectRatio, u32 width, + u32 height, u32 pitchInPixel); +s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata); s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, - s32 bufferNum, - const SceVideoOutBufferAttribute* attribute); + s32 bufferNum, const BufferAttribute* attribute); s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate); s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle); s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg); -s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status); +s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status); s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status); s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index, const void* param); s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle); -} // namespace HLE::Libs::Graphics::VideoOut +void Flip(std::chrono::microseconds micros); +void Vblank(); + +void RegisterLib(Core::Loader::SymbolsResolver* sym); + +} // namespace Libraries::VideoOut diff --git a/src/emulator.cpp b/src/emulator.cpp deleted file mode 100644 index 70c42d15..00000000 --- a/src/emulator.cpp +++ /dev/null @@ -1,353 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/singleton.h" -#include "common/version.h" -#include "core/PS4/HLE/Graphics/graphics_render.h" -#include "core/PS4/HLE/Graphics/video_out.h" -#include "core/libraries/pad/pad.h" -#include "emulator.h" -#include "input/controller.h" -#include "vulkan_util.h" - -namespace Emu { - -bool m_emu_needs_exit = false; -bool m_game_is_paused = {false}; -double m_current_time_seconds = {0.0}; -double m_previous_time_seconds = {0.0}; -int m_update_num = {0}; -int m_frame_num = {0}; -double m_update_time_seconds = {0.0}; -double m_current_fps = {0.0}; -int m_max_updates_per_frame = {4}; -double m_update_fixed_time = 1.0 / 60.0; -int m_fps_frames_num = {0}; -double m_fps_start_time = {0}; - -void emuInit(u32 width, u32 height) { - auto window_ctx = Common::Singleton::Instance(); - window_ctx->m_graphic_ctx.screen_width = width; - window_ctx->m_graphic_ctx.screen_height = height; -} - -void checkAndWaitForGraphicsInit() { - auto window_ctx = Common::Singleton::Instance(); - std::unique_lock lock{window_ctx->m_mutex}; - - while (!window_ctx->m_is_graphic_initialized) { - window_ctx->m_graphic_initialized_cond.wait(lock); - } -} -static void CreateSdlWindow(WindowCtx* ctx) { - int width = static_cast(ctx->m_graphic_ctx.screen_width); - int height = static_cast(ctx->m_graphic_ctx.screen_height); - - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - fmt::print("{}\n", SDL_GetError()); - std::exit(0); - } - std::string title = "shadps4 v" + std::string(Common::VERSION); - SDL_PropertiesID props = SDL_CreateProperties(); - SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); - SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); - SDL_SetNumberProperty( - props, "flags", - (static_cast(SDL_WINDOW_HIDDEN) | static_cast(SDL_WINDOW_VULKAN))); - ctx->m_window = SDL_CreateWindowWithProperties(props); - SDL_DestroyProperties(props); - - ctx->is_window_hidden = - true; // hide window until we need to show something (should draw something in buffers) - - if (ctx->m_window == nullptr) { - fmt::print("{}\n", SDL_GetError()); - std::exit(0); - } - - SDL_SetWindowResizable(ctx->m_window, SDL_FALSE); // we don't support resizable atm -} -static void update() { - static double lag = 0.0; - - lag += m_current_time_seconds - m_previous_time_seconds; - - int num = 0; - - while (lag >= m_update_fixed_time) { - if (num < m_max_updates_per_frame) { - m_update_num++; - num++; - m_update_time_seconds = m_update_num * m_update_fixed_time; - } - - lag -= m_update_fixed_time; - } -} -static void calculateFps(double game_time_s) { - m_previous_time_seconds = m_current_time_seconds; - m_current_time_seconds = game_time_s; - - m_frame_num++; - - m_fps_frames_num++; - if (m_current_time_seconds - m_fps_start_time > 0.25f) { - m_current_fps = - static_cast(m_fps_frames_num) / (m_current_time_seconds - m_fps_start_time); - m_fps_frames_num = 0; - m_fps_start_time = m_current_time_seconds; - } -} -void emuRun() { - auto window_ctx = Common::Singleton::Instance(); - { - // init window and wait until init finishes - std::scoped_lock lock{window_ctx->m_mutex}; - CreateSdlWindow(window_ctx); - Graphics::Vulkan::vulkanCreate(window_ctx); - window_ctx->m_is_graphic_initialized = true; - window_ctx->m_graphic_initialized_cond.notify_one(); - calculateFps(0); // TODO: Proper fps - } - - bool exit_loop = false; - - for (;;) { - if (exit_loop) { - break; - } - - SDL_Event event; - if (SDL_PollEvent(&event) != 0) { - switch (event.type) { - case SDL_EVENT_QUIT: - m_emu_needs_exit = true; - break; - - case SDL_EVENT_TERMINATING: - m_emu_needs_exit = true; - break; - - case SDL_EVENT_WILL_ENTER_BACKGROUND: - break; - - case SDL_EVENT_DID_ENTER_BACKGROUND: - break; - - case SDL_EVENT_WILL_ENTER_FOREGROUND: - break; - - case SDL_EVENT_DID_ENTER_FOREGROUND: - break; - - case SDL_EVENT_KEY_DOWN: - case SDL_EVENT_KEY_UP: - if (event.type == SDL_EVENT_KEY_DOWN) { - if (event.key.keysym.sym == SDLK_p) { - m_game_is_paused = !m_game_is_paused; - } - } - keyboardEvent(&event); - break; - } - continue; - } - if (m_game_is_paused) { - SDL_WaitEvent(&event); - - switch (event.type) { - case SDL_EVENT_QUIT: - m_emu_needs_exit = true; - break; - - case SDL_EVENT_TERMINATING: - m_emu_needs_exit = true; - break; - - case SDL_EVENT_WILL_ENTER_BACKGROUND: - break; - - case SDL_EVENT_DID_ENTER_BACKGROUND: - break; - - case SDL_EVENT_WILL_ENTER_FOREGROUND: - break; - - case SDL_EVENT_DID_ENTER_FOREGROUND: - break; - - case SDL_EVENT_KEY_DOWN: - case SDL_EVENT_KEY_UP: - if (event.type == SDL_EVENT_KEY_DOWN) { - if (event.key.keysym.sym == SDLK_p) { - m_game_is_paused = !m_game_is_paused; - } - } - keyboardEvent(&event); - break; - } - exit_loop = m_emu_needs_exit; - continue; - } - exit_loop = m_emu_needs_exit; - if (!m_game_is_paused) { - if (!exit_loop) { - update(); - } - if (!exit_loop) { - if (HLE::Libs::Graphics::VideoOut::videoOutFlip(100000)) { // flip every 0.1 sec - calculateFps(0); // TODO: Proper fps - } - } - HLE::Libs::Graphics::VideoOut::VideoOutVblank(); - } - } -} - -HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() { - auto window_ctx = Common::Singleton::Instance(); - std::scoped_lock lock{window_ctx->m_mutex}; - return &window_ctx->m_graphic_ctx; -} - -void updateSDLTitle() { - const auto title = fmt::format("shadps4 v {} FPS: {}", Common::VERSION, m_current_fps); - auto window_ctx = Common::Singleton::Instance(); - SDL_SetWindowTitle(window_ctx->m_window, title.c_str()); -} - -void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image) { - auto window_ctx = Common::Singleton::Instance(); - if (window_ctx->is_window_hidden) { - SDL_ShowWindow(window_ctx->m_window); - window_ctx->is_window_hidden = false; - } - - window_ctx->swapchain.current_index = static_cast(-1); - - auto result = vkAcquireNextImageKHR(window_ctx->m_graphic_ctx.m_device, - window_ctx->swapchain.swapchain, UINT64_MAX, nullptr, - VK_NULL_HANDLE, &window_ctx->swapchain.current_index); - - if (result != VK_SUCCESS) { - fmt::print("Can't aquireNextImage\n"); - std::exit(0); - } - if (window_ctx->swapchain.current_index == static_cast(-1)) { - fmt::print("Unsupported:swapchain current index is -1\n"); - std::exit(0); - } - - auto blt_src_image = image; - auto blt_dst_image = window_ctx->swapchain; - - if (blt_src_image == nullptr) { - fmt::print("blt_src_image is null\n"); - std::exit(0); - } - - GPU::CommandBuffer buffer(10); - - auto* vk_buffer = buffer.getPool()->buffers[buffer.getIndex()]; - - buffer.begin(); - - Graphics::Vulkan::vulkanBlitImage(&buffer, blt_src_image, &blt_dst_image); - - VkImageMemoryBarrier pre_present_barrier{}; - pre_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - pre_present_barrier.pNext = nullptr; - pre_present_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - pre_present_barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - pre_present_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - pre_present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - pre_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - pre_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - pre_present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - pre_present_barrier.subresourceRange.baseMipLevel = 0; - pre_present_barrier.subresourceRange.levelCount = 1; - pre_present_barrier.subresourceRange.baseArrayLayer = 0; - pre_present_barrier.subresourceRange.layerCount = 1; - pre_present_barrier.image = - window_ctx->swapchain.swapchain_images[window_ctx->swapchain.current_index]; - vkCmdPipelineBarrier(vk_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, - &pre_present_barrier); - - buffer.end(); - buffer.executeWithSemaphore(); - buffer.waitForFence(); // HACK: The whole vulkan backend needs a rewrite - - VkPresentInfoKHR present{}; - present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - present.pNext = nullptr; - present.swapchainCount = 1; - present.pSwapchains = &window_ctx->swapchain.swapchain; - present.pImageIndices = &window_ctx->swapchain.current_index; - present.pWaitSemaphores = &buffer.getPool()->semaphores[buffer.getIndex()]; - present.waitSemaphoreCount = 1; - present.pResults = nullptr; - - const auto& queue = window_ctx->m_graphic_ctx.queues[10]; - - if (queue.mutex != nullptr) { - fmt::print("queue.mutexe is null\n"); - std::exit(0); - } - - result = vkQueuePresentKHR(queue.vk_queue, &present); - if (result != VK_SUCCESS) { - fmt::print("vkQueuePresentKHR failed\n"); - std::exit(0); - } - updateSDLTitle(); -} - -void keyboardEvent(SDL_Event* event) { - using Libraries::LibPad::ScePadButton; - - if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) { - u32 button = 0; - switch (event->key.keysym.sym) { - case SDLK_UP: - button = ScePadButton::UP; - break; - case SDLK_DOWN: - button = ScePadButton::DOWN; - break; - case SDLK_LEFT: - button = ScePadButton::LEFT; - break; - case SDLK_RIGHT: - button = ScePadButton::RIGHT; - break; - case SDLK_KP_8: - button = ScePadButton ::TRIANGLE; - break; - case SDLK_KP_6: - button = ScePadButton ::CIRCLE; - break; - case SDLK_KP_2: - button = ScePadButton ::CROSS; - break; - case SDLK_KP_4: - button = ScePadButton ::SQUARE; - break; - case SDLK_RETURN: - button = ScePadButton ::OPTIONS; - break; - default: - break; - } - if (button != 0) { - auto* controller = Common::Singleton::Instance(); - controller->checKButton(0, button, event->type == SDL_EVENT_KEY_DOWN); - } - } -} - -} // namespace Emu diff --git a/src/emulator.h b/src/emulator.h deleted file mode 100644 index 54193db5..00000000 --- a/src/emulator.h +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include -#include -#include - -namespace Emu { - -struct VulkanExt { - bool enable_validation_layers = false; - - char const* const* required_extensions; - u32 required_extensions_count; - std::vector available_extensions; - std::vector required_layers; - std::vector available_layers; -}; - -struct VulkanSurfaceCapabilities { - VkSurfaceCapabilitiesKHR capabilities{}; - std::vector formats; - std::vector present_modes; - bool is_format_srgb_bgra32 = false; - bool is_format_unorm_bgra32 = false; -}; - -struct VulkanQueueInfo { - u32 family = 0; - u32 index = 0; - bool is_graphics = false; - bool is_compute = false; - bool is_transfer = false; - bool is_present = false; -}; - -struct VulkanQueues { - u32 family_count = 0; - std::vector available; - std::vector graphics; - std::vector compute; - std::vector transfer; - std::vector present; - std::vector family_used; -}; - -struct VulkanSwapchain { - VkSwapchainKHR swapchain = nullptr; - VkFormat swapchain_format = VK_FORMAT_UNDEFINED; - VkExtent2D swapchain_extent = {}; - std::vector swapchain_images; - std::vector swapchain_image_views; - u32 swapchain_images_count = 0; - VkSemaphore present_complete_semaphore = nullptr; - VkFence present_complete_fence = nullptr; - u32 current_index = 0; -}; - -struct WindowCtx { - HLE::Libs::Graphics::GraphicCtx m_graphic_ctx; - std::mutex m_mutex; - bool m_is_graphic_initialized = false; - std::condition_variable m_graphic_initialized_cond; - SDL_Window* m_window = nullptr; - bool is_window_hidden = true; - VkSurfaceKHR m_surface = nullptr; - VulkanSurfaceCapabilities m_surface_capabilities; - VulkanSwapchain swapchain; -}; - -struct EmuPrivate { - EmuPrivate() = default; - std::mutex m_mutex; - HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr; - void* data1 = nullptr; - void* data2 = nullptr; - u32 m_screen_width = {0}; - u32 m_screen_height = {0}; -}; - -void emuInit(u32 width, u32 height); -void emuRun(); -void checkAndWaitForGraphicsInit(); -HLE::Libs::Graphics::GraphicCtx* getGraphicCtx(); -void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image); -void keyboardEvent(SDL_Event* event); -} // namespace Emu diff --git a/src/input/controller.cpp b/src/input/controller.cpp index c7bfe132..08be2838 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -74,7 +74,7 @@ void GameController::addState(const State& state) { m_states_num++; } -void GameController::checKButton(int id, u32 button, bool isPressed) { +void GameController::checkButton(int id, u32 button, bool isPressed) { std::scoped_lock lock{m_mutex}; auto state = getLastState(); state.time = Libraries::Kernel::sceKernelGetProcessTime(); diff --git a/src/input/controller.h b/src/input/controller.h index 6f8c2759..4819e2d7 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -23,7 +23,7 @@ public: void readState(State* state, bool* isConnected, int* connectedCount); int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount); State getLastState() const; - void checKButton(int id, u32 button, bool isPressed); + void checkButton(int id, u32 button, bool isPressed); void addState(const State& state); private: diff --git a/src/main.cpp b/src/main.cpp index c9b37fa3..086e62d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,14 +12,17 @@ #include "common/logging/log.h" #include "common/path_util.h" #include "common/singleton.h" -#include "core/PS4/HLE/Graphics/video_out.h" #include "core/file_sys/fs.h" #include "core/libraries/kernel/thread_management.h" #include "core/libraries/libc/libc.h" #include "core/libraries/libs.h" +#include "core/libraries/videoout/video_out.h" #include "core/linker.h" #include "core/tls.h" -#include "emulator.h" +#include "input/controller.h" +#include "sdl_window.h" + +Frontend::WindowSDL* g_window; int main(int argc, char* argv[]) { if (argc == 1) { @@ -31,10 +34,12 @@ int main(int argc, char* argv[]) { Common::Log::Initialize(); Common::Log::Start(); Libraries::Kernel::init_pthreads(); - auto width = Config::getScreenWidth(); - auto height = Config::getScreenHeight(); - Emu::emuInit(width, height); - HLE::Libs::Graphics::VideoOut::videoOutInit(width, height); + s32 width = Config::getScreenWidth(); + s32 height = Config::getScreenHeight(); + + auto* controller = Common::Singleton::Instance(); + Frontend::WindowSDL window{width, height, controller}; + g_window = &window; // Argument 1 is the path of self file to boot const char* const path = argv[1]; @@ -47,7 +52,8 @@ int main(int argc, char* argv[]) { Libraries::InitHLELibs(&linker->getHLESymbols()); Core::InstallTlsHandler(); linker->LoadModule(path); - // check if there is a libc.prx in sce_module folder + + // Check if there is a libc.prx in sce_module folder bool found = false; if (Config::isLleLibc()) { std::filesystem::path sce_module_folder = p.parent_path() / "sce_module"; @@ -62,16 +68,22 @@ int main(int argc, char* argv[]) { } } } - if (!found) // load HLE libc - { - Core::Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols()); + if (!found) { + Libraries::LibC::libcSymbolsRegister(&linker->getHLESymbols()); } std::jthread mainthread([linker](std::stop_token stop_token, void*) { linker->Execute(); }, nullptr); Discord::RPC discordRPC; discordRPC.init(); discordRPC.update(Discord::RPCStatus::Idling, ""); - Emu::emuRun(); + + static constexpr std::chrono::microseconds FlipPeriod{100000}; + + while (window.isOpen()) { + window.waitEvent(); + Libraries::VideoOut::Flip(FlipPeriod); + Libraries::VideoOut::Vblank(); + } discordRPC.stop(); return 0; diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp new file mode 100644 index 00000000..6e765b7f --- /dev/null +++ b/src/sdl_window.cpp @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/assert.h" +#include "common/version.h" +#include "core/libraries/pad/pad.h" +#include "input/controller.h" +#include "sdl_window.h" + +namespace Frontend { + +WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_) + : width{width_}, height{height_}, controller{controller_} { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError()); + } + + const std::string title = "shadps4 v" + std::string(Common::VERSION); + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title.c_str()); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height); + SDL_SetNumberProperty(props, "flags", SDL_WINDOW_VULKAN); + window = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (window == nullptr) { + UNREACHABLE_MSG("Failed to create window handle: {}", SDL_GetError()); + } + + // We don't support resizable at the moment. + SDL_SetWindowResizable(window, SDL_FALSE); + +#if defined(SDL_PLATFORM_WIN32) + window_info.type = WindowSystemType::Windows; + window_info.render_surface = + SDL_GetProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); +#elif defined(SDL_PLATFORM_LINUX) + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) { + window_info.type = WindowSystemType::X11; + window_info.display_connection = SDL_GetProperty(SDL_GetWindowProperties(window), + SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + window_info.render_surface = (void*)SDL_GetNumberProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + } else if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { + window_info.type = WindowSystemType::Wayland; + window_info.display_connection = SDL_GetProperty( + SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + window_info.render_surface = SDL_GetProperty(SDL_GetWindowProperties(window), + SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + } +#endif +} + +WindowSDL::~WindowSDL() = default; + +void WindowSDL::waitEvent() { + // Called on main thread + SDL_Event event; + + if (!SDL_PollEvent(&event)) { + return; + } + + switch (event.type) { + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + onResize(); + break; + case SDL_EVENT_WINDOW_MINIMIZED: + case SDL_EVENT_WINDOW_EXPOSED: + is_shown = event.type == SDL_EVENT_WINDOW_EXPOSED; + onResize(); + break; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + onKeyPress(&event); + break; + case SDL_EVENT_QUIT: + is_open = false; + break; + default: + break; + } +} + +void WindowSDL::onResize() { + SDL_GetWindowSizeInPixels(window, &width, &height); +} + +void WindowSDL::onKeyPress(const SDL_Event* event) { + using Libraries::LibPad::ScePadButton; + + u32 button = 0; + switch (event->key.keysym.sym) { + case SDLK_UP: + button = ScePadButton::UP; + break; + case SDLK_DOWN: + button = ScePadButton::DOWN; + break; + case SDLK_LEFT: + button = ScePadButton::LEFT; + break; + case SDLK_RIGHT: + button = ScePadButton::RIGHT; + break; + case SDLK_KP_8: + button = ScePadButton::TRIANGLE; + break; + case SDLK_KP_6: + button = ScePadButton::CIRCLE; + break; + case SDLK_KP_2: + button = ScePadButton::CROSS; + break; + case SDLK_KP_4: + button = ScePadButton::SQUARE; + break; + case SDLK_RETURN: + button = ScePadButton::OPTIONS; + break; + default: + break; + } + if (button != 0) { + controller->checkButton(0, button, event->type == SDL_EVENT_KEY_DOWN); + } +} + +} // namespace Frontend diff --git a/src/sdl_window.h b/src/sdl_window.h new file mode 100644 index 00000000..13ee7864 --- /dev/null +++ b/src/sdl_window.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +struct SDL_Window; +union SDL_Event; + +namespace Input { +class GameController; +} + +namespace Frontend { + +enum class WindowSystemType : u8 { + Headless, + Windows, + X11, + Wayland, +}; + +struct WindowSystemInfo { + // Window system type. Determines which GL context or Vulkan WSI is used. + WindowSystemType type = WindowSystemType::Headless; + + // Connection to a display server. This is used on X11 and Wayland platforms. + void* display_connection = nullptr; + + // Render surface. This is a pointer to the native window handle, which depends + // on the platform. e.g. HWND for Windows, Window for X11. If the surface is + // set to nullptr, the video backend will run in headless mode. + void* render_surface = nullptr; + + // Scale of the render surface. For hidpi systems, this will be >1. + float render_surface_scale = 1.0f; +}; + +class WindowSDL { +public: + explicit WindowSDL(s32 width, s32 height, Input::GameController* controller); + ~WindowSDL(); + + s32 getWidth() const { + return width; + } + + s32 getHeight() const { + return height; + } + + bool isOpen() const { + return is_open; + } + + WindowSystemInfo getWindowInfo() const { + return window_info; + } + + void waitEvent(); + +private: + void onResize(); + void onKeyPress(const SDL_Event* event); + +private: + s32 width; + s32 height; + Input::GameController* controller; + WindowSystemInfo window_info{}; + SDL_Window* window{}; + bool is_shown{}; + bool is_open{true}; +}; + +} // namespace Frontend diff --git a/src/video_core/pixel_format.h b/src/video_core/pixel_format.h new file mode 100644 index 00000000..cc214645 --- /dev/null +++ b/src/video_core/pixel_format.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/types.h" + +namespace VideoCore { + +// Based on Table 8.13 Data and Image Formats in Sea Islands Series Instruction Set Architecture +enum class PixelFormat : u32 { + Invalid, + R32G32B32A32_Float, + B32G32R32A32_Float, + R32G32B32X32_Float, + B32G32R32X32_Float, + R32G32B32A32_Uint, + R32G32B32A32_Sint, + R32G32B32_Float, + R32G32B32_Uint, + R32G32B32_Sint, + R16G16B16A16_Float, + R16G16B16X16_Float, + B16G16R16X16_Float, + R16G16B16A16_Uint, + R16G16B16A16_Sint, + R16G16B16A16_Unorm, + B16G16R16A16_Unorm, + R16G16B16X16_Unorm, + B16G16R16X16_Unorm, + R16G16B16A16_Snorm, + L32A32_Float, + R32G32_Float, + R32G32_Uint, + R32G32_Sint, + R11G11B10_Float, + R8G8B8A8_Unorm, + R8G8B8X8_Unorm, + R8G8B8A8_UnormSrgb, + R8G8B8X8_UnormSrgb, + R8G8B8A8_Uint, + R8G8B8A8_Snorm, + R8G8B8A8_Sint, + L16A16_Float, + R16G16_Float, + L16A16_Unorm, + R16G16_Unorm, + R16G16_Uint, + R16G16_Snorm, + R16G16_Sint, + R32_Float, + L32_Float, + A32_Float, + R32_Uint, + R32_Sint, + R8G8_Unorm, + R8G8_Uint, + R8G8_Snorm, + R8G8_Sint, + L8A8_Unorm, + L8A8_UnormSrgb, + R16_Float, + L16_Float, + A16_Float, + R16_Unorm, + L16_Unorm, + A16_Unorm, + R16_Uint, + R16_Snorm, + R16_Sint, + R8_Unorm, + L8_Unorm, + L8_UnormSrgb, + R8_Uint, + R8_Snorm, + R8_Sint, + A8_Unorm, +}; + +constexpr bool IsDepthStencilFormat(PixelFormat format) { + return false; +} + +} // namespace VideoCore diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp new file mode 100644 index 00000000..c837f808 --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -0,0 +1,354 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "sdl_window.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" + +#include + +namespace Vulkan { + +bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format format) { + const vk::FormatProperties props{physical_device.getFormatProperties(format)}; + return static_cast(props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eBlitDst); +} + +[[nodiscard]] vk::ImageSubresourceLayers MakeImageSubresourceLayers() { + return vk::ImageSubresourceLayers{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }; +} + +[[nodiscard]] vk::ImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width, + s32 swapchain_height) { + return vk::ImageBlit{ + .srcSubresource = MakeImageSubresourceLayers(), + .srcOffsets = + std::array{ + vk::Offset3D{ + .x = 0, + .y = 0, + .z = 0, + }, + vk::Offset3D{ + .x = frame_width, + .y = frame_height, + .z = 1, + }, + }, + .dstSubresource = MakeImageSubresourceLayers(), + .dstOffsets = + std::array{ + vk::Offset3D{ + .x = 0, + .y = 0, + .z = 0, + }, + vk::Offset3D{ + .x = swapchain_width, + .y = swapchain_height, + .z = 1, + }, + }, + }; +} + +RendererVulkan::RendererVulkan(Frontend::WindowSDL& window_) + : window{window_}, instance{window, 0}, scheduler{instance}, swapchain{instance, window}, + texture_cache{instance, scheduler} { + const u32 num_images = swapchain.GetImageCount(); + const vk::Device device = instance.GetDevice(); + + const vk::CommandPoolCreateInfo pool_info = { + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer | + vk::CommandPoolCreateFlagBits::eTransient, + .queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(), + }; + command_pool = device.createCommandPoolUnique(pool_info); + + const vk::CommandBufferAllocateInfo alloc_info = { + .commandPool = *command_pool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = num_images, + }; + + const auto cmdbuffers = device.allocateCommandBuffers(alloc_info); + present_frames.resize(num_images); + for (u32 i = 0; i < num_images; i++) { + Frame& frame = present_frames[i]; + frame.cmdbuf = cmdbuffers[i]; + frame.render_ready = device.createSemaphore({}); + frame.present_done = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); + free_queue.push(&frame); + } +} + +RendererVulkan::~RendererVulkan() { + scheduler.Finish(); + const vk::Device device = instance.GetDevice(); + for (auto& frame : present_frames) { + vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation); + device.destroyImageView(frame.image_view); + device.destroySemaphore(frame.render_ready); + device.destroyFence(frame.present_done); + } +} + +void RendererVulkan::RecreateFrame(Frame* frame, u32 width, u32 height) { + const vk::Device device = instance.GetDevice(); + if (frame->image_view) { + device.destroyImageView(frame->image_view); + } + if (frame->image) { + vmaDestroyImage(instance.GetAllocator(), frame->image, frame->allocation); + } + + const vk::Format format = swapchain.GetSurfaceFormat().format; + const vk::ImageCreateInfo image_info = { + .imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eTransferSrc, + }; + + const VmaAllocationCreateInfo alloc_info = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + .requiredFlags = 0, + .preferredFlags = 0, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + }; + + VkImage unsafe_image{}; + VkImageCreateInfo unsafe_image_info = static_cast(image_info); + + VkResult result = vmaCreateImage(instance.GetAllocator(), &unsafe_image_info, &alloc_info, + &unsafe_image, &frame->allocation, nullptr); + if (result != VK_SUCCESS) [[unlikely]] { + LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", + vk::to_string(vk::Result{result})); + UNREACHABLE(); + } + frame->image = vk::Image{unsafe_image}; + + const vk::ImageViewCreateInfo view_info = { + .image = frame->image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + frame->image_view = device.createImageView(view_info); + frame->width = width; + frame->height = height; +} + +Frame* RendererVulkan::PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute, + VAddr cpu_address) { + // Request presentation image from the texture cache. + auto& image = texture_cache.FindDisplayBuffer(attribute, cpu_address); + + // Request a free presentation frame. + Frame* frame = GetRenderFrame(); + + // Post-processing (Anti-aliasing, FSR etc) goes here. For now just blit to the frame image. + scheduler.Record([frame, vk_image = vk::Image(image.image), + size = image.info.size](vk::CommandBuffer cmdbuf) { + const vk::ImageMemoryBarrier pre_barrier{ + .srcAccessMask = vk::AccessFlagBits::eTransferRead, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eGeneral, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = frame->image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eTransfer, + vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier); + cmdbuf.blitImage(vk_image, vk::ImageLayout::eGeneral, frame->image, + vk::ImageLayout::eGeneral, + MakeImageBlit(size.width, size.height, frame->width, frame->height), + vk::Filter::eLinear); + }); + + // Flush pending vulkan operations. + scheduler.Flush(frame->render_ready); + scheduler.WaitWorker(); + return frame; +} + +void RendererVulkan::Present(Frame* frame) { + swapchain.AcquireNextImage(); + + const vk::Image swapchain_image = swapchain.Image(); + + const vk::CommandBufferBeginInfo begin_info = { + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, + }; + const vk::CommandBuffer cmdbuf = frame->cmdbuf; + cmdbuf.begin(begin_info); + + const vk::Extent2D extent = swapchain.GetExtent(); + const std::array pre_barriers{ + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapchain_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead, + .oldLayout = vk::ImageLayout::eGeneral, + .newLayout = vk::ImageLayout::eGeneral, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = frame->image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + }; + const vk::ImageMemoryBarrier post_barrier{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapchain_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, + {}, {}, pre_barriers); + + cmdbuf.blitImage(frame->image, vk::ImageLayout::eGeneral, swapchain_image, + vk::ImageLayout::eTransferDstOptimal, + MakeImageBlit(frame->width, frame->height, extent.width, extent.height), + vk::Filter::eLinear); + + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier); + + cmdbuf.end(); + + static constexpr std::array wait_stage_masks = { + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::PipelineStageFlagBits::eAllGraphics, + }; + + const vk::Semaphore present_ready = swapchain.GetPresentReadySemaphore(); + const vk::Semaphore image_acquired = swapchain.GetImageAcquiredSemaphore(); + const std::array wait_semaphores = {image_acquired, frame->render_ready}; + + vk::SubmitInfo submit_info = { + .waitSemaphoreCount = static_cast(wait_semaphores.size()), + .pWaitSemaphores = wait_semaphores.data(), + .pWaitDstStageMask = wait_stage_masks.data(), + .commandBufferCount = 1u, + .pCommandBuffers = &cmdbuf, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &present_ready, + }; + + try { + std::scoped_lock submit_lock{scheduler.submit_mutex}; + instance.GetGraphicsQueue().submit(submit_info, frame->present_done); + } catch (vk::DeviceLostError& err) { + LOG_CRITICAL(Render_Vulkan, "Device lost during present submit: {}", err.what()); + UNREACHABLE(); + } + + swapchain.Present(); + + // Free the frame for reuse + std::scoped_lock fl{free_mutex}; + free_queue.push(frame); + free_cv.notify_one(); +} + +Frame* RendererVulkan::GetRenderFrame() { + // Wait for free presentation frames + Frame* frame; + { + std::unique_lock lock{free_mutex}; + free_cv.wait(lock, [this] { return !free_queue.empty(); }); + + // Take the frame from the queue + frame = free_queue.front(); + free_queue.pop(); + } + + const vk::Device device = instance.GetDevice(); + vk::Result result{}; + + const auto wait = [&]() { + result = device.waitForFences(frame->present_done, false, std::numeric_limits::max()); + return result; + }; + + // Wait for the presentation to be finished so all frame resources are free + while (wait() != vk::Result::eSuccess) { + // Retry if the waiting times out + if (result == vk::Result::eTimeout) { + continue; + } + } + + // Reset fence for next queue submission. + device.resetFences(frame->present_done); + + // If the window dimentions changed, recreate this frame + if (frame->width != window.getWidth() || frame->height != window.getHeight()) { + RecreateFrame(frame, window.getWidth(), window.getHeight()); + } + + return frame; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h new file mode 100644 index 00000000..cd6ac5f3 --- /dev/null +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_swapchain.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace Frontend { +class WindowSDL; +} + +namespace Vulkan { + +struct Frame { + u32 width; + u32 height; + VmaAllocation allocation; + vk::Image image; + vk::ImageView image_view; + vk::Semaphore render_ready; + vk::Fence present_done; + vk::CommandBuffer cmdbuf; +}; + +class RendererVulkan { +public: + explicit RendererVulkan(Frontend::WindowSDL& window); + ~RendererVulkan(); + + Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute, + VAddr cpu_address); + + void Present(Frame* frame); + void RecreateFrame(Frame* frame, u32 width, u32 height); + +private: + Frame* GetRenderFrame(); + +private: + Frontend::WindowSDL& window; + Instance instance; + Scheduler scheduler; + Swapchain swapchain; + VideoCore::TextureCache texture_cache; + vk::UniqueCommandPool command_pool; + std::vector present_frames; + std::queue free_queue; + std::mutex free_mutex; + std::condition_variable free_cv; + std::condition_variable_any frame_cv; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_common.cpp b/src/video_core/renderer_vulkan/vk_common.cpp new file mode 100644 index 00000000..e9265ea9 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_common.cpp @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_common.h" + +// Implement vma functions +#define VMA_IMPLEMENTATION +#include + +// Store the dispatch loader here +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h new file mode 100644 index 00000000..7db6fb06 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_common.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// Include vulkan-hpp header +#define VK_ENABLE_BETA_EXTENSIONS +#define VK_NO_PROTOTYPES +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#define VULKAN_HPP_NO_CONSTRUCTORS +#define VULKAN_HPP_NO_STRUCT_SETTERS +#include + +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 diff --git a/src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp b/src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp new file mode 100644 index 00000000..7699bea9 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_update_queue.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h" +#include "video_core/renderer_vulkan/vk_instance.h" + +namespace Vulkan { + +DescriptorUpdateQueue::DescriptorUpdateQueue(const Instance& instance, u32 descriptor_write_max_) + : device{instance.GetDevice()}, descriptor_write_max{descriptor_write_max_} { + descriptor_infos = std::make_unique(descriptor_write_max); + descriptor_writes = std::make_unique(descriptor_write_max); +} + +void DescriptorUpdateQueue::Flush() { + if (descriptor_write_end == 0) { + return; + } + device.updateDescriptorSets({std::span(descriptor_writes.get(), descriptor_write_end)}, {}); + descriptor_write_end = 0; +} + +void DescriptorUpdateQueue::AddStorageImage(vk::DescriptorSet target, u8 binding, + vk::ImageView image_view, + vk::ImageLayout image_layout) { + if (descriptor_write_end >= descriptor_write_max) [[unlikely]] { + Flush(); + } + + auto& image_info = descriptor_infos[descriptor_write_end].image_info; + image_info.sampler = VK_NULL_HANDLE; + image_info.imageView = image_view; + image_info.imageLayout = image_layout; + + descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{ + .dstSet = target, + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageImage, + .pImageInfo = &image_info, + }; +} + +void DescriptorUpdateQueue::AddImageSampler(vk::DescriptorSet target, u8 binding, u8 array_index, + vk::ImageView image_view, vk::Sampler sampler, + vk::ImageLayout image_layout) { + if (descriptor_write_end >= descriptor_write_max) [[unlikely]] { + Flush(); + } + + auto& image_info = descriptor_infos[descriptor_write_end].image_info; + image_info.sampler = sampler; + image_info.imageView = image_view; + image_info.imageLayout = image_layout; + + descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{ + .dstSet = target, + .dstBinding = binding, + .dstArrayElement = array_index, + .descriptorCount = 1, + .descriptorType = + sampler ? vk::DescriptorType::eCombinedImageSampler : vk::DescriptorType::eSampledImage, + .pImageInfo = &image_info, + }; +} + +void DescriptorUpdateQueue::AddBuffer(vk::DescriptorSet target, u8 binding, vk::Buffer buffer, + vk::DeviceSize offset, vk::DeviceSize size, + vk::DescriptorType type) { + if (descriptor_write_end >= descriptor_write_max) [[unlikely]] { + Flush(); + } + + auto& buffer_info = descriptor_infos[descriptor_write_end].buffer_info; + buffer_info.buffer = buffer; + buffer_info.offset = offset; + buffer_info.range = size; + + descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{ + .dstSet = target, + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = type, + .pBufferInfo = &buffer_info, + }; +} + +void DescriptorUpdateQueue::AddTexelBuffer(vk::DescriptorSet target, u8 binding, + vk::BufferView buffer_view) { + if (descriptor_write_end >= descriptor_write_max) [[unlikely]] { + Flush(); + } + + auto& buffer_info = descriptor_infos[descriptor_write_end].buffer_view; + buffer_info = buffer_view; + descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{ + .dstSet = target, + .dstBinding = binding, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformTexelBuffer, + .pTexelBufferView = &buffer_info, + }; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_descriptor_update_queue.h b/src/video_core/renderer_vulkan/vk_descriptor_update_queue.h new file mode 100644 index 00000000..9e864db6 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_update_queue.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { + +class Instance; + +struct DescriptorInfoUnion { + DescriptorInfoUnion() {} + + union { + vk::DescriptorImageInfo image_info; + vk::DescriptorBufferInfo buffer_info; + vk::BufferView buffer_view; + }; +}; + +class DescriptorUpdateQueue { +public: + explicit DescriptorUpdateQueue(const Instance& instance, u32 descriptor_write_max = 2048); + ~DescriptorUpdateQueue() = default; + + void Flush(); + + void AddStorageImage(vk::DescriptorSet target, u8 binding, vk::ImageView image_view, + vk::ImageLayout image_layout = vk::ImageLayout::eGeneral); + + void AddImageSampler(vk::DescriptorSet target, u8 binding, u8 array_index, + vk::ImageView image_view, vk::Sampler sampler, + vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral); + + void AddBuffer(vk::DescriptorSet target, u8 binding, vk::Buffer buffer, vk::DeviceSize offset, + vk::DeviceSize size = VK_WHOLE_SIZE, + vk::DescriptorType type = vk::DescriptorType::eUniformBufferDynamic); + + void AddTexelBuffer(vk::DescriptorSet target, u8 binding, vk::BufferView buffer_view); + +private: + const vk::Device device; + const u32 descriptor_write_max; + std::unique_ptr descriptor_infos; + std::unique_ptr descriptor_writes; + u32 descriptor_write_end = 0; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp new file mode 100644 index 00000000..ceff8369 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "common/assert.h" +#include "sdl_window.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_platform.h" + +#include + +namespace Vulkan { + +namespace { + +std::vector GetSupportedExtensions(vk::PhysicalDevice physical) { + const std::vector extensions = physical.enumerateDeviceExtensionProperties(); + std::vector supported_extensions; + supported_extensions.reserve(extensions.size()); + for (const auto& extension : extensions) { + supported_extensions.emplace_back(extension.extensionName.data()); + } + return supported_extensions; +} + +std::string GetReadableVersion(u32 version) { + return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version), + VK_VERSION_PATCH(version)); +} + +} // Anonymous namespace + +Instance::Instance(bool enable_validation, bool dump_command_buffers) + : instance{CreateInstance(dl, Frontend::WindowSystemType::Headless, enable_validation, + dump_command_buffers)}, + physical_devices{instance->enumeratePhysicalDevices()} {} + +Instance::Instance(Frontend::WindowSDL& window, u32 physical_device_index) + : instance{CreateInstance(dl, window.getWindowInfo().type, true, false)}, + debug_callback{CreateDebugCallback(*instance)}, physical_devices{ + instance->enumeratePhysicalDevices()} { + const std::size_t num_physical_devices = static_cast(physical_devices.size()); + ASSERT_MSG(physical_device_index < num_physical_devices, + "Invalid physical device index {} provided when only {} devices exist", + physical_device_index, num_physical_devices); + + physical_device = physical_devices[physical_device_index]; + available_extensions = GetSupportedExtensions(physical_device); + properties = physical_device.getProperties(); + if (properties.apiVersion < TargetVulkanApiVersion) { + throw std::runtime_error(fmt::format( + "Vulkan {}.{} is required, but only {}.{} is supported by device!", + VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion), + VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion))); + } + + CollectDeviceParameters(); + CreateDevice(); + CollectToolingInfo(); +} + +Instance::~Instance() { + vmaDestroyAllocator(allocator); +} + +std::string Instance::GetDriverVersionName() { + // Extracted from + // https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314 + const u32 version = properties.driverVersion; + if (driver_id == vk::DriverId::eNvidiaProprietary) { + const u32 major = (version >> 22) & 0x3ff; + const u32 minor = (version >> 14) & 0x0ff; + const u32 secondary = (version >> 6) & 0x0ff; + const u32 tertiary = version & 0x003f; + return fmt::format("{}.{}.{}.{}", major, minor, secondary, tertiary); + } + if (driver_id == vk::DriverId::eIntelProprietaryWindows) { + const u32 major = version >> 14; + const u32 minor = version & 0x3fff; + return fmt::format("{}.{}", major, minor); + } + return GetReadableVersion(version); +} + +bool Instance::CreateDevice() { + const vk::StructureChain feature_chain = physical_device.getFeatures2< + vk::PhysicalDeviceFeatures2, vk::PhysicalDevicePortabilitySubsetFeaturesKHR, + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, + vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT, + vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT, + vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR, + vk::PhysicalDeviceCustomBorderColorFeaturesEXT, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT, + vk::PhysicalDeviceFragmentShaderInterlockFeaturesEXT, + vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT, + vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR>(); + const vk::StructureChain properties_chain = + physical_device.getProperties2(); + + features = feature_chain.get().features; + if (available_extensions.empty()) { + LOG_CRITICAL(Render_Vulkan, "No extensions supported by device."); + return false; + } + + boost::container::static_vector enabled_extensions; + const auto add_extension = [&](std::string_view extension) -> bool { + const auto result = + std::find_if(available_extensions.begin(), available_extensions.end(), + [&](const std::string& name) { return name == extension; }); + + if (result != available_extensions.end()) { + LOG_INFO(Render_Vulkan, "Enabling extension: {}", extension); + enabled_extensions.push_back(extension.data()); + return true; + } + + LOG_WARNING(Render_Vulkan, "Extension {} unavailable.", extension); + return false; + }; + + add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + image_format_list = add_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); + shader_stencil_export = add_extension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); + external_memory_host = add_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); + tooling_info = add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME); + + const auto family_properties = physical_device.getQueueFamilyProperties(); + if (family_properties.empty()) { + LOG_CRITICAL(Render_Vulkan, "Physical device reported no queues."); + return false; + } + + bool graphics_queue_found = false; + for (std::size_t i = 0; i < family_properties.size(); i++) { + const u32 index = static_cast(i); + if (family_properties[i].queueFlags & vk::QueueFlagBits::eGraphics) { + queue_family_index = index; + graphics_queue_found = true; + } + } + + if (!graphics_queue_found) { + LOG_CRITICAL(Render_Vulkan, "Unable to find graphics and/or present queues."); + return false; + } + + static constexpr std::array queue_priorities = {1.0f}; + + const vk::DeviceQueueCreateInfo queue_info = { + .queueFamilyIndex = queue_family_index, + .queueCount = static_cast(queue_priorities.size()), + .pQueuePriorities = queue_priorities.data(), + }; + + vk::StructureChain device_chain = { + vk::DeviceCreateInfo{ + .queueCreateInfoCount = 1u, + .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = static_cast(enabled_extensions.size()), + .ppEnabledExtensionNames = enabled_extensions.data(), + }, + vk::PhysicalDeviceFeatures2{ + .features{ + .robustBufferAccess = features.robustBufferAccess, + .geometryShader = features.geometryShader, + .logicOp = features.logicOp, + .samplerAnisotropy = features.samplerAnisotropy, + .fragmentStoresAndAtomics = features.fragmentStoresAndAtomics, + .shaderClipDistance = features.shaderClipDistance, + }, + }, + vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR{ + .timelineSemaphore = true, + }, + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT{ + .extendedDynamicState = true, + }, + vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT{ + .extendedDynamicState2 = true, + .extendedDynamicState2LogicOp = true, + }, + vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{}, + vk::PhysicalDeviceCustomBorderColorFeaturesEXT{ + .customBorderColors = true, + .customBorderColorWithoutFormat = true, + }, + vk::PhysicalDeviceIndexTypeUint8FeaturesEXT{ + .indexTypeUint8 = true, + }, + }; + + try { + device = physical_device.createDeviceUnique(device_chain.get()); + } catch (vk::ExtensionNotPresentError& err) { + LOG_CRITICAL(Render_Vulkan, "Some required extensions are not available {}", err.what()); + return false; + } + + VULKAN_HPP_DEFAULT_DISPATCHER.init(*device); + + graphics_queue = device->getQueue(queue_family_index, 0); + present_queue = device->getQueue(queue_family_index, 0); + + CreateAllocator(); + return true; +} + +void Instance::CreateAllocator() { + const VmaVulkanFunctions functions = { + .vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr, + .vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr, + }; + + const VmaAllocatorCreateInfo allocator_info = { + .physicalDevice = physical_device, + .device = *device, + .pVulkanFunctions = &functions, + .instance = *instance, + .vulkanApiVersion = TargetVulkanApiVersion, + }; + + const VkResult result = vmaCreateAllocator(&allocator_info, &allocator); + if (result != VK_SUCCESS) { + UNREACHABLE_MSG("Failed to initialize VMA with error {}", + vk::to_string(vk::Result{result})); + } +} + +void Instance::CollectDeviceParameters() { + const vk::StructureChain property_chain = + physical_device + .getProperties2(); + const vk::PhysicalDeviceDriverProperties driver = + property_chain.get(); + + driver_id = driver.driverID; + vendor_name = driver.driverName.data(); + + const std::string model_name{GetModelName()}; + const std::string driver_version = GetDriverVersionName(); + const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version); + const std::string api_version = GetReadableVersion(properties.apiVersion); + const std::string extensions = fmt::format("{}", fmt::join(available_extensions, ", ")); + + LOG_INFO(Render_Vulkan, "GPU_Vendor", vendor_name); + LOG_INFO(Render_Vulkan, "GPU_Model", model_name); + LOG_INFO(Render_Vulkan, "GPU_Vulkan_Driver", driver_name); + LOG_INFO(Render_Vulkan, "GPU_Vulkan_Version", api_version); + LOG_INFO(Render_Vulkan, "GPU_Vulkan_Extensions", extensions); +} + +void Instance::CollectToolingInfo() { + if (!tooling_info) { + return; + } + const auto tools = physical_device.getToolPropertiesEXT(); + for (const vk::PhysicalDeviceToolProperties& tool : tools) { + const std::string_view name = tool.name; + LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); + has_renderdoc = has_renderdoc || name == "RenderDoc"; + has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics"; + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h new file mode 100644 index 00000000..59be14d4 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "video_core/renderer_vulkan/vk_platform.h" + +namespace Frontend { +class WindowSDL; +} + +VK_DEFINE_HANDLE(VmaAllocator) + +namespace Vulkan { + +class Instance { +public: + explicit Instance(bool validation = false, bool dump_command_buffers = false); + explicit Instance(Frontend::WindowSDL& window, u32 physical_device_index); + ~Instance(); + + /// Returns a formatted string for the driver version + std::string GetDriverVersionName(); + + /// Returns the Vulkan instance + vk::Instance GetInstance() const { + return *instance; + } + + /// Returns the current physical device + vk::PhysicalDevice GetPhysicalDevice() const { + return physical_device; + } + + /// Returns the Vulkan device + vk::Device GetDevice() const { + return *device; + } + + /// Returns the VMA allocator handle + VmaAllocator GetAllocator() const { + return allocator; + } + + /// Returns a list of the available physical devices + std::span GetPhysicalDevices() const { + return physical_devices; + } + + /// Retrieve queue information + u32 GetGraphicsQueueFamilyIndex() const { + return queue_family_index; + } + + u32 GetPresentQueueFamilyIndex() const { + return queue_family_index; + } + + vk::Queue GetGraphicsQueue() const { + return graphics_queue; + } + + vk::Queue GetPresentQueue() const { + return present_queue; + } + + /// Returns true when a known debugging tool is attached. + bool HasDebuggingToolAttached() const { + return has_renderdoc || has_nsight_graphics; + } + + /// Returns true if anisotropic filtering is supported + bool IsAnisotropicFilteringSupported() const { + return features.samplerAnisotropy; + } + + /// Returns true when VK_EXT_custom_border_color is supported + bool IsCustomBorderColorSupported() const { + return custom_border_color; + } + + /// Returns true when VK_EXT_index_type_uint8 is supported + bool IsIndexTypeUint8Supported() const { + return index_type_uint8; + } + + /// Returns true when VK_EXT_fragment_shader_interlock is supported + bool IsFragmentShaderInterlockSupported() const { + return fragment_shader_interlock; + } + + /// Returns true when VK_KHR_image_format_list is supported + bool IsImageFormatListSupported() const { + return image_format_list; + } + + /// Returns true when VK_EXT_pipeline_creation_cache_control is supported + bool IsPipelineCreationCacheControlSupported() const { + return pipeline_creation_cache_control; + } + + /// Returns true when VK_EXT_shader_stencil_export is supported + bool IsShaderStencilExportSupported() const { + return shader_stencil_export; + } + + /// Returns true when VK_EXT_external_memory_host is supported + bool IsExternalMemoryHostSupported() const { + return external_memory_host; + } + + /// Returns the vendor ID of the physical device + u32 GetVendorID() const { + return properties.vendorID; + } + + /// Returns the device ID of the physical device + u32 GetDeviceID() const { + return properties.deviceID; + } + + /// Returns the driver ID. + vk::DriverId GetDriverID() const { + return driver_id; + } + + /// Returns the current driver version provided in Vulkan-formatted version numbers. + u32 GetDriverVersion() const { + return properties.driverVersion; + } + + /// Returns the current Vulkan API version provided in Vulkan-formatted version numbers. + u32 ApiVersion() const { + return properties.apiVersion; + } + + /// Returns the vendor name reported from Vulkan. + std::string_view GetVendorName() const { + return vendor_name; + } + + /// Returns the list of available extensions. + std::span GetAvailableExtensions() const { + return available_extensions; + } + + /// Returns the device name. + std::string_view GetModelName() const { + return properties.deviceName; + } + + /// Returns the pipeline cache unique identifier + const auto GetPipelineCacheUUID() const { + return properties.pipelineCacheUUID; + } + + /// Returns the minimum required alignment for uniforms + vk::DeviceSize UniformMinAlignment() const { + return properties.limits.minUniformBufferOffsetAlignment; + } + + /// Returns the minimum alignemt required for accessing host-mapped device memory + vk::DeviceSize NonCoherentAtomSize() const { + return properties.limits.nonCoherentAtomSize; + } + + /// Returns the maximum supported elements in a texel buffer + u32 MaxTexelBufferElements() const { + return properties.limits.maxTexelBufferElements; + } + + /// Returns true if shaders can declare the ClipDistance attribute + bool IsShaderClipDistanceSupported() const { + return features.shaderClipDistance; + } + + /// Returns the minimum imported host pointer alignment + u64 GetMinImportedHostPointerAlignment() const { + return min_imported_host_pointer_alignment; + } + +private: + /// Creates the logical device opportunistically enabling extensions + bool CreateDevice(); + + /// Creates the VMA allocator handle + void CreateAllocator(); + + /// Collects telemetry information from the device. + void CollectDeviceParameters(); + void CollectToolingInfo(); + +private: + vk::DynamicLoader dl; + vk::UniqueInstance instance; + vk::PhysicalDevice physical_device; + vk::UniqueDevice device; + vk::PhysicalDeviceProperties properties; + vk::PhysicalDeviceFeatures features; + vk::DriverIdKHR driver_id; + vk::UniqueDebugUtilsMessengerEXT debug_callback; + std::string vendor_name; + VmaAllocator allocator{}; + vk::Queue present_queue; + vk::Queue graphics_queue; + std::vector physical_devices; + std::vector available_extensions; + u32 queue_family_index{0}; + bool image_view_reinterpretation{true}; + bool timeline_semaphores{}; + bool custom_border_color{}; + bool index_type_uint8{}; + bool fragment_shader_interlock{}; + bool image_format_list{}; + bool pipeline_creation_cache_control{}; + bool fragment_shader_barycentric{}; + bool shader_stencil_export{}; + bool external_memory_host{}; + u64 min_imported_host_pointer_alignment{}; + bool tooling_info{}; + bool debug_utils_supported{}; + bool has_nsight_graphics{}; + bool has_renderdoc{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp new file mode 100644 index 00000000..037510d4 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/assert.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" + +namespace Vulkan { + +constexpr u64 WAIT_TIMEOUT = std::numeric_limits::max(); + +MasterSemaphore::MasterSemaphore(const Instance& instance_) : instance{instance_} { + const vk::StructureChain semaphore_chain = { + vk::SemaphoreCreateInfo{}, + vk::SemaphoreTypeCreateInfo{ + .semaphoreType = vk::SemaphoreType::eTimeline, + .initialValue = 0, + }, + }; + semaphore = instance.GetDevice().createSemaphoreUnique(semaphore_chain.get()); +} + +MasterSemaphore::~MasterSemaphore() = default; + +void MasterSemaphore::Refresh() { + u64 this_tick{}; + u64 counter{}; + do { + this_tick = gpu_tick.load(std::memory_order_acquire); + counter = instance.GetDevice().getSemaphoreCounterValue(*semaphore); + if (counter < this_tick) { + return; + } + } while (!gpu_tick.compare_exchange_weak(this_tick, counter, std::memory_order_release, + std::memory_order_relaxed)); +} + +void MasterSemaphore::Wait(u64 tick) { + // No need to wait if the GPU is ahead of the tick + if (IsFree(tick)) { + return; + } + // Update the GPU tick and try again + Refresh(); + if (IsFree(tick)) { + return; + } + + // If none of the above is hit, fallback to a regular wait + const vk::SemaphoreWaitInfo wait_info = { + .semaphoreCount = 1, + .pSemaphores = &semaphore.get(), + .pValues = &tick, + }; + + while (instance.GetDevice().waitSemaphores(&wait_info, WAIT_TIMEOUT) != vk::Result::eSuccess) { + } + Refresh(); +} + +void MasterSemaphore::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, + u64 signal_value) { + cmdbuf.end(); + + const u32 num_signal_semaphores = signal ? 2U : 1U; + const std::array signal_values{signal_value, u64(0)}; + const std::array signal_semaphores{Handle(), signal}; + + const u32 num_wait_semaphores = wait ? 2U : 1U; + const std::array wait_values{signal_value - 1, u64(1)}; + const std::array wait_semaphores{Handle(), wait}; + + static constexpr std::array wait_stage_masks = { + vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + }; + + const vk::TimelineSemaphoreSubmitInfo timeline_si = { + .waitSemaphoreValueCount = num_wait_semaphores, + .pWaitSemaphoreValues = wait_values.data(), + .signalSemaphoreValueCount = num_signal_semaphores, + .pSignalSemaphoreValues = signal_values.data(), + }; + + const vk::SubmitInfo submit_info = { + .pNext = &timeline_si, + .waitSemaphoreCount = num_wait_semaphores, + .pWaitSemaphores = wait_semaphores.data(), + .pWaitDstStageMask = wait_stage_masks.data(), + .commandBufferCount = 1u, + .pCommandBuffers = &cmdbuf, + .signalSemaphoreCount = num_signal_semaphores, + .pSignalSemaphores = signal_semaphores.data(), + }; + + try { + instance.GetGraphicsQueue().submit(submit_info); + } catch (vk::DeviceLostError& err) { + UNREACHABLE_MSG("Device lost during submit: {}", err.what()); + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h new file mode 100644 index 00000000..963676b1 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { + +class Instance; +class Scheduler; + +class MasterSemaphore { +public: + explicit MasterSemaphore(const Instance& instance_); + ~MasterSemaphore(); + + [[nodiscard]] u64 CurrentTick() const noexcept { + return current_tick.load(std::memory_order_acquire); + } + + [[nodiscard]] u64 KnownGpuTick() const noexcept { + return gpu_tick.load(std::memory_order_acquire); + } + + [[nodiscard]] bool IsFree(u64 tick) const noexcept { + return KnownGpuTick() >= tick; + } + + [[nodiscard]] u64 NextTick() noexcept { + return current_tick.fetch_add(1, std::memory_order_release); + } + + [[nodiscard]] vk::Semaphore Handle() const noexcept { + return semaphore.get(); + } + + /// Refresh the known GPU tick + void Refresh(); + + /// Waits for a tick to be hit on the GPU + void Wait(u64 tick); + + /// Submits the provided command buffer for execution + void SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait, vk::Semaphore signal, + u64 signal_value); + +protected: + const Instance& instance; + vk::UniqueSemaphore semaphore; ///< Timeline semaphore. + std::atomic gpu_tick{0}; ///< Current known GPU tick. + std::atomic current_tick{1}; ///< Current logical tick. +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp new file mode 100644 index 00000000..ecf7fae5 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Include the vulkan platform specific header +#if defined(ANDROID) +#define VK_USE_PLATFORM_ANDROID_KHR +#elif defined(WIN32) +#define VK_USE_PLATFORM_WIN32_KHR +#elif defined(__APPLE__) +#define VK_USE_PLATFORM_METAL_EXT +#else +#define VK_USE_PLATFORM_WAYLAND_KHR +#define VK_USE_PLATFORM_XLIB_KHR +#endif + +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "sdl_window.h" +#include "video_core/renderer_vulkan/vk_platform.h" + +namespace Vulkan { + +static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { + + switch (static_cast(callback_data->messageIdNumber)) { + case 0x609a13b: // Vertex attribute at location not consumed by shader + case 0xc81ad50e: + return VK_FALSE; + default: + break; + } + + Common::Log::Level level{}; + switch (severity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + level = Common::Log::Level::Error; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + level = Common::Log::Level::Info; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + level = Common::Log::Level::Debug; + break; + default: + level = Common::Log::Level::Info; + } + + LOG_GENERIC(Common::Log::Class::Render_Vulkan, level, "{}: {}", + callback_data->pMessageIdName ? callback_data->pMessageIdName : "", + callback_data->pMessage ? callback_data->pMessage : ""); + + return VK_FALSE; +} + +vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window) { + const auto& window_info = emu_window.getWindowInfo(); + vk::SurfaceKHR surface{}; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (window_info.type == Frontend::WindowSystemType::Windows) { + const vk::Win32SurfaceCreateInfoKHR win32_ci = { + .hinstance = nullptr, + .hwnd = static_cast(window_info.render_surface), + }; + + if (instance.createWin32SurfaceKHR(&win32_ci, nullptr, &surface) != vk::Result::eSuccess) { + LOG_CRITICAL(Render_Vulkan, "Failed to initialize Win32 surface"); + UNREACHABLE(); + } + } +#elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (window_info.type == Frontend::WindowSystemType::X11) { + const vk::XlibSurfaceCreateInfoKHR xlib_ci = { + .dpy = static_cast(window_info.display_connection), + .window = reinterpret_cast(window_info.render_surface), + }; + + if (instance.createXlibSurfaceKHR(&xlib_ci, nullptr, &surface) != vk::Result::eSuccess) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface"); + UNREACHABLE(); + } + } else if (window_info.type == Frontend::WindowSystemType::Wayland) { + const vk::WaylandSurfaceCreateInfoKHR wayland_ci = { + .display = static_cast(window_info.display_connection), + .surface = static_cast(window_info.render_surface), + }; + + if (instance.createWaylandSurfaceKHR(&wayland_ci, nullptr, &surface) != + vk::Result::eSuccess) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface"); + UNREACHABLE(); + } + } +#endif + + if (!surface) { + LOG_CRITICAL(Render_Vulkan, "Presentation not supported on this platform"); + UNREACHABLE(); + } + + return surface; +} + +std::vector GetInstanceExtensions(Frontend::WindowSystemType window_type, + bool enable_debug_utils) { + const auto properties = vk::enumerateInstanceExtensionProperties(); + if (properties.empty()) { + LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); + return {}; + } + + // Add the windowing system specific extension + std::vector extensions; + extensions.reserve(7); + + switch (window_type) { + case Frontend::WindowSystemType::Headless: + break; +#if defined(VK_USE_PLATFORM_WIN32_KHR) + case Frontend::WindowSystemType::Windows: + extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + break; +#elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) + case Frontend::WindowSystemType::X11: + extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + break; + case Frontend::WindowSystemType::Wayland: + extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + break; +#endif + default: + LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); + break; + } + + if (window_type != Frontend::WindowSystemType::Headless) { + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + } + + if (enable_debug_utils) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + // Sanitize extension list + std::erase_if(extensions, [&](const char* extension) -> bool { + const auto it = + std::find_if(properties.begin(), properties.end(), [extension](const auto& prop) { + return std::strcmp(extension, prop.extensionName) == 0; + }); + + if (it == properties.end()) { + LOG_INFO(Render_Vulkan, "Candidate instance extension {} is not available", extension); + return true; + } + return false; + }); + + return extensions; +} + +vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemType window_type, + bool enable_validation, bool dump_command_buffers) { + auto vkGetInstanceProcAddr = + dl.getProcAddress("vkGetInstanceProcAddr"); + VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); + + const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion + ? vk::enumerateInstanceVersion() + : VK_API_VERSION_1_0; + + if (available_version < TargetVulkanApiVersion) { + throw std::runtime_error(fmt::format( + "Vulkan {}.{} is required, but only {}.{} is supported by instance!", + VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion), + VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version))); + } + + const auto extensions = GetInstanceExtensions(window_type, enable_validation); + + const vk::ApplicationInfo application_info = { + .pApplicationName = "Citra", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "Citra Vulkan", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = available_version, + }; + + u32 num_layers = 0; + std::array layers; + + if (enable_validation) { + layers[num_layers++] = "VK_LAYER_KHRONOS_validation"; + } + if (dump_command_buffers) { + layers[num_layers++] = "VK_LAYER_LUNARG_api_dump"; + } + + vk::InstanceCreateInfo instance_ci = { + .pApplicationInfo = &application_info, + .enabledLayerCount = num_layers, + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + }; + + auto instance = vk::createInstanceUnique(instance_ci); + + VULKAN_HPP_DEFAULT_DISPATCHER.init(*instance); + + return instance; +} + +vk::UniqueDebugUtilsMessengerEXT CreateDebugCallback(vk::Instance instance) { + const vk::DebugUtilsMessengerCreateInfoEXT msg_ci = { + .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose, + .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::eDeviceAddressBinding | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, + .pfnUserCallback = DebugUtilsCallback, + }; + return instance.createDebugUtilsMessengerEXTUnique(msg_ci); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h new file mode 100644 index 00000000..793f2f3e --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Frontend { +enum class WindowSystemType : u8; +class WindowSDL; +} // namespace Frontend + +namespace Vulkan { + +constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_3; + +vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window); + +vk::UniqueInstance CreateInstance(vk::DynamicLoader& dl, Frontend::WindowSystemType window_type, + bool enable_validation, bool dump_command_buffers); + +vk::UniqueDebugUtilsMessengerEXT CreateDebugCallback(vk::Instance instance); + +template +concept VulkanHandleType = vk::isVulkanHandleType::value; + +template +void SetObjectName(vk::Device device, const HandleType& handle, std::string_view debug_name) { + const vk::DebugUtilsObjectNameInfoEXT name_info = { + .objectType = HandleType::objectType, + .objectHandle = reinterpret_cast(static_cast(handle)), + .pObjectName = debug_name.data(), + }; + device.setDebugUtilsObjectNameEXT(name_info); +} + +template +void SetObjectName(vk::Device device, const HandleType& handle, const char* format, + const Args&... args) { + const std::string debug_name = fmt::vformat(format, fmt::make_format_args(args...)); + SetObjectName(device, handle, debug_name); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp new file mode 100644 index 00000000..f9f2ae0a --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include "common/assert.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" + +namespace Vulkan { + +ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_, std::size_t grow_step_) + : master_semaphore{master_semaphore_}, grow_step{grow_step_} {} + +std::size_t ResourcePool::CommitResource() { + u64 gpu_tick = master_semaphore->KnownGpuTick(); + const auto search = [this, gpu_tick](std::size_t begin, + std::size_t end) -> std::optional { + for (std::size_t iterator = begin; iterator < end; ++iterator) { + if (gpu_tick >= ticks[iterator]) { + ticks[iterator] = master_semaphore->CurrentTick(); + return iterator; + } + } + return std::nullopt; + }; + + // Try to find a free resource from the hinted position to the end. + auto found = search(hint_iterator, ticks.size()); + if (!found) { + // Refresh semaphore to query updated results + master_semaphore->Refresh(); + gpu_tick = master_semaphore->KnownGpuTick(); + found = search(hint_iterator, ticks.size()); + } + if (!found) { + // Search from beginning to the hinted position. + found = search(0, hint_iterator); + if (!found) { + // Both searches failed, the pool is full; handle it. + const std::size_t free_resource = ManageOverflow(); + + ticks[free_resource] = master_semaphore->CurrentTick(); + found = free_resource; + } + } + + // Free iterator is hinted to the resource after the one that's been commited. + hint_iterator = (*found + 1) % ticks.size(); + return *found; +} + +std::size_t ResourcePool::ManageOverflow() { + const std::size_t old_capacity = ticks.size(); + ticks.resize(old_capacity + grow_step); + Allocate(old_capacity, old_capacity + grow_step); + return old_capacity; +} + +constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 4; + +CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore) + : ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} { + const vk::CommandPoolCreateInfo pool_create_info = { + .flags = vk::CommandPoolCreateFlagBits::eTransient | + vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(), + }; + const vk::Device device = instance.GetDevice(); + cmd_pool = device.createCommandPoolUnique(pool_create_info); + if (instance.HasDebuggingToolAttached()) { + SetObjectName(device, *cmd_pool, "CommandPool"); + } +} + +CommandPool::~CommandPool() = default; + +void CommandPool::Allocate(std::size_t begin, std::size_t end) { + cmd_buffers.resize(end); + + const vk::CommandBufferAllocateInfo buffer_alloc_info = { + .commandPool = *cmd_pool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = COMMAND_BUFFER_POOL_SIZE, + }; + + const vk::Device device = instance.GetDevice(); + const auto result = + device.allocateCommandBuffers(&buffer_alloc_info, cmd_buffers.data() + begin); + ASSERT(result == vk::Result::eSuccess); + + if (instance.HasDebuggingToolAttached()) { + for (std::size_t i = begin; i < end; ++i) { + SetObjectName(device, cmd_buffers[i], "CommandPool: Command Buffer {}", i); + } + } +} + +vk::CommandBuffer CommandPool::Commit() { + const std::size_t index = CommitResource(); + return cmd_buffers[index]; +} + +constexpr u32 DESCRIPTOR_SET_BATCH = 32; + +DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, + std::span bindings, + u32 descriptor_heap_count_) + : ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()}, + descriptor_heap_count{descriptor_heap_count_} { + // Create descriptor set layout. + const vk::DescriptorSetLayoutCreateInfo layout_ci = { + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data(), + }; + descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_ci); + if (instance.HasDebuggingToolAttached()) { + SetObjectName(device, *descriptor_set_layout, "DescriptorSetLayout"); + } + + // Build descriptor set pool counts. + std::unordered_map descriptor_type_counts; + for (const auto& binding : bindings) { + descriptor_type_counts[binding.descriptorType] += binding.descriptorCount; + } + for (const auto& [type, count] : descriptor_type_counts) { + auto& pool_size = pool_sizes.emplace_back(); + pool_size.descriptorCount = count * descriptor_heap_count; + pool_size.type = type; + } + + // Create descriptor pool + AppendDescriptorPool(); +} + +DescriptorHeap::~DescriptorHeap() = default; + +void DescriptorHeap::Allocate(std::size_t begin, std::size_t end) { + ASSERT(end - begin == DESCRIPTOR_SET_BATCH); + descriptor_sets.resize(end); + hashes.resize(end); + + std::array layouts; + layouts.fill(*descriptor_set_layout); + + u32 current_pool = 0; + vk::DescriptorSetAllocateInfo alloc_info = { + .descriptorPool = *pools[current_pool], + .descriptorSetCount = DESCRIPTOR_SET_BATCH, + .pSetLayouts = layouts.data(), + }; + + // Attempt to allocate the descriptor set batch. If the pool has run out of space, use a new + // one. + while (true) { + const auto result = + device.allocateDescriptorSets(&alloc_info, descriptor_sets.data() + begin); + if (result == vk::Result::eSuccess) { + break; + } + if (result == vk::Result::eErrorOutOfPoolMemory) { + current_pool++; + if (current_pool == pools.size()) { + LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!"); + AppendDescriptorPool(); + } + alloc_info.descriptorPool = *pools[current_pool]; + } + } +} + +vk::DescriptorSet DescriptorHeap::Commit() { + const std::size_t index = CommitResource(); + return descriptor_sets[index]; +} + +void DescriptorHeap::AppendDescriptorPool() { + const vk::DescriptorPoolCreateInfo pool_info = { + .flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, + .maxSets = descriptor_heap_count, + .poolSizeCount = static_cast(pool_sizes.size()), + .pPoolSizes = pool_sizes.data(), + }; + auto& pool = pools.emplace_back(); + pool = device.createDescriptorPoolUnique(pool_info); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h new file mode 100644 index 00000000..b138b969 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_resource_pool.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { + +class Instance; +class MasterSemaphore; + +/** + * Handles a pool of resources protected by fences. Manages resource overflow allocating more + * resources. + */ +class ResourcePool { +public: + explicit ResourcePool() = default; + explicit ResourcePool(MasterSemaphore* master_semaphore, std::size_t grow_step); + virtual ~ResourcePool() = default; + + ResourcePool& operator=(ResourcePool&&) noexcept = default; + ResourcePool(ResourcePool&&) noexcept = default; + + ResourcePool& operator=(const ResourcePool&) = default; + ResourcePool(const ResourcePool&) = default; + +protected: + std::size_t CommitResource(); + + /// Called when a chunk of resources have to be allocated. + virtual void Allocate(std::size_t begin, std::size_t end) = 0; + +private: + /// Manages pool overflow allocating new resources. + std::size_t ManageOverflow(); + +protected: + MasterSemaphore* master_semaphore{nullptr}; + std::size_t grow_step = 0; ///< Number of new resources created after an overflow + std::size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found + std::vector ticks; ///< Ticks for each resource +}; + +class CommandPool final : public ResourcePool { +public: + explicit CommandPool(const Instance& instance, MasterSemaphore* master_semaphore); + ~CommandPool() override; + + void Allocate(std::size_t begin, std::size_t end) override; + + vk::CommandBuffer Commit(); + +private: + const Instance& instance; + vk::UniqueCommandPool cmd_pool; + std::vector cmd_buffers; +}; + +class DescriptorHeap final : public ResourcePool { +public: + explicit DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore, + std::span bindings, + u32 descriptor_heap_count = 1024); + ~DescriptorHeap() override; + + const vk::DescriptorSetLayout& Layout() const { + return *descriptor_set_layout; + } + + void Allocate(std::size_t begin, std::size_t end) override; + + vk::DescriptorSet Commit(); + +private: + void AppendDescriptorPool(); + +private: + vk::Device device; + vk::UniqueDescriptorSetLayout descriptor_set_layout; + u32 descriptor_heap_count; + std::vector pool_sizes; + std::vector pools; + std::vector descriptor_sets; + std::vector hashes; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp new file mode 100644 index 00000000..fe7f7e32 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/thread.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" + +namespace Vulkan { + +void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) { + auto command = first; + while (command != nullptr) { + auto next = command->GetNext(); + command->Execute(cmdbuf); + command->~Command(); + command = next; + } + submit = false; + command_offset = 0; + first = nullptr; + last = nullptr; +} + +Scheduler::Scheduler(const Instance& instance) + : master_semaphore{instance}, command_pool{instance, &master_semaphore}, use_worker_thread{ + true} { + AllocateWorkerCommandBuffers(); + if (use_worker_thread) { + AcquireNewChunk(); + worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); + } +} + +Scheduler::~Scheduler() = default; + +void Scheduler::Flush(vk::Semaphore signal, vk::Semaphore wait) { + // When flushing, we only send data to the worker thread; no waiting is necessary. + SubmitExecution(signal, wait); +} + +void Scheduler::Finish(vk::Semaphore signal, vk::Semaphore wait) { + // When finishing, we need to wait for the submission to have executed on the device. + const u64 presubmit_tick = CurrentTick(); + SubmitExecution(signal, wait); + Wait(presubmit_tick); +} + +void Scheduler::WaitWorker() { + if (!use_worker_thread) { + return; + } + + DispatchWork(); + + // Ensure the queue is drained. + { + std::unique_lock ql{queue_mutex}; + event_cv.wait(ql, [this] { return work_queue.empty(); }); + } + + // Now wait for execution to finish. + // This needs to be done in the same order as WorkerThread. + std::scoped_lock el{execution_mutex}; +} + +void Scheduler::Wait(u64 tick) { + if (tick >= master_semaphore.CurrentTick()) { + // Make sure we are not waiting for the current tick without signalling + Flush(); + } + master_semaphore.Wait(tick); +} + +void Scheduler::DispatchWork() { + if (!use_worker_thread || chunk->Empty()) { + return; + } + + { + std::scoped_lock ql{queue_mutex}; + work_queue.push(std::move(chunk)); + } + + event_cv.notify_all(); + AcquireNewChunk(); +} + +void Scheduler::WorkerThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("VulkanWorker"); + + const auto TryPopQueue{[this](auto& work) -> bool { + if (work_queue.empty()) { + return false; + } + + work = std::move(work_queue.front()); + work_queue.pop(); + event_cv.notify_all(); + return true; + }}; + + while (!stop_token.stop_requested()) { + std::unique_ptr work; + + { + std::unique_lock lk{queue_mutex}; + + // Wait for work. + event_cv.wait(lk, stop_token, [&] { return TryPopQueue(work); }); + + // If we've been asked to stop, we're done. + if (stop_token.stop_requested()) { + return; + } + + // Exchange lock ownership so that we take the execution lock before + // the queue lock goes out of scope. This allows us to force execution + // to complete in the next step. + std::exchange(lk, std::unique_lock{execution_mutex}); + + // Perform the work, tracking whether the chunk was a submission + // before executing. + const bool has_submit = work->HasSubmit(); + work->ExecuteAll(current_cmdbuf); + + // If the chunk was a submission, reallocate the command buffer. + if (has_submit) { + AllocateWorkerCommandBuffers(); + } + } + + { + std::scoped_lock rl{reserve_mutex}; + + // Recycle the chunk back to the reserve. + chunk_reserve.emplace_back(std::move(work)); + } + } +} + +void Scheduler::AllocateWorkerCommandBuffers() { + const vk::CommandBufferBeginInfo begin_info = { + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, + }; + + current_cmdbuf = command_pool.Commit(); + current_cmdbuf.begin(begin_info); +} + +void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore) { + const u64 signal_value = master_semaphore.NextTick(); + + Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) { + std::scoped_lock lock{submit_mutex}; + master_semaphore.SubmitWork(cmdbuf, wait_semaphore, signal_semaphore, signal_value); + }); + + master_semaphore.Refresh(); + + if (!use_worker_thread) { + AllocateWorkerCommandBuffers(); + } else { + chunk->MarkSubmit(); + DispatchWork(); + } +} + +void Scheduler::AcquireNewChunk() { + std::scoped_lock lock{reserve_mutex}; + if (chunk_reserve.empty()) { + chunk = std::make_unique(); + return; + } + + chunk = std::move(chunk_reserve.back()); + chunk_reserve.pop_back(); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h new file mode 100644 index 00000000..9eb456c3 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "common/alignment.h" +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_master_semaphore.h" +#include "video_core/renderer_vulkan/vk_resource_pool.h" + +namespace Vulkan { + +class Instance; + +/// The scheduler abstracts command buffer and fence management with an interface that's able to do +/// OpenGL-like operations on Vulkan command buffers. +class Scheduler { +public: + explicit Scheduler(const Instance& instance); + ~Scheduler(); + + /// Sends the current execution context to the GPU. + void Flush(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); + + /// Sends the current execution context to the GPU and waits for it to complete. + void Finish(vk::Semaphore signal = nullptr, vk::Semaphore wait = nullptr); + + /// Waits for the worker thread to finish executing everything. After this function returns it's + /// safe to touch worker resources. + void WaitWorker(); + + /// Waits for the given tick to trigger on the GPU. + void Wait(u64 tick); + + /// Sends currently recorded work to the worker thread. + void DispatchWork(); + + /// Records the command to the current chunk. + template + void Record(T&& command) { + if (chunk->Record(command)) { + return; + } + DispatchWork(); + (void)chunk->Record(command); + } + + /// Registers a callback to perform on queue submission. + void RegisterOnSubmit(std::function&& func) { + on_submit = std::move(func); + } + + /// Registers a callback to perform on queue submission. + void RegisterOnDispatch(std::function&& func) { + on_dispatch = std::move(func); + } + + /// Returns the current command buffer tick. + [[nodiscard]] u64 CurrentTick() const noexcept { + return master_semaphore.CurrentTick(); + } + + /// Returns true when a tick has been triggered by the GPU. + [[nodiscard]] bool IsFree(u64 tick) const noexcept { + return master_semaphore.IsFree(tick); + } + + /// Returns the master timeline semaphore. + [[nodiscard]] MasterSemaphore* GetMasterSemaphore() noexcept { + return &master_semaphore; + } + + std::mutex submit_mutex; + +private: + class Command { + public: + virtual ~Command() = default; + + virtual void Execute(vk::CommandBuffer cmdbuf) const = 0; + + Command* GetNext() const { + return next; + } + + void SetNext(Command* next_) { + next = next_; + } + + private: + Command* next = nullptr; + }; + + template + class TypedCommand final : public Command { + public: + explicit TypedCommand(T&& command_) : command{std::move(command_)} {} + ~TypedCommand() override = default; + + TypedCommand(TypedCommand&&) = delete; + TypedCommand& operator=(TypedCommand&&) = delete; + + void Execute(vk::CommandBuffer cmdbuf) const override { + command(cmdbuf); + } + + private: + T command; + }; + + class CommandChunk final { + public: + void ExecuteAll(vk::CommandBuffer cmdbuf); + + template + bool Record(T& command) { + using FuncType = TypedCommand; + static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large"); + + recorded_counts++; + command_offset = Common::alignUp(command_offset, alignof(FuncType)); + if (command_offset > sizeof(data) - sizeof(FuncType)) { + return false; + } + Command* const current_last = last; + last = new (data.data() + command_offset) FuncType(std::move(command)); + + if (current_last) { + current_last->SetNext(last); + } else { + first = last; + } + command_offset += sizeof(FuncType); + return true; + } + + void MarkSubmit() { + submit = true; + } + + bool Empty() const { + return recorded_counts == 0; + } + + bool HasSubmit() const { + return submit; + } + + private: + Command* first = nullptr; + Command* last = nullptr; + + std::size_t recorded_counts = 0; + std::size_t command_offset = 0; + bool submit = false; + alignas(std::max_align_t) std::array data{}; + }; + +private: + void WorkerThread(std::stop_token stop_token); + + void AllocateWorkerCommandBuffers(); + + void SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wait_semaphore); + + void AcquireNewChunk(); + +private: + MasterSemaphore master_semaphore; + CommandPool command_pool; + std::unique_ptr chunk; + std::queue> work_queue; + std::vector> chunk_reserve; + vk::CommandBuffer current_cmdbuf; + std::function on_submit; + std::function on_dispatch; + std::mutex execution_mutex; + std::mutex reserve_mutex; + std::mutex queue_mutex; + std::condition_variable_any event_cv; + std::jthread worker_thread; + bool use_worker_thread; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp new file mode 100644 index 00000000..699fa15a --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" + +namespace Vulkan { + +namespace { +constexpr TBuiltInResource DefaultTBuiltInResource = { + .maxLights = 32, + .maxClipPlanes = 6, + .maxTextureUnits = 32, + .maxTextureCoords = 32, + .maxVertexAttribs = 64, + .maxVertexUniformComponents = 4096, + .maxVaryingFloats = 64, + .maxVertexTextureImageUnits = 32, + .maxCombinedTextureImageUnits = 80, + .maxTextureImageUnits = 32, + .maxFragmentUniformComponents = 4096, + .maxDrawBuffers = 32, + .maxVertexUniformVectors = 128, + .maxVaryingVectors = 8, + .maxFragmentUniformVectors = 16, + .maxVertexOutputVectors = 16, + .maxFragmentInputVectors = 15, + .minProgramTexelOffset = -8, + .maxProgramTexelOffset = 7, + .maxClipDistances = 8, + .maxComputeWorkGroupCountX = 65535, + .maxComputeWorkGroupCountY = 65535, + .maxComputeWorkGroupCountZ = 65535, + .maxComputeWorkGroupSizeX = 1024, + .maxComputeWorkGroupSizeY = 1024, + .maxComputeWorkGroupSizeZ = 64, + .maxComputeUniformComponents = 1024, + .maxComputeTextureImageUnits = 16, + .maxComputeImageUniforms = 8, + .maxComputeAtomicCounters = 8, + .maxComputeAtomicCounterBuffers = 1, + .maxVaryingComponents = 60, + .maxVertexOutputComponents = 64, + .maxGeometryInputComponents = 64, + .maxGeometryOutputComponents = 128, + .maxFragmentInputComponents = 128, + .maxImageUnits = 8, + .maxCombinedImageUnitsAndFragmentOutputs = 8, + .maxCombinedShaderOutputResources = 8, + .maxImageSamples = 0, + .maxVertexImageUniforms = 0, + .maxTessControlImageUniforms = 0, + .maxTessEvaluationImageUniforms = 0, + .maxGeometryImageUniforms = 0, + .maxFragmentImageUniforms = 8, + .maxCombinedImageUniforms = 8, + .maxGeometryTextureImageUnits = 16, + .maxGeometryOutputVertices = 256, + .maxGeometryTotalOutputComponents = 1024, + .maxGeometryUniformComponents = 1024, + .maxGeometryVaryingComponents = 64, + .maxTessControlInputComponents = 128, + .maxTessControlOutputComponents = 128, + .maxTessControlTextureImageUnits = 16, + .maxTessControlUniformComponents = 1024, + .maxTessControlTotalOutputComponents = 4096, + .maxTessEvaluationInputComponents = 128, + .maxTessEvaluationOutputComponents = 128, + .maxTessEvaluationTextureImageUnits = 16, + .maxTessEvaluationUniformComponents = 1024, + .maxTessPatchComponents = 120, + .maxPatchVertices = 32, + .maxTessGenLevel = 64, + .maxViewports = 16, + .maxVertexAtomicCounters = 0, + .maxTessControlAtomicCounters = 0, + .maxTessEvaluationAtomicCounters = 0, + .maxGeometryAtomicCounters = 0, + .maxFragmentAtomicCounters = 8, + .maxCombinedAtomicCounters = 8, + .maxAtomicCounterBindings = 1, + .maxVertexAtomicCounterBuffers = 0, + .maxTessControlAtomicCounterBuffers = 0, + .maxTessEvaluationAtomicCounterBuffers = 0, + .maxGeometryAtomicCounterBuffers = 0, + .maxFragmentAtomicCounterBuffers = 1, + .maxCombinedAtomicCounterBuffers = 1, + .maxAtomicCounterBufferSize = 16384, + .maxTransformFeedbackBuffers = 4, + .maxTransformFeedbackInterleavedComponents = 64, + .maxCullDistances = 8, + .maxCombinedClipAndCullDistances = 8, + .maxSamples = 4, + .maxMeshOutputVerticesNV = 256, + .maxMeshOutputPrimitivesNV = 512, + .maxMeshWorkGroupSizeX_NV = 32, + .maxMeshWorkGroupSizeY_NV = 1, + .maxMeshWorkGroupSizeZ_NV = 1, + .maxTaskWorkGroupSizeX_NV = 32, + .maxTaskWorkGroupSizeY_NV = 1, + .maxTaskWorkGroupSizeZ_NV = 1, + .maxMeshViewCountNV = 4, + .maxDualSourceDrawBuffersEXT = 1, + .limits = + TLimits{ + .nonInductiveForLoops = 1, + .whileLoops = 1, + .doWhileLoops = 1, + .generalUniformIndexing = 1, + .generalAttributeMatrixVectorIndexing = 1, + .generalVaryingIndexing = 1, + .generalSamplerIndexing = 1, + .generalVariableIndexing = 1, + .generalConstantMatrixVectorIndexing = 1, + }, +}; + +EShLanguage ToEshShaderStage(vk::ShaderStageFlagBits stage) { + switch (stage) { + case vk::ShaderStageFlagBits::eVertex: + return EShLanguage::EShLangVertex; + case vk::ShaderStageFlagBits::eGeometry: + return EShLanguage::EShLangGeometry; + case vk::ShaderStageFlagBits::eFragment: + return EShLanguage::EShLangFragment; + case vk::ShaderStageFlagBits::eCompute: + return EShLanguage::EShLangCompute; + default: + UNREACHABLE_MSG("Unkown shader stage {}", vk::to_string(stage)); + } + return EShLanguage::EShLangVertex; +} + +bool InitializeCompiler() { + static bool glslang_initialized = false; + + if (glslang_initialized) { + return true; + } + + if (!glslang::InitializeProcess()) { + LOG_CRITICAL(Render_Vulkan, "Failed to initialize glslang shader compiler"); + return false; + } + + std::atexit([]() { glslang::FinalizeProcess(); }); + + glslang_initialized = true; + return true; +} +} // Anonymous namespace + +vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) { + if (!InitializeCompiler()) { + return {}; + } + + EProfile profile = ECoreProfile; + EShMessages messages = + static_cast(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); + EShLanguage lang = ToEshShaderStage(stage); + + const int default_version = 450; + const char* pass_source_code = code.data(); + int pass_source_code_length = static_cast(code.size()); + + auto shader = std::make_unique(lang); + shader->setEnvTarget(glslang::EShTargetSpv, + glslang::EShTargetLanguageVersion::EShTargetSpv_1_3); + shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1); + + glslang::TShader::ForbidIncluder includer; + if (!shader->parse(&DefaultTBuiltInResource, default_version, profile, false, true, messages, + includer)) [[unlikely]] { + LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(), + shader->getInfoDebugLog()); + LOG_INFO(Render_Vulkan, "Shader Source:\n{}", code); + return {}; + } + + // Even though there's only a single shader, we still need to link it to generate SPV + auto program = std::make_unique(); + program->addShader(shader.get()); + if (!program->link(messages)) { + LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(), + program->getInfoDebugLog()); + return {}; + } + + glslang::TIntermediate* intermediate = program->getIntermediate(lang); + std::vector out_code; + spv::SpvBuildLogger logger; + glslang::SpvOptions options; + + // Enable optimizations on the generated SPIR-V code. + options.disableOptimizer = false; + options.validate = false; + options.optimizeSize = true; + + glslang::GlslangToSpv(*intermediate, out_code, &logger, &options); + + const std::string spv_messages = logger.getAllMessages(); + if (!spv_messages.empty()) { + LOG_INFO(Render_Vulkan, "SPIR-V conversion messages: {}", spv_messages); + } + + return CompileSPV(out_code, device); +} + +vk::ShaderModule CompileSPV(std::span code, vk::Device device) { + const vk::ShaderModuleCreateInfo shader_info = { + .codeSize = code.size() * sizeof(u32), + .pCode = code.data(), + }; + + try { + return device.createShaderModule(shader_info); + } catch (vk::SystemError& err) { + UNREACHABLE_MSG("{}", err.what()); + } + + return {}; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h new file mode 100644 index 00000000..3a86acf2 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_shader_util.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { + +/** + * @brief Creates a vulkan shader module from GLSL by converting it to SPIR-V using glslang. + * @param code The string containing GLSL code. + * @param stage The pipeline stage the shader will be used in. + * @param device The vulkan device handle. + */ +vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device); + +/** + * @brief Creates a vulkan shader module from SPIR-V bytecode. + * @param code The SPIR-V bytecode data. + * @param device The vulkan device handle + */ +vk::ShaderModule CompileSPV(std::span code, vk::Device device); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp new file mode 100644 index 00000000..0d9bedfe --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -0,0 +1,234 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/alignment.h" +#include "common/assert.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_stream_buffer.h" + +namespace Vulkan { + +namespace { + +std::string_view BufferTypeName(BufferType type) { + switch (type) { + case BufferType::Upload: + return "Upload"; + case BufferType::Download: + return "Download"; + case BufferType::Stream: + return "Stream"; + default: + return "Invalid"; + } +} + +vk::MemoryPropertyFlags MakePropertyFlags(BufferType type) { + switch (type) { + case BufferType::Upload: + return vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; + case BufferType::Download: + return vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent | vk::MemoryPropertyFlagBits::eHostCached; + case BufferType::Stream: + return vk::MemoryPropertyFlagBits::eDeviceLocal | vk::MemoryPropertyFlagBits::eHostVisible | + vk::MemoryPropertyFlagBits::eHostCoherent; + default: + UNREACHABLE_MSG("Unknown buffer type {}", static_cast(type)); + return vk::MemoryPropertyFlagBits::eHostVisible; + } +} + +static std::optional FindMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, + vk::MemoryPropertyFlags wanted) { + for (u32 i = 0; i < properties.memoryTypeCount; ++i) { + const auto flags = properties.memoryTypes[i].propertyFlags; + if ((flags & wanted) == wanted) { + return i; + } + } + return std::nullopt; +} + +/// Get the preferred host visible memory type. +u32 GetMemoryType(const vk::PhysicalDeviceMemoryProperties& properties, BufferType type) { + vk::MemoryPropertyFlags flags = MakePropertyFlags(type); + std::optional preferred_type = FindMemoryType(properties, flags); + + constexpr std::array remove_flags = { + vk::MemoryPropertyFlagBits::eHostCached, + vk::MemoryPropertyFlagBits::eHostCoherent, + }; + + for (u32 i = 0; i < remove_flags.size() && !preferred_type; i++) { + flags &= ~remove_flags[i]; + preferred_type = FindMemoryType(properties, flags); + } + ASSERT_MSG(preferred_type, "No suitable memory type found"); + return preferred_type.value(); +} + +constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; +constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; + +} // Anonymous namespace + +StreamBuffer::StreamBuffer(const Instance& instance_, Scheduler& scheduler_, + vk::BufferUsageFlags usage_, u64 size, BufferType type_) + : instance{instance_}, scheduler{scheduler_}, device{instance.GetDevice()}, + stream_buffer_size{size}, usage{usage_}, type{type_} { + CreateBuffers(size); + ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); + ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); +} + +StreamBuffer::~StreamBuffer() { + device.unmapMemory(memory); + device.destroyBuffer(buffer); + device.freeMemory(memory); +} + +std::tuple StreamBuffer::Map(u64 size, u64 alignment) { + if (!is_coherent && type == BufferType::Stream) { + size = Common::alignUp(size, instance.NonCoherentAtomSize()); + } + + ASSERT(size <= stream_buffer_size); + mapped_size = size; + + if (alignment > 0) { + offset = Common::alignUp(offset, alignment); + } + + bool invalidate{false}; + if (offset + size > stream_buffer_size) { + // The buffer would overflow, save the amount of used watches and reset the state. + invalidate = true; + invalidation_mark = current_watch_cursor; + current_watch_cursor = 0; + offset = 0; + + // Swap watches and reset waiting cursors. + std::swap(previous_watches, current_watches); + wait_cursor = 0; + wait_bound = 0; + } + + const u64 mapped_upper_bound = offset + size; + WaitPendingOperations(mapped_upper_bound); + + return std::make_tuple(mapped + offset, offset, invalidate); +} + +void StreamBuffer::Commit(u64 size) { + if (!is_coherent && type == BufferType::Stream) { + size = Common::alignUp(size, instance.NonCoherentAtomSize()); + } + + ASSERT_MSG(size <= mapped_size, "Reserved size {} is too small compared to {}", mapped_size, + size); + + const vk::MappedMemoryRange range = { + .memory = memory, + .offset = offset, + .size = size, + }; + + if (!is_coherent && type == BufferType::Download) { + device.invalidateMappedMemoryRanges(range); + } else if (!is_coherent) { + device.flushMappedMemoryRanges(range); + } + + offset += size; + + if (current_watch_cursor + 1 >= current_watches.size()) { + // Ensure that there are enough watches. + ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); + } + auto& watch = current_watches[current_watch_cursor++]; + watch.upper_bound = offset; + watch.tick = scheduler.CurrentTick(); +} + +void StreamBuffer::CreateBuffers(u64 prefered_size) { + const vk::Device device = instance.GetDevice(); + const auto memory_properties = instance.GetPhysicalDevice().getMemoryProperties(); + const u32 preferred_type = GetMemoryType(memory_properties, type); + const vk::MemoryType mem_type = memory_properties.memoryTypes[preferred_type]; + const u32 preferred_heap = mem_type.heapIndex; + is_coherent = + static_cast(mem_type.propertyFlags & vk::MemoryPropertyFlagBits::eHostCoherent); + + // Substract from the preferred heap size some bytes to avoid getting out of memory. + const vk::DeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size; + // As per DXVK's example, using `heap_size / 2` + const vk::DeviceSize allocable_size = heap_size / 2; + buffer = device.createBuffer({ + .size = std::min(prefered_size, allocable_size), + .usage = usage, + }); + + const auto requirements_chain = + device + .getBufferMemoryRequirements2( + {.buffer = buffer}); + + const auto& requirements = requirements_chain.get(); + const auto& dedicated_requirements = requirements_chain.get(); + + stream_buffer_size = static_cast(requirements.memoryRequirements.size); + + LOG_INFO(Render_Vulkan, "Creating {} buffer with size {} KiB with flags {}", + BufferTypeName(type), stream_buffer_size / 1024, + vk::to_string(mem_type.propertyFlags)); + + if (dedicated_requirements.prefersDedicatedAllocation) { + vk::StructureChain alloc_chain = + {}; + + auto& alloc_info = alloc_chain.get(); + alloc_info.allocationSize = requirements.memoryRequirements.size; + alloc_info.memoryTypeIndex = preferred_type; + + auto& dedicated_alloc_info = alloc_chain.get(); + dedicated_alloc_info.buffer = buffer; + + memory = device.allocateMemory(alloc_chain.get()); + } else { + memory = device.allocateMemory({ + .allocationSize = requirements.memoryRequirements.size, + .memoryTypeIndex = preferred_type, + }); + } + + device.bindBufferMemory(buffer, memory, 0); + mapped = reinterpret_cast(device.mapMemory(memory, 0, VK_WHOLE_SIZE)); + + if (instance.HasDebuggingToolAttached()) { + SetObjectName(device, buffer, "StreamBuffer({}): {} KiB {}", BufferTypeName(type), + stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags)); + SetObjectName(device, memory, "StreamBufferMemory({}): {} Kib {}", BufferTypeName(type), + stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags)); + } +} + +void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) { + watches.resize(watches.size() + grow_size); +} + +void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { + if (!invalidation_mark) { + return; + } + while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { + auto& watch = previous_watches[wait_cursor]; + wait_bound = watch.upper_bound; + scheduler.Wait(watch.tick); + ++wait_cursor; + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h new file mode 100644 index 00000000..d31a1f5d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { + +enum class BufferType : u32 { + Upload = 0, + Download = 1, + Stream = 2, +}; + +class Instance; +class Scheduler; + +class StreamBuffer final { + static constexpr std::size_t MAX_BUFFER_VIEWS = 3; + +public: + explicit StreamBuffer(const Instance& instance, Scheduler& scheduler, + vk::BufferUsageFlags usage, u64 size, + BufferType type = BufferType::Stream); + ~StreamBuffer(); + + /** + * Reserves a region of memory from the stream buffer. + * @param size Size to reserve. + * @returns A pair of a raw memory pointer (with offset added), and the buffer offset + */ + std::tuple Map(u64 size, u64 alignment); + + /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. + void Commit(u64 size); + + vk::Buffer Handle() const noexcept { + return buffer; + } + +private: + struct Watch { + u64 tick{}; + u64 upper_bound{}; + }; + + /// Creates Vulkan buffer handles committing the required the required memory. + void CreateBuffers(u64 prefered_size); + + /// Increases the amount of watches available. + void ReserveWatches(std::vector& watches, std::size_t grow_size); + + void WaitPendingOperations(u64 requested_upper_bound); + +private: + const Instance& instance; ///< Vulkan instance. + Scheduler& scheduler; ///< Command scheduler. + + vk::Device device; + vk::Buffer buffer; ///< Mapped buffer. + vk::DeviceMemory memory; ///< Memory allocation. + u8* mapped{}; ///< Pointer to the mapped memory + u64 stream_buffer_size{}; ///< Stream buffer size. + vk::BufferUsageFlags usage{}; + BufferType type; + + u64 offset{}; ///< Buffer iterator. + u64 mapped_size{}; ///< Size reserved for the current copy. + bool is_coherent{}; ///< True if the buffer is coherent + + std::vector current_watches; ///< Watches recorded in the current iteration. + std::size_t current_watch_cursor{}; ///< Count of watches, reset on invalidation. + std::optional invalidation_mark; ///< Number of watches used in the previous cycle. + + std::vector previous_watches; ///< Watches used in the previous iteration. + std::size_t wait_cursor{}; ///< Last watch being waited for completion. + u64 wait_bound{}; ///< Highest offset being watched for completion. +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp new file mode 100644 index 00000000..cb0bccda --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -0,0 +1,225 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "sdl_window.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_swapchain.h" + +namespace Vulkan { + +Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window) + : instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} { + FindPresentFormat(); + Create(window.getWidth(), window.getHeight(), surface); +} + +Swapchain::~Swapchain() { + Destroy(); + instance.GetInstance().destroySurfaceKHR(surface); +} + +void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { + width = width_; + height = height_; + surface = surface_; + needs_recreation = false; + + Destroy(); + + SetSurfaceProperties(); + + const std::array queue_family_indices = { + instance.GetGraphicsQueueFamilyIndex(), + instance.GetPresentQueueFamilyIndex(), + }; + + const bool exclusive = queue_family_indices[0] == queue_family_indices[1]; + const u32 queue_family_indices_count = exclusive ? 1u : 2u; + const vk::SharingMode sharing_mode = + exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent; + const vk::SwapchainCreateInfoKHR swapchain_info = { + .surface = surface, + .minImageCount = image_count, + .imageFormat = surface_format.format, + .imageColorSpace = surface_format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment | + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst, + .imageSharingMode = sharing_mode, + .queueFamilyIndexCount = queue_family_indices_count, + .pQueueFamilyIndices = queue_family_indices.data(), + .preTransform = transform, + .compositeAlpha = composite_alpha, + .presentMode = vk::PresentModeKHR::eMailbox, + .clipped = true, + .oldSwapchain = nullptr, + }; + + try { + swapchain = instance.GetDevice().createSwapchainKHR(swapchain_info); + } catch (vk::SystemError& err) { + LOG_CRITICAL(Render_Vulkan, "{}", err.what()); + UNREACHABLE(); + } + + SetupImages(); + RefreshSemaphores(); +} + +bool Swapchain::AcquireNextImage() { + vk::Device device = instance.GetDevice(); + vk::Result result = + device.acquireNextImageKHR(swapchain, std::numeric_limits::max(), + image_acquired[frame_index], VK_NULL_HANDLE, &image_index); + + switch (result) { + case vk::Result::eSuccess: + break; + case vk::Result::eSuboptimalKHR: + case vk::Result::eErrorSurfaceLostKHR: + case vk::Result::eErrorOutOfDateKHR: + needs_recreation = true; + break; + default: + LOG_CRITICAL(Render_Vulkan, "Swapchain acquire returned unknown result {}", + vk::to_string(result)); + UNREACHABLE(); + break; + } + + return !needs_recreation; +} + +void Swapchain::Present() { + if (needs_recreation) { + return; + } + + const vk::PresentInfoKHR present_info = { + .waitSemaphoreCount = 1, + .pWaitSemaphores = &present_ready[image_index], + .swapchainCount = 1, + .pSwapchains = &swapchain, + .pImageIndices = &image_index, + }; + + try { + [[maybe_unused]] vk::Result result = instance.GetPresentQueue().presentKHR(present_info); + } catch (vk::OutOfDateKHRError&) { + needs_recreation = true; + } catch (const vk::SystemError& err) { + LOG_CRITICAL(Render_Vulkan, "Swapchain presentation failed {}", err.what()); + UNREACHABLE(); + } + + frame_index = (frame_index + 1) % image_count; +} + +void Swapchain::FindPresentFormat() { + const auto formats = instance.GetPhysicalDevice().getSurfaceFormatsKHR(surface); + + // If there is a single undefined surface format, the device doesn't care, so we'll just use + // RGBA. + if (formats[0].format == vk::Format::eUndefined) { + surface_format.format = vk::Format::eR8G8B8A8Unorm; + surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; + return; + } + + // Try to find a suitable format. + for (const vk::SurfaceFormatKHR& sformat : formats) { + vk::Format format = sformat.format; + if (format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eB8G8R8A8Unorm) { + continue; + } + + surface_format.format = format; + surface_format.colorSpace = sformat.colorSpace; + return; + } + + UNREACHABLE_MSG("Unable to find required swapchain format!"); +} + +void Swapchain::SetSurfaceProperties() { + const vk::SurfaceCapabilitiesKHR capabilities = + instance.GetPhysicalDevice().getSurfaceCapabilitiesKHR(surface); + + extent = capabilities.currentExtent; + if (capabilities.currentExtent.width == std::numeric_limits::max()) { + extent.width = std::max(capabilities.minImageExtent.width, + std::min(capabilities.maxImageExtent.width, width)); + extent.height = std::max(capabilities.minImageExtent.height, + std::min(capabilities.maxImageExtent.height, height)); + } + + // Select number of images in swap chain, we prefer one buffer in the background to work on + image_count = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0) { + image_count = std::min(image_count, capabilities.maxImageCount); + } + + // Prefer identity transform if possible + transform = vk::SurfaceTransformFlagBitsKHR::eIdentity; + if (!(capabilities.supportedTransforms & transform)) { + transform = capabilities.currentTransform; + } + + // Opaque is not supported everywhere. + composite_alpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + if (!(capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque)) { + composite_alpha = vk::CompositeAlphaFlagBitsKHR::eInherit; + } +} + +void Swapchain::Destroy() { + vk::Device device = instance.GetDevice(); + if (swapchain) { + device.destroySwapchainKHR(swapchain); + } + for (u32 i = 0; i < image_count; i++) { + device.destroySemaphore(image_acquired[i]); + device.destroySemaphore(present_ready[i]); + } + image_acquired.clear(); + present_ready.clear(); +} + +void Swapchain::RefreshSemaphores() { + const vk::Device device = instance.GetDevice(); + image_acquired.resize(image_count); + present_ready.resize(image_count); + + for (vk::Semaphore& semaphore : image_acquired) { + semaphore = device.createSemaphore({}); + } + for (vk::Semaphore& semaphore : present_ready) { + semaphore = device.createSemaphore({}); + } + + if (instance.HasDebuggingToolAttached()) { + for (u32 i = 0; i < image_count; ++i) { + SetObjectName(device, image_acquired[i], "Swapchain Semaphore: image_acquired {}", i); + SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}", i); + } + } +} + +void Swapchain::SetupImages() { + vk::Device device = instance.GetDevice(); + images = device.getSwapchainImagesKHR(swapchain); + image_count = static_cast(images.size()); + + if (instance.HasDebuggingToolAttached()) { + for (u32 i = 0; i < image_count; ++i) { + SetObjectName(device, images[i], "Swapchain Image {}", i); + } + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h new file mode 100644 index 00000000..28ac3a9c --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Frontend { +class WindowSDL; +} + +namespace Vulkan { + +class Instance; +class Scheduler; + +class Swapchain { +public: + explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window); + ~Swapchain(); + + /// Creates (or recreates) the swapchain with a given size. + void Create(u32 width, u32 height, vk::SurfaceKHR surface); + + /// Acquires the next image in the swapchain. + bool AcquireNextImage(); + + /// Presents the current image and move to the next one + void Present(); + + vk::SurfaceKHR GetSurface() const { + return surface; + } + + vk::Image Image() const { + return images[image_index]; + } + + vk::SurfaceFormatKHR GetSurfaceFormat() const { + return surface_format; + } + + vk::SwapchainKHR GetHandle() const { + return swapchain; + } + + u32 GetWidth() const { + return width; + } + + u32 GetHeight() const { + return height; + } + + u32 GetImageCount() const { + return image_count; + } + + u32 GetFrameIndex() const { + return frame_index; + } + + vk::Extent2D GetExtent() const { + return extent; + } + + [[nodiscard]] vk::Semaphore GetImageAcquiredSemaphore() const { + return image_acquired[frame_index]; + } + + [[nodiscard]] vk::Semaphore GetPresentReadySemaphore() const { + return present_ready[image_index]; + } + +private: + /// Selects the best available swapchain image format + void FindPresentFormat(); + + /// Sets the surface properties according to device capabilities + void SetSurfaceProperties(); + + /// Destroys current swapchain resources + void Destroy(); + + /// Performs creation of image views and framebuffers from the swapchain images + void SetupImages(); + + /// Creates the image acquired and present ready semaphores + void RefreshSemaphores(); + +private: + const Instance& instance; + vk::SwapchainKHR swapchain{}; + vk::SurfaceKHR surface{}; + vk::SurfaceFormatKHR surface_format; + vk::Extent2D extent; + vk::SurfaceTransformFlagBitsKHR transform; + vk::CompositeAlphaFlagBitsKHR composite_alpha; + std::vector images; + std::vector image_acquired; + std::vector present_ready; + u32 width = 0; + u32 height = 0; + u32 image_count = 0; + u32 image_index = 0; + u32 frame_index = 0; + bool needs_recreation = true; +}; + +} // namespace Vulkan diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp new file mode 100644 index 00000000..e32ddf9b --- /dev/null +++ b/src/video_core/texture_cache/image.cpp @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/config.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/image.h" + +#include + +namespace VideoCore { + +using namespace Vulkan; +using VideoOutFormat = Libraries::VideoOut::PixelFormat; +using Libraries::VideoOut::TilingMode; + +[[nodiscard]] vk::Format ConvertPixelFormat(const VideoOutFormat format) { + switch (format) { + case VideoOutFormat::A8R8G8B8Srgb: + return vk::Format::eB8G8R8A8Srgb; + case VideoOutFormat::A8B8G8R8Srgb: + return vk::Format::eA8B8G8R8SrgbPack32; + case VideoOutFormat::A2R10G10B10: + case VideoOutFormat::A2R10G10B10Srgb: + return vk::Format::eA2R10G10B10UnormPack32; + default: + break; + } + UNREACHABLE_MSG("Unknown format={}", static_cast(format)); + return {}; +} + +[[nodiscard]] vk::ImageUsageFlags ImageUsageFlags(const vk::Format format) { + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eTransferSrc | + vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eSampled; + if (false /*&& IsDepthStencilFormat(format)*/) { + usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + } else { + // usage |= vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eStorage; + } + return usage; +} + +ImageInfo::ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group) noexcept { + const auto& attrib = group.attrib; + is_tiled = attrib.tiling_mode == TilingMode::Tile; + pixel_format = ConvertPixelFormat(attrib.pixel_format); + type = vk::ImageType::e2D; + size.width = attrib.width; + size.height = attrib.height; + pitch = attrib.tiling_mode == TilingMode::Linear ? size.width : (size.width + 127) >> 7; +} + +UniqueImage::UniqueImage(vk::Device device_, VmaAllocator allocator_) + : device{device_}, allocator{allocator_} {} + +UniqueImage::~UniqueImage() { + if (image) { + vmaDestroyImage(allocator, image, allocation); + } +} + +void UniqueImage::Create(const vk::ImageCreateInfo& image_ci) { + const VmaAllocationCreateInfo alloc_info = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + .requiredFlags = 0, + .preferredFlags = 0, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + }; + + const VkImageCreateInfo image_ci_unsafe = static_cast(image_ci); + VkImage unsafe_image{}; + VkResult result = vmaCreateImage(allocator, &image_ci_unsafe, &alloc_info, &unsafe_image, + &allocation, nullptr); + ASSERT_MSG(result == VK_SUCCESS, "Failed allocating image with error {}", + vk::to_string(vk::Result{result})); + image = vk::Image{unsafe_image}; +} + +Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, + const ImageInfo& info_, VAddr cpu_addr) + : instance{&instance_}, scheduler{&scheduler_}, info{info_}, + image{instance->GetDevice(), instance->GetAllocator()}, cpu_addr{cpu_addr} { + vk::ImageCreateFlags flags{}; + if (info.type == vk::ImageType::e2D && info.resources.layers >= 6 && + info.size.width == info.size.height) { + flags |= vk::ImageCreateFlagBits::eCubeCompatible; + } + if (info.type == vk::ImageType::e3D) { + flags |= vk::ImageCreateFlagBits::e2DArrayCompatible; + } + const vk::ImageCreateInfo image_ci = { + .flags = flags, + .imageType = info.type, + .format = info.pixel_format, + .extent{ + .width = info.size.width, + .height = info.size.height, + .depth = info.size.depth, + }, + .mipLevels = static_cast(info.resources.levels), + .arrayLayers = static_cast(info.resources.layers), + .tiling = vk::ImageTiling::eOptimal, + .usage = ImageUsageFlags(info.pixel_format), + .initialLayout = vk::ImageLayout::eUndefined, + }; + + image.Create(image_ci); + + const vk::Image handle = image; + scheduler->Record([handle](vk::CommandBuffer cmdbuf) { + const vk::ImageMemoryBarrier init_barrier = { + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eNone, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eGeneral, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = handle, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, + vk::PipelineStageFlagBits::eTopOfPipe, + vk::DependencyFlagBits::eByRegion, {}, {}, init_barrier); + }); + + const bool is_32bpp = info.pixel_format == vk::Format::eB8G8R8A8Srgb || + info.pixel_format == vk::Format::eA8B8G8R8SrgbPack32; + ASSERT(info.is_tiled && is_32bpp); + + if (Config::isNeoMode()) { + guest_size_bytes = info.pitch * 128 * ((info.size.height + 127) & (~127)) * 4; + } else { + guest_size_bytes = info.pitch * 128 * ((info.size.height + 63) & (~63)) * 4; + } + cpu_addr_end = cpu_addr + guest_size_bytes; +} + +Image::~Image() = default; + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h new file mode 100644 index 00000000..cb8ff052 --- /dev/null +++ b/src/video_core/texture_cache/image.h @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/enum.h" +#include "common/types.h" +#include "core/libraries/videoout/buffer.h" +#include "video_core/pixel_format.h" +#include "video_core/renderer_vulkan/vk_common.h" +#include "video_core/texture_cache/types.h" + +namespace Vulkan { +class Instance; +class Scheduler; +} // namespace Vulkan + +VK_DEFINE_HANDLE(VmaAllocation) +VK_DEFINE_HANDLE(VmaAllocator) + +namespace VideoCore { + +enum ImageFlagBits : u32 { + CpuModified = 1 << 2, ///< Contents have been modified from the CPU + GpuModified = 1 << 3, ///< Contents have been modified from the GPU + Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU + Registered = 1 << 6, ///< True when the image is registered + Picked = 1 << 7, ///< Temporary flag to mark the image as picked +}; +DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) + +struct ImageInfo { + ImageInfo() = default; + explicit ImageInfo(const Libraries::VideoOut::BufferAttributeGroup& group) noexcept; + + bool is_tiled = false; + vk::Format pixel_format = vk::Format::eUndefined; + vk::ImageType type = vk::ImageType::e1D; + SubresourceExtent resources; + Extent3D size{1, 1, 1}; + u32 pitch; +}; + +struct Handle { + VmaAllocation allocation; + VkImage image; + + Handle() = default; + + Handle(Handle&& other) + : image{std::exchange(other.image, VK_NULL_HANDLE)}, + allocation{std::exchange(other.allocation, VK_NULL_HANDLE)} {} + + Handle& operator=(Handle&& other) { + image = std::exchange(other.image, VK_NULL_HANDLE); + allocation = std::exchange(other.allocation, VK_NULL_HANDLE); + return *this; + } +}; + +struct UniqueImage { + explicit UniqueImage(vk::Device device, VmaAllocator allocator); + ~UniqueImage(); + + UniqueImage(const UniqueImage&) = delete; + UniqueImage& operator=(const UniqueImage&) = delete; + + UniqueImage(UniqueImage&& other) : image{std::exchange(other.image, VK_NULL_HANDLE)} {} + UniqueImage& operator=(UniqueImage&& other) { + image = std::exchange(other.image, VK_NULL_HANDLE); + return *this; + } + + void Create(const vk::ImageCreateInfo& image_ci); + + operator vk::Image() const { + return image; + } + +private: + vk::Device device; + VmaAllocator allocator; + VmaAllocation allocation; + vk::Image image{}; +}; + +struct Image { + explicit Image(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + const ImageInfo& info, VAddr cpu_addr); + ~Image(); + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + + Image(Image&&) = default; + Image& operator=(Image&&) = default; + + [[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept { + const VAddr overlap_end = overlap_cpu_addr + overlap_size; + return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end; + } + + const Vulkan::Instance* instance; + Vulkan::Scheduler* scheduler; + ImageInfo info; + UniqueImage image; + vk::ImageAspectFlags aspect_mask; + u32 guest_size_bytes = 0; + size_t channel = 0; + ImageFlagBits flags = ImageFlagBits::CpuModified; + VAddr cpu_addr = 0; + VAddr cpu_addr_end = 0; + u64 modification_tick = 0; +}; + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp new file mode 100644 index 00000000..e0822f64 --- /dev/null +++ b/src/video_core/texture_cache/image_view.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/texture_cache/image_view.h" + +namespace VideoCore { + +[[nodiscard]] vk::ImageViewType ConvertImageViewType(const ImageViewType type) { + switch (type) { + case ImageViewType::e1D: + return vk::ImageViewType::e1D; + case ImageViewType::e2D: + return vk::ImageViewType::e2D; + case ImageViewType::e3D: + return vk::ImageViewType::e3D; + case ImageViewType::Buffer: + break; + default: + break; + } + UNREACHABLE_MSG("Invalid image type={}", static_cast(type)); + return {}; +} + +[[nodiscard]] vk::Format ConvertPixelFormat(const PixelFormat format) { + switch (format) { + default: + break; + } + UNREACHABLE_MSG("Unknown format={}", static_cast(format)); + return {}; +} + +ImageView::ImageView(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + const ImageViewInfo& info_, vk::Image image) + : info{info_} { + const vk::ImageViewCreateInfo image_view_ci = { + .image = image, + .viewType = ConvertImageViewType(info.type), + .format = ConvertPixelFormat(info.format), + .components{ + .r = vk::ComponentSwizzle::eIdentity, + .g = vk::ComponentSwizzle::eIdentity, + .b = vk::ComponentSwizzle::eIdentity, + .a = vk::ComponentSwizzle::eIdentity, + }, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0U, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + image_view = instance.GetDevice().createImageViewUnique(image_view_ci); +} + +ImageView::~ImageView() = default; + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image_view.h b/src/video_core/texture_cache/image_view.h new file mode 100644 index 00000000..0d250e44 --- /dev/null +++ b/src/video_core/texture_cache/image_view.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "video_core/pixel_format.h" +#include "video_core/renderer_vulkan/vk_common.h" +#include "video_core/texture_cache/types.h" + +namespace Vulkan { +class Instance; +class Scheduler; +} // namespace Vulkan + +namespace VideoCore { + +enum class ImageViewType : u32 { + e1D, + e2D, + Cube, + e3D, + e1DArray, + e2DArray, + CubeArray, + Buffer, +}; + +enum class SwizzleSource : u32 { + Zero = 0, + One = 1, + R = 2, + G = 3, + B = 4, + A = 5, +}; + +struct ImageViewInfo { + ImageViewType type{}; + PixelFormat format{}; + SubresourceRange range; + u8 x_source = static_cast(SwizzleSource::R); + u8 y_source = static_cast(SwizzleSource::G); + u8 z_source = static_cast(SwizzleSource::B); + u8 w_source = static_cast(SwizzleSource::A); +}; + +class ImageView { + explicit ImageView(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, + const ImageViewInfo& info, vk::Image image); + ~ImageView(); + + ImageId image_id{}; + Extent3D size{0, 0, 0}; + ImageViewInfo info{}; + vk::UniqueImageView image_view; +}; + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h new file mode 100644 index 00000000..6b2e7553 --- /dev/null +++ b/src/video_core/texture_cache/slot_vector.h @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/types.h" + +namespace VideoCore { + +struct SlotId { + static constexpr u32 INVALID_INDEX = std::numeric_limits::max(); + + constexpr auto operator<=>(const SlotId&) const noexcept = default; + + constexpr explicit operator bool() const noexcept { + return index != INVALID_INDEX; + } + + u32 index = INVALID_INDEX; +}; + +template +class SlotVector { + constexpr static std::size_t InitialCapacity = 1024; + +public: + SlotVector() { + Reserve(InitialCapacity); + } + + ~SlotVector() noexcept { + std::size_t index = 0; + for (u64 bits : stored_bitset) { + for (std::size_t bit = 0; bits; ++bit, bits >>= 1) { + if ((bits & 1) != 0) { + values[index + bit].object.~T(); + } + } + index += 64; + } + delete[] values; + } + + [[nodiscard]] T& operator[](SlotId id) noexcept { + ValidateIndex(id); + return values[id.index].object; + } + + [[nodiscard]] const T& operator[](SlotId id) const noexcept { + ValidateIndex(id); + return values[id.index].object; + } + + template + [[nodiscard]] SlotId insert(Args&&... args) noexcept { + const u32 index = FreeValueIndex(); + new (&values[index].object) T(std::forward(args)...); + SetStorageBit(index); + + return SlotId{index}; + } + + template + [[nodiscard]] SlotId swap_and_insert(SlotId existing_id, Args&&... args) noexcept { + const u32 index = FreeValueIndex(); + T& existing_value = values[existing_id.index].object; + + new (&values[index].object) T(std::move(existing_value)); + existing_value.~T(); + new (&values[existing_id.index].object) T(std::forward(args)...); + SetStorageBit(index); + + return SlotId{index}; + } + + void erase(SlotId id) noexcept { + values[id.index].object.~T(); + free_list.push_back(id.index); + ResetStorageBit(id.index); + } + + std::size_t size() const noexcept { + return values_capacity - free_list.size(); + } + +private: + struct NonTrivialDummy { + NonTrivialDummy() noexcept {} + }; + + union Entry { + Entry() noexcept : dummy{} {} + ~Entry() noexcept {} + + NonTrivialDummy dummy; + T object; + }; + + void SetStorageBit(u32 index) noexcept { + stored_bitset[index / 64] |= u64(1) << (index % 64); + } + + void ResetStorageBit(u32 index) noexcept { + stored_bitset[index / 64] &= ~(u64(1) << (index % 64)); + } + + bool ReadStorageBit(u32 index) noexcept { + return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0; + } + + void ValidateIndex([[maybe_unused]] SlotId id) const noexcept { + DEBUG_ASSERT(id); + DEBUG_ASSERT(id.index / 64 < stored_bitset.size()); + DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0); + } + + [[nodiscard]] u32 FreeValueIndex() noexcept { + if (free_list.empty()) { + Reserve(values_capacity ? (values_capacity << 1) : 1); + } + + const u32 free_index = free_list.back(); + free_list.pop_back(); + return free_index; + } + + void Reserve(std::size_t new_capacity) noexcept { + Entry* const new_values = new Entry[new_capacity]; + std::size_t index = 0; + for (u64 bits : stored_bitset) { + for (std::size_t bit = 0; bits; ++bit, bits >>= 1) { + const std::size_t i = index + bit; + if ((bits & 1) == 0) { + continue; + } + T& old_value = values[i].object; + new (&new_values[i].object) T(std::move(old_value)); + old_value.~T(); + } + index += 64; + } + + stored_bitset.resize((new_capacity + 63) / 64); + + const std::size_t old_free_size = free_list.size(); + free_list.resize(old_free_size + (new_capacity - values_capacity)); + std::iota(free_list.begin() + old_free_size, free_list.end(), + static_cast(values_capacity)); + + delete[] values; + values = new_values; + values_capacity = new_capacity; + } + + Entry* values = nullptr; + std::size_t values_capacity = 0; + + std::vector stored_bitset; + std::vector free_list; +}; + +} // namespace VideoCore + +template <> +struct std::hash { + std::size_t operator()(const VideoCore::SlotId& id) const noexcept { + return std::hash{}(id.index); + } +}; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp new file mode 100644 index 00000000..5939a8c7 --- /dev/null +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/config.h" +#include "core/libraries/videoout/buffer.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/texture_cache/tile_manager.h" + +#ifndef _WIN64 +#include +#include +#endif + +namespace VideoCore { + +static TextureCache* g_texture_cache = nullptr; + +#ifndef _WIN64 +void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) { + ucontext_t* ctx = reinterpret_cast(raw_context); + const VAddr address = reinterpret_cast(info->si_addr); + if (ctx->uc_mcontext.gregs[REG_ERR] & 0x2) { + g_texture_cache->OnCpuWrite(address); + } else { + // Read not supported! + UNREACHABLE(); + } +} +#endif + +static constexpr u64 StreamBufferSize = 128_MB; + +TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_) + : instance{instance_}, scheduler{scheduler_}, staging{instance, scheduler, + vk::BufferUsageFlagBits::eTransferSrc, + StreamBufferSize, + Vulkan::BufferType::Upload} { + +#ifndef _WIN64 + sigset_t signal_mask; + sigemptyset(&signal_mask); + sigaddset(&signal_mask, SIGSEGV); + + using HandlerType = decltype(sigaction::sa_sigaction); + + struct sigaction guest_access_fault {}; + guest_access_fault.sa_flags = SA_SIGINFO | SA_ONSTACK; + guest_access_fault.sa_sigaction = &GuestFaultSignalHandler; + guest_access_fault.sa_mask = signal_mask; + sigaction(SIGSEGV, &guest_access_fault, nullptr); +#endif + g_texture_cache = this; +} + +TextureCache::~TextureCache() = default; + +void TextureCache::OnCpuWrite(VAddr address) { + const VAddr address_aligned = address & ~((1 << PageBits) - 1); + ForEachImageInRegion(address_aligned, 1 << PageBits, [&](ImageId image_id, Image& image) { + // Ensure image is reuploaded when accessed again. + image.flags |= ImageFlagBits::CpuModified; + // Untrack image, so the range is unprotected and the guest can write freely. + UntrackImage(image, image_id); + }); +} + +Image& TextureCache::FindDisplayBuffer(const Libraries::VideoOut::BufferAttributeGroup& group, + VAddr cpu_address) { + boost::container::small_vector image_ids; + ForEachImageInRegion(cpu_address, group.size_in_bytes, [&](ImageId image_id, Image& image) { + if (image.cpu_addr == cpu_address) { + image_ids.push_back(image_id); + } + }); + + ASSERT_MSG(image_ids.size() <= 1, "Overlapping framebuffers not allowed!"); + + ImageId image_id{}; + if (image_ids.empty()) { + image_id = slot_images.insert(instance, scheduler, ImageInfo{group}, cpu_address); + RegisterImage(image_id); + } else { + image_id = image_ids[0]; + } + + Image& image = slot_images[image_id]; + if (True(image.flags & ImageFlagBits::CpuModified)) { + RefreshImage(image); + TrackImage(image, image_id); + } + + return image; +} + +void TextureCache::RefreshImage(Image& image) { + // Mark image as validated. + image.flags &= ~ImageFlagBits::CpuModified; + + // Upload data to the staging buffer. + const auto [data, offset, _] = staging.Map(image.guest_size_bytes, 0); + ConvertTileToLinear(data, reinterpret_cast(image.cpu_addr), image.info.size.width, + image.info.size.height, Config::isNeoMode()); + staging.Commit(image.guest_size_bytes); + + // Copy to the image. + const vk::BufferImageCopy image_copy = { + .bufferOffset = offset, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {image.info.size.width, image.info.size.height, 1}, + }; + + const vk::Buffer src_buffer = staging.Handle(); + const vk::Image dst_image = image.image; + scheduler.Record([src_buffer, dst_image, image_copy](vk::CommandBuffer cmdbuf) { + cmdbuf.copyBufferToImage(src_buffer, dst_image, vk::ImageLayout::eGeneral, image_copy); + }); +} + +void TextureCache::RegisterImage(ImageId image_id) { + Image& image = slot_images[image_id]; + ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), + "Trying to register an already registered image"); + image.flags |= ImageFlagBits::Registered; + ForEachPage(image.cpu_addr, image.guest_size_bytes, + [this, image_id](u64 page) { page_table[page].push_back(image_id); }); +} + +void TextureCache::UnregisterImage(ImageId image_id) { + Image& image = slot_images[image_id]; + ASSERT_MSG(True(image.flags & ImageFlagBits::Registered), + "Trying to unregister an already registered image"); + image.flags &= ~ImageFlagBits::Registered; + ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) { + const auto page_it = page_table.find(page); + if (page_it == page_table.end()) { + ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << PageBits); + return; + } + auto& image_ids = page_it.value(); + const auto vector_it = std::ranges::find(image_ids, image_id); + if (vector_it == image_ids.end()) { + ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", page << PageBits); + return; + } + image_ids.erase(vector_it); + }); + slot_images.erase(image_id); +} + +void TextureCache::TrackImage(Image& image, ImageId image_id) { + if (True(image.flags & ImageFlagBits::Tracked)) { + return; + } + image.flags |= ImageFlagBits::Tracked; + UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1); +} + +void TextureCache::UntrackImage(Image& image, ImageId image_id) { + if (False(image.flags & ImageFlagBits::Tracked)) { + return; + } + image.flags &= ~ImageFlagBits::Tracked; + UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1); +} + +void TextureCache::UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta) { + const u64 num_pages = ((addr + size - 1) >> PageBits) - (addr >> PageBits) + 1; + const u64 page_start = addr >> PageBits; + const u64 page_end = page_start + num_pages; + + const auto pages_interval = + decltype(cached_pages)::interval_type::right_open(page_start, page_end); + if (delta > 0) { + cached_pages.add({pages_interval, delta}); + } + + const auto& range = cached_pages.equal_range(pages_interval); + for (const auto& [range, count] : boost::make_iterator_range(range)) { + const auto interval = range & pages_interval; + const VAddr interval_start_addr = boost::icl::first(interval) << PageBits; + const VAddr interval_end_addr = boost::icl::last_next(interval) << PageBits; + const u32 interval_size = interval_end_addr - interval_start_addr; +#ifndef _WIN64 + void* addr = reinterpret_cast(interval_start_addr); + if (delta > 0 && count == delta) { + mprotect(addr, interval_size, PROT_NONE); + } else if (delta < 0 && count == -delta) { + mprotect(addr, interval_size, PROT_READ | PROT_WRITE); + } else { + ASSERT(count >= 0); + } +#endif + } + + if (delta < 0) { + cached_pages.add({pages_interval, delta}); + } +} + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h new file mode 100644 index 00000000..c9c4a1f0 --- /dev/null +++ b/src/video_core/texture_cache/texture_cache.h @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "video_core/renderer_vulkan/vk_stream_buffer.h" +#include "video_core/texture_cache/image.h" +#include "video_core/texture_cache/slot_vector.h" + +namespace Core::Libraries::VideoOut { +struct BufferAttributeGroup; +} + +namespace VideoCore { + +class TextureCache { + static constexpr u64 PageBits = 14; + +public: + explicit TextureCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); + ~TextureCache(); + + /// Invalidates any image in the logical page range. + void OnCpuWrite(VAddr address); + + /// Retrieves the image handle of the image with the provided attributes and address. + Image& FindDisplayBuffer(const Libraries::VideoOut::BufferAttributeGroup& attribute, + VAddr cpu_address); + +private: + /// Iterate over all page indices in a range + template + static void ForEachPage(PAddr addr, size_t size, Func&& func) { + static constexpr bool RETURNS_BOOL = std::is_same_v, bool>; + const u64 page_end = (addr + size - 1) >> PageBits; + for (u64 page = addr >> PageBits; page <= page_end; ++page) { + if constexpr (RETURNS_BOOL) { + if (func(page)) { + break; + } + } else { + func(page); + } + } + } + + template + void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) { + using FuncReturn = typename std::invoke_result::type; + static constexpr bool BOOL_BREAK = std::is_same_v; + boost::container::small_vector images; + ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) { + const auto it = page_table.find(page); + if (it == page_table.end()) { + if constexpr (BOOL_BREAK) { + return false; + } else { + return; + } + } + for (const ImageId image_id : it->second) { + Image& image = slot_images[image_id]; + if (image.flags & ImageFlagBits::Picked) { + continue; + } + image.flags |= ImageFlagBits::Picked; + images.push_back(image_id); + if constexpr (BOOL_BREAK) { + if (func(image_id, image)) { + return true; + } + } else { + func(image_id, image); + } + } + if constexpr (BOOL_BREAK) { + return false; + } + }); + for (const ImageId image_id : images) { + slot_images[image_id].flags &= ~ImageFlagBits::Picked; + } + } + + /// Create an image from the given parameters + [[nodiscard]] ImageId InsertImage(const ImageInfo& info, VAddr cpu_addr); + + /// Reuploads image contents. + void RefreshImage(Image& image); + + /// Register image in the page table + void RegisterImage(ImageId image); + + /// Unregister image from the page table + void UnregisterImage(ImageId image); + + /// Track CPU reads and writes for image + void TrackImage(Image& image, ImageId image_id); + + /// Stop tracking CPU reads and writes for image + void UntrackImage(Image& image, ImageId image_id); + + /// Increase/decrease the number of surface in pages touching the specified region + void UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta); + +private: + const Vulkan::Instance& instance; + Vulkan::Scheduler& scheduler; + Vulkan::StreamBuffer staging; + SlotVector slot_images; + tsl::robin_pg_map> page_table; + boost::icl::interval_map cached_pages; +}; + +} // namespace VideoCore diff --git a/src/core/PS4/GPU/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp similarity index 87% rename from src/core/PS4/GPU/tile_manager.cpp rename to src/video_core/texture_cache/tile_manager.cpp index 4d8c2cfc..8cd21640 100644 --- a/src/core/PS4/GPU/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -1,23 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include "common/singleton.h" -#include "core/PS4/GPU/tile_manager.h" +#include +#include "video_core/texture_cache/tile_manager.h" -namespace GPU { +namespace VideoCore { static u32 IntLog2(u32 i) { return 31 - __builtin_clz(i | 1u); } -class TileManager { -public: - TileManager() {} - virtual ~TileManager() {} - std::mutex m_mutex; -}; - class TileManager32 { public: u32 m_macro_tile_height = 0; @@ -148,14 +140,10 @@ public: } }; -void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool is_neo) { +void ConvertTileToLinear(u8* dst, const u8* src, u32 width, u32 height, bool is_neo) { TileManager32 t; t.Init(width, height, is_neo); - auto* g_TileManager = Common::Singleton::Instance(); - - std::scoped_lock lock{g_TileManager->m_mutex}; - for (u32 y = 0; y < height; y++) { u32 x = 0; u64 linear_offset = y * width * 4; @@ -163,16 +151,14 @@ void convertTileToLinear(void* dst, const void* src, u32 width, u32 height, bool for (; x + 1 < width; x += 2) { auto tiled_offset = t.getTiledOffs(x, y, is_neo); - *reinterpret_cast(static_cast(dst) + linear_offset) = - *reinterpret_cast(static_cast(src) + tiled_offset); + std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u64)); linear_offset += 8; } if (x < width) { auto tiled_offset = t.getTiledOffs(x, y, is_neo); - - *reinterpret_cast(static_cast(dst) + linear_offset) = - *reinterpret_cast(static_cast(src) + tiled_offset); + std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u32)); } } } -} // namespace GPU + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/tile_manager.h b/src/video_core/texture_cache/tile_manager.h new file mode 100644 index 00000000..7903114e --- /dev/null +++ b/src/video_core/texture_cache/tile_manager.h @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace VideoCore { + +/// Converts tiled texture data to linear format. +void ConvertTileToLinear(u8* dst, const u8* src, u32 width, u32 height, bool neo); + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h new file mode 100644 index 00000000..70aaddc5 --- /dev/null +++ b/src/video_core/texture_cache/types.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "video_core/texture_cache/slot_vector.h" + +namespace VideoCore { + +using ImageId = SlotId; +using ImageViewId = SlotId; + +struct Offset2D { + s32 x; + s32 y; +}; + +struct Offset3D { + s32 x; + s32 y; + s32 z; +}; + +struct Region2D { + Offset2D start; + Offset2D end; +}; + +struct Extent2D { + u32 width; + u32 height; +}; + +struct Extent3D { + u32 width; + u32 height; + u32 depth; +}; + +struct SubresourceLayers { + s32 base_level = 0; + s32 base_layer = 0; + s32 num_layers = 1; +}; + +struct SubresourceBase { + s32 level = 0; + s32 layer = 0; +}; + +struct SubresourceExtent { + s32 levels = 1; + s32 layers = 1; +}; + +struct SubresourceRange { + SubresourceBase base; + SubresourceExtent extent; +}; + +struct ImageCopy { + SubresourceLayers src_subresource; + SubresourceLayers dst_subresource; + Offset3D src_offset; + Offset3D dst_offset; + Extent3D extent; +}; + +struct BufferImageCopy { + std::size_t buffer_offset; + std::size_t buffer_size; + u32 buffer_row_length; + u32 buffer_image_height; + SubresourceLayers image_subresource; + Offset3D image_offset; + Extent3D image_extent; +}; + +struct BufferCopy { + u64 src_offset; + u64 dst_offset; + std::size_t size; +}; + +} // namespace VideoCore diff --git a/src/vulkan_util.cpp b/src/vulkan_util.cpp index 82865472..bc4bb734 100644 --- a/src/vulkan_util.cpp +++ b/src/vulkan_util.cpp @@ -15,7 +15,7 @@ #include void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) { - Emu::VulkanExt ext; + /*Emu::VulkanExt ext; vulkanGetInstanceExtensions(&ext); VkApplicationInfo app_info{}; @@ -68,12 +68,13 @@ void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) { ASSERT_MSG(ctx->m_graphic_ctx.m_device, "Can't create vulkan device"); vulkanCreateQueues(&ctx->m_graphic_ctx, queues); - ctx->swapchain = vulkanCreateSwapchain(&ctx->m_graphic_ctx, 2); + ctx->swapchain = vulkanCreateSwapchain(&ctx->m_graphic_ctx, 2);*/ } Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics::GraphicCtx* ctx, u32 image_count) { - auto window_ctx = Common::Singleton::Instance(); + return {}; + /*auto window_ctx = Common::Singleton::Instance(); const auto& capabilities = window_ctx->m_surface_capabilities.capabilities; Emu::VulkanSwapchain s{}; @@ -166,7 +167,7 @@ Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics result = vkCreateFence(ctx->m_device, &fence_info, nullptr, &s.present_complete_fence); ASSERT_MSG(result == VK_SUCCESS, "Can't create vulkan fence"); - return s; + return s;*/ } void Graphics::Vulkan::vulkanCreateQueues(HLE::Libs::Graphics::GraphicCtx* ctx, @@ -195,7 +196,7 @@ VkDevice Graphics::Vulkan::vulkanCreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const Emu::VulkanExt* r, const Emu::VulkanQueues& queues, const std::vector& device_extensions) { - std::vector queue_create_info(queues.family_count); + /*std::vector queue_create_info(queues.family_count); std::vector> queue_priority(queues.family_count); uint32_t queue_create_info_num = 0; @@ -237,10 +238,10 @@ VkDevice Graphics::Vulkan::vulkanCreateDevice(VkPhysicalDevice physical_device, vkCreateDevice(physical_device, &create_info, nullptr, &device); - return device; + return device;*/ } void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) { - u32 required_extensions_count = 0; + /*u32 required_extensions_count = 0; u32 available_extensions_count = 0; u32 available_layers_count = 0; ext->required_extensions = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count); @@ -270,14 +271,14 @@ void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) { LOG_INFO(Render_Vulkan, "Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}", l.layerName, l.specVersion, l.implementationVersion, l.description); - } + }*/ } void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice( VkInstance instance, VkSurfaceKHR surface, const std::vector& device_extensions, Emu::VulkanSurfaceCapabilities* out_capabilities, VkPhysicalDevice* out_device, Emu::VulkanQueues* out_queues) { - u32 count_devices = 0; + /*u32 count_devices = 0; vkEnumeratePhysicalDevices(instance, &count_devices, nullptr); std::vector devices(count_devices); @@ -306,14 +307,14 @@ void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice( found_best_queues = qs; } *out_device = found_best_device; - *out_queues = found_best_queues; + *out_queues = found_best_queues;*/ } Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device, VkSurfaceKHR surface) { Emu::VulkanQueues qs; - u32 queue_family_count = 0; + /*u32 queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr); std::vector queue_families(queue_family_count); vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data()); @@ -391,14 +392,15 @@ Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device, } index++; } - } + }*/ return qs; } void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, Emu::VulkanSurfaceCapabilities* surfaceCap) { - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surfaceCap->capabilities); + /*vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, + &surfaceCap->capabilities); uint32_t formats_count = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, nullptr); @@ -426,7 +428,7 @@ void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_de surfaceCap->is_format_unorm_bgra32 = true; break; } - } + }*/ } static void set_image_layout(VkCommandBuffer buffer, HLE::Libs::Graphics::VulkanImage* dst_image, @@ -484,8 +486,8 @@ static void set_image_layout(VkCommandBuffer buffer, HLE::Libs::Graphics::Vulkan VkPipelineStageFlags src_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkPipelineStageFlags dest_stages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - vkCmdPipelineBarrier(buffer, src_stages, dest_stages, 0, 0, nullptr, 0, nullptr, 1, - &imageMemoryBarrier); + // vkCmdPipelineBarrier(buffer, src_stages, dest_stages, 0, 0, nullptr, 0, nullptr, 1, + // &imageMemoryBarrier); dst_image->layout = new_image_layout; } @@ -528,9 +530,9 @@ void Graphics::Vulkan::vulkanBlitImage(GPU::CommandBuffer* buffer, region.dstOffsets[1].y = static_cast(dst_swapchain->swapchain_extent.height); region.dstOffsets[1].z = 1; - vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, - VK_FILTER_LINEAR); + // vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + // swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, + // VK_FILTER_LINEAR); set_image_layout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, @@ -548,10 +550,10 @@ void Graphics::Vulkan::vulkanFillImage(HLE::Libs::Graphics::GraphicCtx* ctx, vulkanCreateBuffer(ctx, size, &staging_buffer); void* data = nullptr; - vkMapMemory(ctx->m_device, staging_buffer.memory.memory, staging_buffer.memory.offset, - staging_buffer.memory.requirements.size, 0, &data); - std::memcpy(data, src_data, size); - vkUnmapMemory(ctx->m_device, staging_buffer.memory.memory); + // vkMapMemory(ctx->m_device, staging_buffer.memory.memory, staging_buffer.memory.offset, + // staging_buffer.memory.requirements.size, 0, &data); + // std::memcpy(data, src_data, size); + // vkUnmapMemory(ctx->m_device, staging_buffer.memory.memory); GPU::CommandBuffer buffer(9); @@ -587,8 +589,8 @@ void Graphics::Vulkan::vulkanBufferToImage(GPU::CommandBuffer* buffer, region.imageOffset = {0, 0, 0}; region.imageExtent = {dst_image->extent.width, dst_image->extent.height, 1}; - vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + // vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image, + // VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); set_image_layout(vk_buffer, dst_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(dst_layout)); @@ -602,22 +604,23 @@ void Graphics::Vulkan::vulkanCreateBuffer(HLE::Libs::Graphics::GraphicCtx* ctx, buffer_info.usage = buffer->usage; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - vkCreateBuffer(ctx->m_device, &buffer_info, nullptr, &buffer->buffer); + // vkCreateBuffer(ctx->m_device, &buffer_info, nullptr, &buffer->buffer); - vkGetBufferMemoryRequirements(ctx->m_device, buffer->buffer, &buffer->memory.requirements); + // vkGetBufferMemoryRequirements(ctx->m_device, buffer->buffer, &buffer->memory.requirements); bool allocated = GPU::vulkanAllocateMemory(ctx, &buffer->memory); if (!allocated) { fmt::print("Can't allocate vulkan\n"); std::exit(0); } - vkBindBufferMemory(ctx->m_device, buffer->buffer, buffer->memory.memory, buffer->memory.offset); + // vkBindBufferMemory(ctx->m_device, buffer->buffer, buffer->memory.memory, + // buffer->memory.offset); } void Graphics::Vulkan::vulkanDeleteBuffer(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanBuffer* buffer) { - vkDestroyBuffer(ctx->m_device, buffer->buffer, nullptr); - vkFreeMemory(ctx->m_device, buffer->memory.memory, nullptr); + // vkDestroyBuffer(ctx->m_device, buffer->buffer, nullptr); + // vkFreeMemory(ctx->m_device, buffer->memory.memory, nullptr); buffer->memory.memory = nullptr; buffer->buffer = nullptr; }