video_core: Rewrite vulkan and videoout

This commit is contained in:
GPUCode 2024-04-14 17:09:51 +03:00
parent 0a94899c86
commit c01b6f8397
89 changed files with 5378 additions and 2150 deletions

15
.gitmodules vendored
View File

@ -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

View File

@ -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)

View File

@ -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)

1
externals/ext-boost vendored Submodule

@ -0,0 +1 @@
Subproject commit dfb313f8357b8f6601fa7420be1a39a51ba86f77

1
externals/glslang vendored Submodule

@ -0,0 +1 @@
Subproject commit 2db79056b418587e61122a8a820cd832b2abdf83

1
externals/robin-map vendored Submodule

@ -0,0 +1 @@
Subproject commit 048eb1442a76ab81ecb3c4ab0495f15a68e54a6d

1
externals/vma vendored Submodule

@ -0,0 +1 @@
Subproject commit 5677097bafb8477097c6e3354ce68b7a44fd01a4

1
externals/vulkan-headers vendored Submodule

@ -0,0 +1 @@
Subproject commit cfebfc96b2b0bce93da7d12f2c14cc01793ae25c

31
src/common/alignment.h Normal file
View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2014 Jannik Vogel <email@jannikvogel.de>
// SPDX-License-Identifier: CC0-1.0
#pragma once
#include <cstddef>
#include <type_traits>
namespace Common {
template <typename T>
[[nodiscard]] constexpr T alignUp(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
[[nodiscard]] constexpr T alignDown(T value, std::size_t size) {
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
return static_cast<T>(value - value % size);
}
template <typename T>
requires std::is_integral_v<T>
[[nodiscard]] constexpr bool is16KBAligned(T value) {
return (value & 0x3FFF) == 0;
}
} // namespace Common

60
src/common/enum.h Normal file
View File

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(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<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}

View File

@ -22,6 +22,9 @@ using f64 = double;
using u128 = std::array<std::uint64_t, 2>;
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

View File

@ -1,203 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <xxh3.h>
#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<GPUMemory>::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<GPUMemory>::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<uint32_t>(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<GPUMemory>::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<GPUMemory>::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<const u8*>(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<int>(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<const uint8_t*>(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++;
}
}

View File

@ -1,94 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <vector>
#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<HeapObject> objects;
};
class GPUMemory {
public:
GPUMemory() {}
virtual ~GPUMemory() {}
int getHeapId(u64 vaddr, u64 size);
std::mutex m_mutex;
std::vector<MemoryHeap> 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

View File

@ -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

View File

@ -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<HLE::Libs::Graphics::VideoOutVulkanImage*>(obj);
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
if (tiled) {
std::vector<u8> tempbuff(*size);
GPU::convertTileToLinear(tempbuff.data(), reinterpret_cast<void*>(*virtual_addr), width,
height, neo);
Graphics::Vulkan::vulkanFillImage(
ctx, vk_obj, tempbuff.data(), *size, pitch,
static_cast<u64>(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
} else {
Graphics::Vulkan::vulkanFillImage(
ctx, vk_obj, reinterpret_cast<void*>(*virtual_addr), *size, pitch,
static_cast<u64>(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<uint64_t>(GPU::VideoOutBufferFormat::R8G8B8A8Srgb):
vk_format = VK_FORMAT_R8G8B8A8_SRGB;
break;
case static_cast<uint64_t>(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<VkImageUsageFlags>(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;
}

View File

@ -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<uint64_t>(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

View File

@ -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<int>(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<void*>(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<int>(m_requests.size());
return true;
}
} // namespace HLE::Graphics::Objects

View File

@ -1,90 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#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<Libraries::Kernel::SceKernelEqueue> m_flip_evtEq;
int m_flip_rate = 0;
VideoOutBufferInfo buffers[16];
std::vector<VideoOutBufferSetInternal> 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<Request> 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

View File

@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <mutex>
#include <vector>
#include <vulkan/vulkan.h>
#include "common/types.h"
namespace HLE::Libs::Graphics {
struct VulkanCommandPool {
std::mutex mutex;
VkCommandPool pool = nullptr;
std::vector<VkCommandBuffer> buffers;
std::vector<VkFence> fences;
std::vector<VkSemaphore> semaphores;
std::vector<bool> busy;
u32 buffers_count = 0;
};
struct VulkanQueueInfo {
std::unique_ptr<std::mutex> mutex{};
u32 family = static_cast<u32>(-1);
u32 index = static_cast<u32>(-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

View File

@ -1,207 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/core.h>
#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<RenderCtx>::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<uint32_t>(-1);
}
void GPU::CommandBuffer::waitForFence() {
auto* render_ctx = Common::Singleton<RenderCtx>::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<RenderCtx>::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<RenderCtx>::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<RenderCtx>::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<RenderCtx>::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);
}
}
}

View File

@ -1,81 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#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<HLE::Libs::Graphics::VulkanCommandPool, 11> 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<u32>(-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

View File

@ -1,382 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#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<HLE::Graphics::Objects::VideoOutCtx>::Instance();
videoOut->Init(width, height);
}
bool videoOutFlip(u32 micros) {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
return videoOut->getFlipQueue().flip(micros);
}
void VideoOutVblank() {
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::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<intptr_t>(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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<Graphics::VideoOutVulkanImage*>(GPU::memoryCreateObj(
0, videoOut->getGraphicCtx(), nullptr, reinterpret_cast<uint64_t>(addresses[i]),
buffer_size, buffer_info));
LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex,
reinterpret_cast<u64>(addresses[i]));
}
return registration_index;
}
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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<HLE::Graphics::Objects::VideoOutCtx>::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

View File

@ -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() {

View File

@ -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;

View File

@ -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<uintptr_t>(data);
}
};
class EqueueInternal {

View File

@ -2,10 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
#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<PhysicalMemory>::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;
}

View File

@ -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;
}

View File

@ -6,7 +6,6 @@
#include <mutex>
#include <vector>
#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<AllocatedBlock> m_allocatedBlocks;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -6,10 +6,10 @@
#include <pthread.h>
#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

View File

@ -4,7 +4,7 @@
#include <cmath>
#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

View File

@ -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

View File

@ -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<Core::FileSys::MntPoints>::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

View File

@ -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

View File

@ -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

View File

@ -6,7 +6,7 @@
#include <cstddef>
#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

View File

@ -5,7 +5,7 @@
#include <cstring>
#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

View File

@ -6,7 +6,7 @@
#include <cstddef>
#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

View File

@ -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

View File

@ -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<T*, 1, 8>(l);
}
} // namespace Core::Libraries::LibC
} // namespace Libraries::LibC

View File

@ -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

View File

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#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<u32>(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

View File

@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <pthread.h>
#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<Vulkan::RendererVulkan>(*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<u32>(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<u32>(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<uintptr_t>(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<int>(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<void*>(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<int>(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

View File

@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#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<VideoOutBuffer, MaxDisplayBuffers> buffer_slots;
std::array<BufferAttributeGroup, MaxDisplayBufferGroups> groups;
FlipStatus flip_status;
SceVideoOutVblankStatus vblank_status;
std::vector<Kernel::SceKernelEqueue> 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<Request> requests;
std::unique_ptr<Vulkan::RendererVulkan> renderer;
bool is_neo{};
};
} // namespace Libraries::VideoOut

View File

@ -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<VideoOutDriver> 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>(pixelFormat);
attribute->tiling_mode = static_cast<TilingMode>(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<const ServiceThreadParams*>(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<VideoOutDriver>(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

View File

@ -3,14 +3,14 @@
#pragma once
#include <string>
#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

View File

@ -1,353 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/core.h>
#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<Emu::WindowCtx>::Instance();
window_ctx->m_graphic_ctx.screen_width = width;
window_ctx->m_graphic_ctx.screen_height = height;
}
void checkAndWaitForGraphicsInit() {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::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<int>(ctx->m_graphic_ctx.screen_width);
int height = static_cast<int>(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<uint32_t>(SDL_WINDOW_HIDDEN) | static_cast<uint32_t>(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<double>(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<Emu::WindowCtx>::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<Emu::WindowCtx>::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<Emu::WindowCtx>::Instance();
SDL_SetWindowTitle(window_ctx->m_window, title.c_str());
}
void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image) {
auto window_ctx = Common::Singleton<Emu::WindowCtx>::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<u32>(-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<u32>(-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<Input::GameController>::Instance();
controller->checKButton(0, button, event->type == SDL_EVENT_KEY_DOWN);
}
}
}
} // namespace Emu

View File

@ -1,92 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <SDL.h>
#include <core/PS4/HLE/Graphics/graphics_ctx.h>
#include <condition_variable>
#include <mutex>
#include <vector>
namespace Emu {
struct VulkanExt {
bool enable_validation_layers = false;
char const* const* required_extensions;
u32 required_extensions_count;
std::vector<VkExtensionProperties> available_extensions;
std::vector<const char*> required_layers;
std::vector<VkLayerProperties> available_layers;
};
struct VulkanSurfaceCapabilities {
VkSurfaceCapabilitiesKHR capabilities{};
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> 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<VulkanQueueInfo> available;
std::vector<VulkanQueueInfo> graphics;
std::vector<VulkanQueueInfo> compute;
std::vector<VulkanQueueInfo> transfer;
std::vector<VulkanQueueInfo> present;
std::vector<u32> family_used;
};
struct VulkanSwapchain {
VkSwapchainKHR swapchain = nullptr;
VkFormat swapchain_format = VK_FORMAT_UNDEFINED;
VkExtent2D swapchain_extent = {};
std::vector<VkImage> swapchain_images;
std::vector<VkImageView> 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

View File

@ -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();

View File

@ -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:

View File

@ -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<Input::GameController>::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;

135
src/sdl_window.cpp Normal file
View File

@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL.h>
#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

77
src/sdl_window.h Normal file
View File

@ -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

View File

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <climits>
#include <utility>
#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

View File

@ -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 <vk_mem_alloc.h>
namespace Vulkan {
bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format format) {
const vk::FormatProperties props{physical_device.getFormatProperties(format)};
return static_cast<bool>(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<VkImageCreateInfo>(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<vk::PipelineStageFlags, 2> 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<u32>(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<u64>::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

View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#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<Frame> present_frames;
std::queue<Frame*> free_queue;
std::mutex free_mutex;
std::condition_variable free_cv;
std::condition_variable_any frame_cv;
};
} // namespace Vulkan

View File

@ -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 <vk_mem_alloc.h>
// Store the dispatch loader here
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE

View File

@ -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 <vulkan/vulkan.hpp>
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1

View File

@ -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<DescriptorInfoUnion[]>(descriptor_write_max);
descriptor_writes = std::make_unique<vk::WriteDescriptorSet[]>(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

View File

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#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<DescriptorInfoUnion[]> descriptor_infos;
std::unique_ptr<vk::WriteDescriptorSet[]> descriptor_writes;
u32 descriptor_write_end = 0;
};
} // namespace Vulkan

View File

@ -0,0 +1,271 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include <boost/container/static_vector.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>
#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 <vk_mem_alloc.h>
namespace Vulkan {
namespace {
std::vector<std::string> GetSupportedExtensions(vk::PhysicalDevice physical) {
const std::vector extensions = physical.enumerateDeviceExtensionProperties();
std::vector<std::string> 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<u16>(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<vk::PhysicalDeviceProperties2,
vk::PhysicalDevicePortabilitySubsetPropertiesKHR,
vk::PhysicalDeviceExternalMemoryHostPropertiesEXT>();
features = feature_chain.get().features;
if (available_extensions.empty()) {
LOG_CRITICAL(Render_Vulkan, "No extensions supported by device.");
return false;
}
boost::container::static_vector<const char*, 13> 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<u32>(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<f32, 1> queue_priorities = {1.0f};
const vk::DeviceQueueCreateInfo queue_info = {
.queueFamilyIndex = queue_family_index,
.queueCount = static_cast<u32>(queue_priorities.size()),
.pQueuePriorities = queue_priorities.data(),
};
vk::StructureChain device_chain = {
vk::DeviceCreateInfo{
.queueCreateInfoCount = 1u,
.pQueueCreateInfos = &queue_info,
.enabledExtensionCount = static_cast<u32>(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<vk::PhysicalDeviceProperties2, vk::PhysicalDeviceDriverProperties>();
const vk::PhysicalDeviceDriverProperties driver =
property_chain.get<vk::PhysicalDeviceDriverProperties>();
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

View File

@ -0,0 +1,228 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#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<const vk::PhysicalDevice> 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<const std::string> 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<vk::PhysicalDevice> physical_devices;
std::vector<std::string> 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

View File

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <limits>
#include <mutex>
#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<u64>::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<vk::PipelineStageFlags, 2> 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

View File

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <thread>
#include <queue>
#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<u64> gpu_tick{0}; ///< Current known GPU tick.
std::atomic<u64> current_tick{1}; ///< Current logical tick.
};
} // namespace Vulkan

View File

@ -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 <vector>
#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<u32>(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 : "<null>",
callback_data->pMessage ? callback_data->pMessage : "<null>");
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<HWND>(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<Display*>(window_info.display_connection),
.window = reinterpret_cast<Window>(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<wl_display*>(window_info.display_connection),
.surface = static_cast<wl_surface*>(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<const char*> 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<const char*> 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<PFN_vkGetInstanceProcAddr>("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<const char*, 2> 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<u32>(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

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <variant>
#include <fmt/format.h>
#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 <typename T>
concept VulkanHandleType = vk::isVulkanHandleType<T>::value;
template <VulkanHandleType HandleType>
void SetObjectName(vk::Device device, const HandleType& handle, std::string_view debug_name) {
const vk::DebugUtilsObjectNameInfoEXT name_info = {
.objectType = HandleType::objectType,
.objectHandle = reinterpret_cast<u64>(static_cast<typename HandleType::NativeType>(handle)),
.pObjectName = debug_name.data(),
};
device.setDebugUtilsObjectNameEXT(name_info);
}
template <VulkanHandleType HandleType, typename... Args>
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

View File

@ -0,0 +1,190 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#include <optional>
#include <unordered_map>
#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<std::size_t> {
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<const vk::DescriptorSetLayoutBinding> 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<u32>(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<vk::DescriptorType, u16> 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<vk::DescriptorSetLayout, DESCRIPTOR_SET_BATCH> 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<u32>(pool_sizes.size()),
.pPoolSizes = pool_sizes.data(),
};
auto& pool = pools.emplace_back();
pool = device.createDescriptorPoolUnique(pool_info);
}
} // namespace Vulkan

View File

@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include <tsl/robin_map.h>
#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<u64> 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<vk::CommandBuffer> cmd_buffers;
};
class DescriptorHeap final : public ResourcePool {
public:
explicit DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
std::span<const vk::DescriptorSetLayoutBinding> 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<vk::DescriptorPoolSize> pool_sizes;
std::vector<vk::UniqueDescriptorPool> pools;
std::vector<vk::DescriptorSet> descriptor_sets;
std::vector<std::size_t> hashes;
};
} // namespace Vulkan

View File

@ -0,0 +1,181 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include <utility>
#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<CommandChunk> 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<CommandChunk>();
return;
}
chunk = std::move(chunk_reserve.back());
chunk_reserve.pop_back();
}
} // namespace Vulkan

View File

@ -0,0 +1,192 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <functional>
#include <memory>
#include <thread>
#include <utility>
#include <queue>
#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 <typename T>
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<void()>&& func) {
on_submit = std::move(func);
}
/// Registers a callback to perform on queue submission.
void RegisterOnDispatch(std::function<void()>&& 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 <typename T>
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 <typename T>
bool Record(T& command) {
using FuncType = TypedCommand<T>;
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<u8, 0x8000> 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<CommandChunk> chunk;
std::queue<std::unique_ptr<CommandChunk>> work_queue;
std::vector<std::unique_ptr<CommandChunk>> chunk_reserve;
vk::CommandBuffer current_cmdbuf;
std::function<void()> on_submit;
std::function<void()> 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

View File

@ -0,0 +1,230 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <SPIRV/GlslangToSpv.h>
#include <glslang/Include/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>
#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<EShMessages>(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<int>(code.size());
auto shader = std::make_unique<glslang::TShader>(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<glslang::TProgram>();
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<u32> 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<const u32> 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

View File

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#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<const u32> code, vk::Device device);
} // namespace Vulkan

View File

@ -0,0 +1,234 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#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<u32>(type));
return vk::MemoryPropertyFlagBits::eHostVisible;
}
}
static std::optional<u32> 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<u8*, u64, bool> 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<bool>(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<vk::MemoryRequirements2, vk::MemoryDedicatedRequirements>(
{.buffer = buffer});
const auto& requirements = requirements_chain.get<vk::MemoryRequirements2>();
const auto& dedicated_requirements = requirements_chain.get<vk::MemoryDedicatedRequirements>();
stream_buffer_size = static_cast<u64>(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<vk::MemoryAllocateInfo, vk::MemoryDedicatedAllocateInfo> alloc_chain =
{};
auto& alloc_info = alloc_chain.get<vk::MemoryAllocateInfo>();
alloc_info.allocationSize = requirements.memoryRequirements.size;
alloc_info.memoryTypeIndex = preferred_type;
auto& dedicated_alloc_info = alloc_chain.get<vk::MemoryDedicatedAllocateInfo>();
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<u8*>(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<Watch>& 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

View File

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <span>
#include <tuple>
#include <vector>
#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<u8*, u64, bool> 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<Watch>& 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<Watch> current_watches; ///< Watches recorded in the current iteration.
std::size_t current_watch_cursor{}; ///< Count of watches, reset on invalidation.
std::optional<std::size_t> invalidation_mark; ///< Number of watches used in the previous cycle.
std::vector<Watch> 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

View File

@ -0,0 +1,225 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <limits>
#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<u64>::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<u32>::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<u32>(images.size());
if (instance.HasDebuggingToolAttached()) {
for (u32 i = 0; i < image_count; ++i) {
SetObjectName(device, images[i], "Swapchain Image {}", i);
}
}
}
} // namespace Vulkan

View File

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <vector>
#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<vk::Image> images;
std::vector<vk::Semaphore> image_acquired;
std::vector<vk::Semaphore> 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

View File

@ -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 <vk_mem_alloc.h>
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<u32>(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<VkImageCreateInfo>(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<u32>(info.resources.levels),
.arrayLayers = static_cast<u32>(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

View File

@ -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

View File

@ -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<u32>(type));
return {};
}
[[nodiscard]] vk::Format ConvertPixelFormat(const PixelFormat format) {
switch (format) {
default:
break;
}
UNREACHABLE_MSG("Unknown format={}", static_cast<u32>(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

View File

@ -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<u8>(SwizzleSource::R);
u8 y_source = static_cast<u8>(SwizzleSource::G);
u8 z_source = static_cast<u8>(SwizzleSource::B);
u8 w_source = static_cast<u8>(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

View File

@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <bit>
#include <compare>
#include <numeric>
#include <type_traits>
#include <utility>
#include <vector>
#include "common/assert.h"
#include "common/types.h"
namespace VideoCore {
struct SlotId {
static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::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 T>
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 <typename... Args>
[[nodiscard]] SlotId insert(Args&&... args) noexcept {
const u32 index = FreeValueIndex();
new (&values[index].object) T(std::forward<Args>(args)...);
SetStorageBit(index);
return SlotId{index};
}
template <typename... Args>
[[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>(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<u32>(values_capacity));
delete[] values;
values = new_values;
values_capacity = new_capacity;
}
Entry* values = nullptr;
std::size_t values_capacity = 0;
std::vector<u64> stored_bitset;
std::vector<u32> free_list;
};
} // namespace VideoCore
template <>
struct std::hash<VideoCore::SlotId> {
std::size_t operator()(const VideoCore::SlotId& id) const noexcept {
return std::hash<u32>{}(id.index);
}
};

View File

@ -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 <signal.h>
#include <sys/mman.h>
#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<ucontext_t*>(raw_context);
const VAddr address = reinterpret_cast<VAddr>(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<ImageId, 2> 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<const u8*>(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<void*>(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

View File

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <forward_list>
#include <boost/container/small_vector.hpp>
#include <boost/icl/interval_map.hpp>
#include <tsl/robin_map.h>
#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 <typename Func>
static void ForEachPage(PAddr addr, size_t size, Func&& func) {
static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, 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 <typename Func>
void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) {
using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
boost::container::small_vector<ImageId, 32> 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<Image> slot_images;
tsl::robin_pg_map<u64, std::vector<ImageId>> page_table;
boost::icl::interval_map<VAddr, s32> cached_pages;
};
} // namespace VideoCore

View File

@ -1,23 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/singleton.h"
#include "core/PS4/GPU/tile_manager.h"
#include <cstring>
#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<TileManager>::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<u64*>(static_cast<u8*>(dst) + linear_offset) =
*reinterpret_cast<const u64*>(static_cast<const u8*>(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<u32*>(static_cast<u8*>(dst) + linear_offset) =
*reinterpret_cast<const u32*>(static_cast<const u8*>(src) + tiled_offset);
std::memcpy(dst + linear_offset, src + tiled_offset, sizeof(u32));
}
}
}
} // namespace GPU
} // namespace VideoCore

View File

@ -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

View File

@ -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

View File

@ -15,7 +15,7 @@
#include <algorithm>
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<Emu::WindowCtx>::Instance();
return {};
/*auto window_ctx = Common::Singleton<Emu::WindowCtx>::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<const char*>& device_extensions) {
std::vector<VkDeviceQueueCreateInfo> queue_create_info(queues.family_count);
/*std::vector<VkDeviceQueueCreateInfo> queue_create_info(queues.family_count);
std::vector<std::vector<float>> 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<const char*>& 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<VkPhysicalDevice> 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<VkQueueFamilyProperties> 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<int>(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, &region,
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, &region,
// 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, &region);
// vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image,
// VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
set_image_layout(vk_buffer, dst_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<VkImageLayout>(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;
}