diff --git a/.gitmodules b/.gitmodules index abb8fc02..9cfbedd0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,3 +33,11 @@ path = third-party/toml11 url = https://github.com/ToruNiina/toml11 branch = master +[submodule "third-party/vulkan"] + path = third-party/vulkan + url = https://github.com/shadps4/vulkan.git + branch = main +[submodule "third-party/xxHash"] + path = third-party/xxHash + url = https://github.com/Cyan4973/xxHash.git + branch = dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 82488063..a5a0fe28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ include_directories(third-party/fmt/include) include_directories(third-party/magic_enum/include) include_directories(third-party/zydis/include/Zydis) include_directories(third-party/winpthread/include) +include_directories(third-party/vulkan/include) +include_directories(third-party/xxhash/include) add_subdirectory("third-party") #=================== EXAMPLE =================== include_directories(src) @@ -52,10 +54,10 @@ add_executable(shadps4 src/Core/PS4/HLE/Kernel/event_queues.h src/Core/PS4/HLE/Kernel/cpu_management.cpp src/Core/PS4/HLE/Kernel/cpu_management.h - "src/Util/Singleton.h" "src/Util/Disassembler.cpp" "src/Util/Disassembler.h" "src/Core/PS4/Util/aerolib.h" "src/Core/PS4/Loader/SymbolsResolver.h" "src/Core/PS4/Loader/SymbolsResolver.cpp" "src/Core/PS4/HLE/Libs.cpp" "src/Core/PS4/HLE/Libs.h" "src/Core/PS4/HLE/LibC.cpp" "src/Core/PS4/HLE/LibC.h" "src/Lib/Timer.cpp" "src/Lib/Timer.h" "src/Core/PS4/HLE/LibKernel.cpp" "src/Core/PS4/HLE/LibKernel.h" "src/Core/PS4/HLE/LibSceGnmDriver.cpp" "src/Core/PS4/HLE/LibSceGnmDriver.h" "src/Core/PS4/HLE/Kernel/ThreadManagement.cpp" "src/Core/PS4/HLE/Kernel/ThreadManagement.h" "src/Core/PS4/HLE/ErrorCodes.h" "src/debug.h" "src/Core/PS4/HLE/Kernel/memory_management.cpp" "src/Core/PS4/HLE/Kernel/memory_management.h" "src/Core/PS4/GPU/gpu_memory.cpp" "src/Core/PS4/GPU/gpu_memory.h" "src/emulator.cpp" "src/emulator.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.cpp" "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/Util/Singleton.h" "src/Util/Disassembler.cpp" "src/Util/Disassembler.h" "src/Core/PS4/Util/aerolib.h" "src/Core/PS4/Loader/SymbolsResolver.h" "src/Core/PS4/Loader/SymbolsResolver.cpp" "src/Core/PS4/HLE/Libs.cpp" "src/Core/PS4/HLE/Libs.h" "src/Core/PS4/HLE/LibC.cpp" "src/Core/PS4/HLE/LibC.h" "src/Lib/Timer.cpp" "src/Lib/Timer.h" "src/Core/PS4/HLE/LibKernel.cpp" "src/Core/PS4/HLE/LibKernel.h" "src/Core/PS4/HLE/LibSceGnmDriver.cpp" "src/Core/PS4/HLE/LibSceGnmDriver.h" "src/Core/PS4/HLE/Kernel/ThreadManagement.cpp" "src/Core/PS4/HLE/Kernel/ThreadManagement.h" "src/Core/PS4/HLE/ErrorCodes.h" "src/debug.h" "src/Core/PS4/HLE/Kernel/memory_management.cpp" "src/Core/PS4/HLE/Kernel/memory_management.h" "src/Core/PS4/GPU/gpu_memory.cpp" "src/Core/PS4/GPU/gpu_memory.h" "src/emulator.cpp" "src/emulator.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.h" "src/Core/PS4/HLE/Kernel/Objects/event_queue.cpp" "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/vulkan_util.cpp" "src/vulkan_util.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") find_package(OpenGL REQUIRED) -target_link_libraries(shadps4 PUBLIC fmt mincore spdlog IMGUI SDL3-shared ${OPENGL_LIBRARY}) +target_link_libraries(shadps4 PUBLIC fmt mincore spdlog IMGUI SDL3-shared ${OPENGL_LIBRARY} vulkan-1 spirv-tools-opt spirv-tools) add_custom_command(TARGET shadps4 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/src/Core/PS4/GPU/gpu_memory.cpp b/src/Core/PS4/GPU/gpu_memory.cpp index 873f43b5..39591892 100644 --- a/src/Core/PS4/GPU/gpu_memory.cpp +++ b/src/Core/PS4/GPU/gpu_memory.cpp @@ -1,5 +1,188 @@ #include "gpu_memory.h" -namespace GPU { -void MemorySetAllocArea(u64 virtual_addr, u64 size) {} -} // namespace GPU \ No newline at end of file +#include + +#include "Util/Singleton.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 = Singleton::Instance(); + + return gpumemory->memoryCreateObj(submit_id, ctx, nullptr, &virtual_addr, &size, 1, info); +} + +void GPU::memorySetAllocArea(u64 virtual_addr, u64 size) { + auto* gpumemory = Singleton::Instance(); + + Lib::LockMutexGuard 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 u08* buf, u64 size) { return (size > 0 && buf != nullptr ? XXH3_64bits(buf, size) : 0); } + +bool GPU::vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanMemory* mem) { + static std::atomic_uint64_t unique_id = 0; + + VkPhysicalDeviceMemoryProperties memory_properties{}; + vkGetPhysicalDeviceMemoryProperties(ctx->m_physical_device, &memory_properties); + + u32 index = 0; + for (; index < memory_properties.memoryTypeCount; index++) { + if ((mem->requirements.memoryTypeBits & (static_cast(1) << index)) != 0 && + (memory_properties.memoryTypes[index].propertyFlags & mem->property) == mem->property) { + break; + } + } + + mem->type = index; + mem->offset = 0; + + VkMemoryAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = nullptr; + alloc_info.allocationSize = mem->requirements.size; + alloc_info.memoryTypeIndex = index; + + mem->unique_id = ++unique_id; + + auto result = vkAllocateMemory(ctx->m_device, &alloc_info, nullptr, &mem->memory); + + if (result == VK_SUCCESS) { + return true; + } + return false; +} + +void GPU::flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx) { + auto* gpumemory = Singleton::Instance(); + gpumemory->flushAllHeaps(ctx); +} + +int GPU::GPUMemory::getHeapId(u64 virtual_addr, u64 size) { + int index = 0; + for (const auto& heap : m_heaps) { + if ((virtual_addr >= heap.allocated_virtual_addr && virtual_addr < heap.allocated_virtual_addr + heap.allocated_size) || + ((virtual_addr + size - 1) >= heap.allocated_virtual_addr && + (virtual_addr + size - 1) < heap.allocated_virtual_addr + heap.allocated_size)) { + return index; + } + index++; + } + return -1; +} + +void* GPU::GPUMemory::memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, void* todo, const u64* virtual_addr, const u64* size, + int virtual_addr_num, const GPUObject& info) { + auto* gpumemory = Singleton::Instance(); + + Lib::LockMutexGuard lock(gpumemory->m_mutex); + + int heap_id = gpumemory->getHeapId(virtual_addr[0], size[0]); + + if (heap_id < 0) { + return nullptr; + } + auto& heap = m_heaps[heap_id]; + + ObjInfo objInfo = {}; + + // copy parameters from info to obj + for (int i = 0; i < 8; i++) { + objInfo.obj_params[i] = info.obj_params[i]; + } + + objInfo.gpu_object.objectType = info.objectType; + objInfo.gpu_object.obj = nullptr; + + for (int h = 0; h < virtual_addr_num; h++) { + if (info.check_hash) { + objInfo.hash[h] = GPU::calculate_hash(reinterpret_cast(virtual_addr[h]), size[h]); + } else { + objInfo.hash[h] = 0; + } + } + objInfo.submit_id = submit_id; + objInfo.check_hash = info.check_hash; + + objInfo.gpu_object.obj = info.getCreateFunc()(ctx, objInfo.obj_params, virtual_addr, size, virtual_addr_num, &objInfo.mem); + + objInfo.update_func = info.getUpdateFunc(); + int index = static_cast(heap.objects.size()); + + HeapObject hobj{}; + hobj.block = createHeapBlock(virtual_addr, size, virtual_addr_num, heap_id, index); + hobj.info = objInfo; + hobj.free = false; + heap.objects.push_back(hobj); + + return objInfo.gpu_object.obj; +} + +GPU::HeapBlock GPU::GPUMemory::createHeapBlock(const u64* virtual_addr, const u64* size, int virtual_addr_num, int heap_id, int obj_id) { + auto& heap = m_heaps[heap_id]; + + GPU::HeapBlock heapBlock{}; + heapBlock.virtual_addr_num = virtual_addr_num; + for (int vi = 0; vi < virtual_addr_num; vi++) { + heapBlock.virtual_addr[vi] = virtual_addr[vi]; + heapBlock.size[vi] = size[vi]; + } + return heapBlock; +} + +void GPU::GPUMemory::update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, int obj_id) { + auto& heap = m_heaps[heap_id]; + + auto& heapObj = heap.objects[obj_id]; + auto& objInfo = heapObj.info; + bool need_update = false; + + if (submit_id > objInfo.submit_id) { + uint64_t hash[3] = {}; + + for (int i = 0; i < heapObj.block.virtual_addr_num; i++) { + if (objInfo.check_hash) { + hash[i] = GPU::calculate_hash(reinterpret_cast(heapObj.block.virtual_addr[i]), heapObj.block.size[i]); + } else { + hash[i] = 0; + } + } + + for (int i = 0; i < heapObj.block.virtual_addr_num; i++) { + if (objInfo.hash[i] != hash[i]) { + need_update = true; + objInfo.hash[i] = hash[i]; + } + } + + if (submit_id != UINT64_MAX) { + objInfo.submit_id = submit_id; + } + } + + if (need_update) { + objInfo.update_func(ctx, objInfo.obj_params, objInfo.gpu_object.obj, heapObj.block.virtual_addr, heapObj.block.size, + heapObj.block.virtual_addr_num); + } +} + +void GPU::GPUMemory::flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx) { + Lib::LockMutexGuard lock(m_mutex); + + int heap_id = 0; + for (auto& heap : m_heaps) { + int index = 0; + for (auto& heapObj : heap.objects) { + if (!heapObj.free) { + update(UINT64_MAX, ctx, heap_id, index); + } + index++; + } + heap_id++; + } +} diff --git a/src/Core/PS4/GPU/gpu_memory.h b/src/Core/PS4/GPU/gpu_memory.h index 3f667bbf..f7612649 100644 --- a/src/Core/PS4/GPU/gpu_memory.h +++ b/src/Core/PS4/GPU/gpu_memory.h @@ -1,9 +1,84 @@ #pragma once +#include #include +#include namespace GPU { -enum class MemoryMode : u32 { NoAccess = 0, Read = 1, Write = 2, ReadWrite = 3 }; -void MemorySetAllocArea(u64 virtual_addr, u64 size); -} \ No newline at end of file +class GPUObject; + +enum class MemoryMode : u32 { NoAccess = 0, Read = 1, Write = 2, ReadWrite = 3 }; +enum class MemoryObjectType : u64 { InvalidObj, VideoOutBufferObj }; + +struct GpuMemoryObject { + MemoryObjectType objectType = MemoryObjectType::InvalidObj; + void* obj = nullptr; +}; + +struct HeapBlock { + u64 virtual_addr[3] = {}; + u64 size[3] = {}; + int virtual_addr_num = 0; +}; + +class GPUObject { + public: + GPUObject() = default; + virtual ~GPUObject() = default; + u64 obj_params[8] = {}; + bool check_hash = false; + bool isReadOnly = false; + MemoryObjectType objectType = MemoryObjectType::InvalidObj; + + using create_func_t = void* (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, const u64* virtual_addr, const u64* size, int virtual_addr_num, + HLE::Libs::Graphics::VulkanMemory* mem); + using update_func_t = void (*)(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, void* obj, const u64* virtual_addr, const u64* size, + int virtual_addr_num); + + virtual create_func_t getCreateFunc() const = 0; + virtual update_func_t getUpdateFunc() const = 0; +}; + +struct ObjInfo { + u64 obj_params[8] = {}; + GpuMemoryObject gpu_object; + u64 hash[3] = {}; + u64 submit_id = 0; + bool check_hash = false; + HLE::Libs::Graphics::VulkanMemory mem; + GPU::GPUObject::update_func_t update_func = nullptr; +}; + +struct HeapObject { + HeapBlock block; + ObjInfo info; + bool free = true; +}; +struct MemoryHeap { + u64 allocated_virtual_addr = 0; + u64 allocated_size = 0; + std::vector objects; +}; + +class GPUMemory { + public: + GPUMemory() {} + virtual ~GPUMemory() {} + int getHeapId(u64 vaddr, u64 size); + Lib::Mutex m_mutex; + std::vector m_heaps; + void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, /*CommandBuffer* buffer*/ void* todo, const u64* virtual_addr, + const u64* size, int virtual_addr_num, const GPUObject& info); + HeapBlock createHeapBlock(const u64* virtual_addr, const u64* size, int virtual_addr_num, int heap_id, int obj_id); + void update(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, int heap_id, int obj_id); + void flushAllHeaps(HLE::Libs::Graphics::GraphicCtx* ctx); +}; + +void memorySetAllocArea(u64 virtual_addr, u64 size); +void* memoryCreateObj(u64 submit_id, HLE::Libs::Graphics::GraphicCtx* ctx, /*CommandBuffer* buffer*/ void* todo, u64 virtual_addr, u64 size, + const GPUObject& info); +u64 calculate_hash(const u08* buf, u64 size); +bool vulkanAllocateMemory(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanMemory* mem); +void flushGarlic(HLE::Libs::Graphics::GraphicCtx* ctx); +} // namespace GPU \ No newline at end of file diff --git a/src/Core/PS4/GPU/video_out_buffer.cpp b/src/Core/PS4/GPU/video_out_buffer.cpp new file mode 100644 index 00000000..73483a91 --- /dev/null +++ b/src/Core/PS4/GPU/video_out_buffer.cpp @@ -0,0 +1,126 @@ +#include "video_out_buffer.h" + +#include + +#include "debug.h" +#include + +constexpr bool log_file_videoOutBuffer = true; // disable it to disable logging + +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]; + + auto* vk_obj = static_cast(obj); + + vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED; + + Graphics::Vulkan::vulkanFillImage(ctx, vk_obj, reinterpret_cast(*virtual_addr), *size, pitch, + static_cast(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)); +} + +static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, const u64* virtual_addr, const u64* size, int virtual_addr_num, + HLE::Libs::Graphics::VulkanMemory* mem) { + auto pixel_format = params[GPU::VideoOutBufferObj::PIXEL_FORMAT_PARAM]; + auto width = params[GPU::VideoOutBufferObj::WIDTH_PARAM]; + auto height = params[GPU::VideoOutBufferObj::HEIGHT_PARAM]; + + auto* vk_obj = new HLE::Libs::Graphics::VideoOutVulkanImage; + + VkFormat vk_format = VK_FORMAT_UNDEFINED; + + switch (pixel_format) { + case static_cast(GPU::VideoOutBufferFormat::R8G8B8A8Srgb): vk_format = VK_FORMAT_R8G8B8A8_SRGB; break; + case static_cast(GPU::VideoOutBufferFormat::B8G8R8A8Srgb): vk_format = VK_FORMAT_B8G8R8A8_SRGB; break; + default: LOG_CRITICAL_IF(log_file_videoOutBuffer, "unknown pixelFormat = {}\n", pixel_format); std::exit(0); + } + + vk_obj->extent.width = width; + vk_obj->extent.height = height; + vk_obj->format = vk_format; + vk_obj->image = nullptr; + vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED; + + for (auto& view : vk_obj->image_view) { + view = nullptr; + } + + VkImageCreateInfo image_info{}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.pNext = nullptr; + image_info.flags = 0; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.extent.width = vk_obj->extent.width; + image_info.extent.height = vk_obj->extent.height; + image_info.extent.depth = 1; + image_info.mipLevels = 1; + image_info.arrayLayers = 1; + image_info.format = vk_obj->format; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.initialLayout = vk_obj->layout; + image_info.usage = + static_cast(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT) | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + + vkCreateImage(ctx->m_device, &image_info, nullptr, &vk_obj->image); + + if (vk_obj->image == nullptr) { + LOG_CRITICAL_IF(log_file_videoOutBuffer, "vk_obj->image is null\n"); + std::exit(0); + } + + 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) { + LOG_CRITICAL_IF(log_file_videoOutBuffer, "can't allocate vulkan memory\n"); + std::exit(0); + } + + vkBindImageMemory(ctx->m_device, vk_obj->image, mem->memory, mem->offset); + + vk_obj->memory = *mem; + + LOG_INFO_IF(log_file_videoOutBuffer, "videoOutBuffer create object\n"); + LOG_INFO_IF(log_file_videoOutBuffer, "mem requirements.size = {}\n", mem->requirements.size); + LOG_INFO_IF(log_file_videoOutBuffer, "width = {}\n", width); + LOG_INFO_IF(log_file_videoOutBuffer, "height = {}\n", height); + LOG_INFO_IF(log_file_videoOutBuffer, "size = {}\n", *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) { + LOG_CRITICAL_IF(log_file_videoOutBuffer, "vk_obj->image_view is null\n"); + std::exit(0); + } + + return vk_obj; +} + +GPU::GPUObject::create_func_t GPU::VideoOutBufferObj::getCreateFunc() const { return create_func; } + +GPU::GPUObject::update_func_t GPU::VideoOutBufferObj::getUpdateFunc() const { return update_func; } diff --git a/src/Core/PS4/GPU/video_out_buffer.h b/src/Core/PS4/GPU/video_out_buffer.h new file mode 100644 index 00000000..ed459efe --- /dev/null +++ b/src/Core/PS4/GPU/video_out_buffer.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "gpu_memory.h" + +namespace GPU { + +enum class VideoOutBufferFormat : u64 { + Unknown, + R8G8B8A8Srgb, + B8G8R8A8Srgb, +}; + +class VideoOutBufferObj : public GPUObject { + public: + static constexpr int PIXEL_FORMAT_PARAM = 0; + static constexpr int WIDTH_PARAM = 1; + static constexpr int HEIGHT_PARAM = 2; + static constexpr int IS_TILE_PARAM = 3; + static constexpr int IS_NEO_PARAM = 4; + static constexpr int PITCH_PARAM = 5; + + explicit VideoOutBufferObj(VideoOutBufferFormat pixel_format, u32 width, u32 height, bool is_tiled, bool is_neo, u32 pitch) { + obj_params[PIXEL_FORMAT_PARAM] = static_cast(pixel_format); + obj_params[WIDTH_PARAM] = width; + obj_params[HEIGHT_PARAM] = height; + obj_params[IS_TILE_PARAM] = is_tiled ? 1 : 0; + obj_params[IS_NEO_PARAM] = is_neo ? 1 : 0; + obj_params[PITCH_PARAM] = pitch; + check_hash = true; + objectType = GPU::MemoryObjectType::VideoOutBufferObj; + } + + create_func_t getCreateFunc() const override; + update_func_t getUpdateFunc() const override; +}; +} // namespace GPU diff --git a/src/Core/PS4/HLE/ErrorCodes.h b/src/Core/PS4/HLE/ErrorCodes.h index 04edb64e..06c3014d 100644 --- a/src/Core/PS4/HLE/ErrorCodes.h +++ b/src/Core/PS4/HLE/ErrorCodes.h @@ -9,9 +9,14 @@ constexpr int SCE_KERNEL_ERROR_EAGAIN = 0x80020023; // Memory cannot be a constexpr int SCE_KERNEL_ERROR_ENAMETOOLONG = 0x8002003f; // character strings exceeds valid size // videoOut -constexpr int SCE_VIDEO_OUT_ERROR_INVALID_VALUE = 0x80290001; // invalid argument -constexpr int SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY = 0x80290009; // already opened -constexpr int SCE_VIDEO_OUT_ERROR_INVALID_INDEX = 0x8029000A; // invalid buffer index -constexpr int SCE_VIDEO_OUT_ERROR_INVALID_HANDLE = 0x8029000B; // invalid handle -constexpr int SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE = 0x8029000C; // Invalid event queue -constexpr int SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL = 0x80290012; // flip queue is full +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_VALUE = 0x80290001; // invalid argument +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS = 0x80290002; // invalid addresses +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE = 0x80290007; // invalid tiling mode +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO = 0x80290008; // invalid aspect ration +constexpr int SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY = 0x80290009; // already opened +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_INDEX = 0x8029000A; // invalid buffer index +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_HANDLE = 0x8029000B; // invalid handle +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE = 0x8029000C; // Invalid event queue +constexpr int SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED = 0x80290010; // slot already used +constexpr int SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL = 0x80290012; // flip queue is full +constexpr int SCE_VIDEO_OUT_ERROR_INVALID_OPTION = 0x8029001A; // Invalid buffer attribute option diff --git a/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp b/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp index afc6426f..aa77713d 100644 --- a/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp +++ b/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.cpp @@ -76,6 +76,12 @@ bool FlipQueue::flip(u32 micros) { auto request = m_requests.at(0); // proceed first request m_mutex.UnlockMutex(); + auto* buffer = request.cfg->buffers[request.index].buffer_render; + + Emulator::DrawBuffer(buffer); + + m_mutex.LockMutex(); + request.cfg->m_mutex.LockMutex(); for (auto& flip_eq : request.cfg->m_flip_evtEq) { if (flip_eq != nullptr) { diff --git a/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.h b/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.h index 1017fbc0..6ce3c755 100644 --- a/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.h +++ b/src/Core/PS4/HLE/Graphics/Objects/video_out_ctx.h @@ -1,12 +1,20 @@ #pragma once #include #include +#include +#include using namespace HLE::Libs::Graphics::VideoOut; namespace HLE::Graphics::Objects { -//class FlipQueue; +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 { Lib::Mutex m_mutex; @@ -16,6 +24,9 @@ struct VideoConfigInternal { SceVideoOutVblankStatus m_vblank_status; std::vector m_flip_evtEq; int m_flip_rate = 0; + VideoOutBufferInfo buffers[16]; + std::vector buffers_sets; + int buffers_registration_index = 0; }; class FlipQueue { @@ -49,9 +60,19 @@ class VideoOutCtx { int Open(); VideoConfigInternal* getCtx(int handle); FlipQueue& getFlipQueue() { return m_flip_queue; } + HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() { + Lib::LockMutexGuard lock(m_mutex); + + if (m_graphic_ctx == nullptr) { + m_graphic_ctx = Emulator::getGraphicCtx(); + } + + return m_graphic_ctx; + } private: Lib::Mutex m_mutex; VideoConfigInternal m_video_out_ctx; FlipQueue m_flip_queue; + HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr; }; }; // namespace HLE::Graphics::Objects \ No newline at end of file diff --git a/src/Core/PS4/HLE/Graphics/graphics_ctx.h b/src/Core/PS4/HLE/Graphics/graphics_ctx.h index 782b66f3..5b7eb493 100644 --- a/src/Core/PS4/HLE/Graphics/graphics_ctx.h +++ b/src/Core/PS4/HLE/Graphics/graphics_ctx.h @@ -1,9 +1,73 @@ #pragma once + #include +#include + +#include "Lib/Threads.h" namespace HLE::Libs::Graphics { + +struct VulkanCommandPool { + Lib::Mutex mutex; + VkCommandPool pool = nullptr; + VkCommandBuffer* buffers = nullptr; + VkFence* fences = nullptr; + VkSemaphore* semaphores = nullptr; + bool* busy = nullptr; + u32 buffers_count = 0; +}; + +struct VulkanQueueInfo { + Lib::Mutex* mutex = nullptr; + u32 family = static_cast(-1); + u32 index = static_cast(-1); + VkQueue vk_queue = nullptr; +}; + struct GraphicCtx { u32 screen_width = 0; u32 screen_height = 0; + VkInstance m_instance = nullptr; + VkPhysicalDevice m_physical_device = nullptr; + VkDevice m_device = nullptr; + VulkanQueueInfo queues[11]; // VULKAN_QUEUES_NUM }; + +enum class VulkanImageType { Unknown, VideoOut }; + +struct VulkanMemory { + VkMemoryRequirements requirements = {}; + VkMemoryPropertyFlags property = 0; + VkDeviceMemory memory = nullptr; + VkDeviceSize offset = 0; + u32 type = 0; + u64 unique_id = 0; +}; + +struct VulkanBuffer { + VkBuffer buffer = nullptr; + VulkanMemory memory; + VkBufferUsageFlags usage = 0; +}; +struct VulkanImage { + static constexpr int VIEW_MAX = 4; + static constexpr int VIEW_DEFAULT = 0; + static constexpr int VIEW_BGRA = 1; + static constexpr int VIEW_DEPTH_TEXTURE = 2; + + explicit VulkanImage(VulkanImageType type) : type(type) {} + + VulkanImageType type = VulkanImageType::Unknown; + VkFormat format = VK_FORMAT_UNDEFINED; + VkExtent2D extent = {}; + VkImage image = nullptr; + VkImageView image_view[VIEW_MAX] = {}; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + VulkanMemory memory; +}; + +struct VideoOutVulkanImage : public VulkanImage { + VideoOutVulkanImage() : VulkanImage(VulkanImageType::VideoOut) {} +}; + } // namespace HLE::Libs::Graphics \ No newline at end of file diff --git a/src/Core/PS4/HLE/Graphics/graphics_render.cpp b/src/Core/PS4/HLE/Graphics/graphics_render.cpp new file mode 100644 index 00000000..e02bdf7b --- /dev/null +++ b/src/Core/PS4/HLE/Graphics/graphics_render.cpp @@ -0,0 +1,232 @@ +#include "graphics_render.h" + +#include "Util/Singleton.h" +#include "emulator.h" + +static thread_local GPU::CommandPool g_command_pool; + +void GPU::renderCreateCtx() { + auto* render_ctx = Singleton::Instance(); + + render_ctx->setGraphicCtx(Emulator::getGraphicCtx()); +} + +void GPU::CommandBuffer::allocateBuffer() { + m_pool = g_command_pool.getPool(m_queue); + + Lib::LockMutexGuard 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() { + Lib::LockMutexGuard lock(m_pool->mutex); + + waitForFence(); + + m_pool->busy[m_index] = false; + vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); + m_index = static_cast(-1); +} + +void GPU::CommandBuffer::waitForFence() { + auto* render_ctx = Singleton::Instance(); + + if (m_execute) { + auto* device = render_ctx->getGraphicCtx()->m_device; + + vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &m_pool->fences[m_index]); + + m_execute = false; + } +} +void GPU::CommandBuffer::begin() const { + auto* buffer = m_pool->buffers[m_index]; + + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.pNext = nullptr; + begin_info.flags = 0; + begin_info.pInheritanceInfo = nullptr; + + auto result = vkBeginCommandBuffer(buffer, &begin_info); + + if (result != VK_SUCCESS) { + printf("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) { + printf("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 = Singleton::Instance(); + const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue]; + + if (queue.mutex != nullptr) { + queue.mutex->LockMutex(); + } + + auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence); + + if (queue.mutex != nullptr) { + queue.mutex->LockMutex(); + } + + m_execute = true; + + if (result != VK_SUCCESS) { + printf("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 = Singleton::Instance(); + const auto& queue = render_ctx->getGraphicCtx()->queues[m_queue]; + + if (queue.mutex != nullptr) { + queue.mutex->LockMutex(); + } + + auto result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence); + + if (queue.mutex != nullptr) { + queue.mutex->UnlockMutex(); + } + + m_execute = true; + + if (result != VK_SUCCESS) { + printf("vkQueueSubmit failed\n"); + std::exit(0); + } +} +void GPU::CommandPool::createPool(int id) { + auto* render_ctx = Singleton::Instance(); + auto* ctx = render_ctx->getGraphicCtx(); + + m_pool[id] = new HLE::Libs::Graphics::VulkanCommandPool; + + 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 == nullptr) { + printf("pool is nullptr"); + std::exit(0); + } + + m_pool[id]->buffers_count = 4; + m_pool[id]->buffers = new VkCommandBuffer[m_pool[id]->buffers_count]; + m_pool[id]->fences = new VkFence[m_pool[id]->buffers_count]; + m_pool[id]->semaphores = new VkSemaphore[m_pool[id]->buffers_count]; + m_pool[id]->busy = new bool[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) != VK_SUCCESS) { + printf("Can't allocate command buffers\n"); + std::exit(0); + } + + for (uint32_t 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) { + printf("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) { + printf("Can't create semas\n"); + std::exit(0); + } + } +} + +void GPU::CommandPool::deleteAllPool() { + auto* render_ctx = Singleton::Instance(); + auto* ctx = render_ctx->getGraphicCtx(); + + for (auto& pool : m_pool) { + if (pool != nullptr) { + for (uint32_t i = 0; i < pool->buffers_count; i++) { + vkDestroySemaphore(ctx->m_device, pool->semaphores[i], nullptr); + vkDestroyFence(ctx->m_device, pool->fences[i], nullptr); + } + + vkFreeCommandBuffers(ctx->m_device, pool->pool, pool->buffers_count, pool->buffers); + + vkDestroyCommandPool(ctx->m_device, pool->pool, nullptr); + + delete[] pool->semaphores; + delete[] pool->fences; + delete[] pool->buffers; + delete[] pool->busy; + + delete pool; + pool = nullptr; + } + } +} diff --git a/src/Core/PS4/HLE/Graphics/graphics_render.h b/src/Core/PS4/HLE/Graphics/graphics_render.h new file mode 100644 index 00000000..b98197de --- /dev/null +++ b/src/Core/PS4/HLE/Graphics/graphics_render.h @@ -0,0 +1,63 @@ +#pragma once +#include "graphics_ctx.h" + +namespace GPU { + +class CommandPool { + public: + CommandPool() = default; + ~CommandPool() {} + + HLE::Libs::Graphics::VulkanCommandPool* getPool(int id) { + if (m_pool[id] == nullptr) { + createPool(id); + } + return m_pool[id]; + } + + private: + void createPool(int id); + void deleteAllPool(); + + HLE::Libs::Graphics::VulkanCommandPool* m_pool[11] = {}; +}; +class CommandBuffer { + public: + explicit CommandBuffer(int queue) : m_queue(queue) { allocateBuffer(); } + virtual ~CommandBuffer() { freeBuffer(); } + void allocateBuffer(); + void freeBuffer(); + void waitForFence(); + void begin() const; + void end() const; + void executeWithSemaphore(); + void execute(); + u32 getIndex() const { return m_index; } + HLE::Libs::Graphics::VulkanCommandPool* getPool() { return m_pool; } + private: + int m_queue = -1; + u32 m_index = static_cast(-1); + HLE::Libs::Graphics::VulkanCommandPool* m_pool = nullptr; + bool m_execute = false; +}; + +class Framebuffer { + public: + Framebuffer() {} + virtual ~Framebuffer() {} +}; +class RenderCtx { + public: + RenderCtx() : m_framebuffer(new Framebuffer) {} + + 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 = nullptr; + HLE::Libs::Graphics::GraphicCtx* m_graphic_ctx = nullptr; +}; + +void renderCreateCtx(); +}; // namespace GPU \ No newline at end of file diff --git a/src/Core/PS4/HLE/Graphics/video_out.cpp b/src/Core/PS4/HLE/Graphics/video_out.cpp index a9356b3f..95b2957c 100644 --- a/src/Core/PS4/HLE/Graphics/video_out.cpp +++ b/src/Core/PS4/HLE/Graphics/video_out.cpp @@ -1,8 +1,10 @@ #include "video_out.h" +#include #include #include #include +#include #include #include #include @@ -12,6 +14,9 @@ #include "Objects/video_out_ctx.h" #include "Util/Singleton.h" +#include "emulator.h" +#include +#include "graphics_render.h" namespace HLE::Libs::Graphics::VideoOut { @@ -115,9 +120,93 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(LibKernel::EventQueues::SceKernelEqueue s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses, s32 bufferNum, const SceVideoOutBufferAttribute* attribute) { - // BREAKPOINT(); - PRINT_DUMMY_FUNCTION_NAME(); - return 0; + PRINT_FUNCTION_NAME(); + auto* videoOut = Singleton::Instance(); + auto* ctx = videoOut->getCtx(handle); + + if (handle == 1) { // main port + if (startIndex < 0 || startIndex > 15) { + LOG_TRACE_IF(log_file_videoout, "invalid startIndex = {}\n", startIndex); + return SCE_VIDEO_OUT_ERROR_INVALID_VALUE; + } + if (bufferNum < 1 || bufferNum > 16) { + LOG_TRACE_IF(log_file_videoout, "invalid bufferNum = {}\n", bufferNum); + return SCE_VIDEO_OUT_ERROR_INVALID_VALUE; + } + } + if (addresses == nullptr) { + LOG_TRACE_IF(log_file_videoout, "addresses are null\n"); + return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS; + } + + if (attribute == nullptr) { + LOG_TRACE_IF(log_file_videoout, "attribute is null\n"); + return SCE_VIDEO_OUT_ERROR_INVALID_OPTION; + } + if (attribute->aspectRatio != 0) { + LOG_TRACE_IF(log_file_videoout, "invalid aspect ratio = {}\n", attribute->aspectRatio); + return SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO; + } + if (attribute->tilingMode < 0 || attribute->tilingMode > 1) { + LOG_TRACE_IF(log_file_videoout, "invalid tilingMode = {}\n", attribute->tilingMode); + return SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE; + } + LOG_INFO_IF(log_file_videoout, "startIndex = {}\n", startIndex); + LOG_INFO_IF(log_file_videoout, "bufferNum = {}\n", bufferNum); + LOG_INFO_IF(log_file_videoout, "pixelFormat = {}\n", log_hex_full(attribute->pixelFormat)); + LOG_INFO_IF(log_file_videoout, "tilingMode = {}\n", attribute->tilingMode); + LOG_INFO_IF(log_file_videoout, "aspectRatio = {}\n", attribute->aspectRatio); + LOG_INFO_IF(log_file_videoout, "width = {}\n", attribute->width); + LOG_INFO_IF(log_file_videoout, "height = {}\n", attribute->height); + LOG_INFO_IF(log_file_videoout, "pitchInPixel = {}\n", attribute->pitchInPixel); + LOG_INFO_IF(log_file_videoout, "option = {}\n", attribute->option); + + int registration_index = ctx->buffers_registration_index++; + + Emulator::checkAndWaitForGraphicsInit(); + GPU::renderCreateCtx(); + + // try to calculate buffer size + u64 buffer_size = 1280 * 768 * 4; // TODO hardcoded value should be redone + 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_TRACE_IF(log_file_videoout, "buffer slot {} is occupied!\n", i + startIndex); + return SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED; + } + + ctx->buffers[i + startIndex].set_id = registration_index; + ctx->buffers[i + startIndex].buffer = addresses[i]; + ctx->buffers[i + startIndex].buffer_size = buffer_size; + ctx->buffers[i + startIndex].buffer_pitch = buffer_pitch; + ctx->buffers[i + startIndex].buffer_render = static_cast( + GPU::memoryCreateObj( + 0, videoOut->getGraphicCtx(), nullptr, reinterpret_cast(addresses[i]), buffer_size, buffer_info)); + + LOG_INFO_IF(log_file_videoout, "buffers[{}] = {}\n", i + startIndex, log_hex_full(reinterpret_cast(addresses[i]))); + } + + return registration_index; } s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) { PRINT_FUNCTION_NAME(); diff --git a/src/Core/PS4/HLE/Graphics/video_out.h b/src/Core/PS4/HLE/Graphics/video_out.h index 3ea6fb54..c3250891 100644 --- a/src/Core/PS4/HLE/Graphics/video_out.h +++ b/src/Core/PS4/HLE/Graphics/video_out.h @@ -87,6 +87,13 @@ struct SceVideoOutVblankStatus { u08 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(SymbolsResolver* sym); diff --git a/src/Core/PS4/HLE/Kernel/memory_management.cpp b/src/Core/PS4/HLE/Kernel/memory_management.cpp index 4897ad38..220e81be 100644 --- a/src/Core/PS4/HLE/Kernel/memory_management.cpp +++ b/src/Core/PS4/HLE/Kernel/memory_management.cpp @@ -120,7 +120,7 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl } if (gpu_mode != GPU::MemoryMode::NoAccess) { - GPU::MemorySetAllocArea(out_addr, len); + GPU::memorySetAllocArea(out_addr, len); } return SCE_OK; } diff --git a/src/Core/PS4/HLE/LibSceGnmDriver.cpp b/src/Core/PS4/HLE/LibSceGnmDriver.cpp index cc48ba45..9744b23f 100644 --- a/src/Core/PS4/HLE/LibSceGnmDriver.cpp +++ b/src/Core/PS4/HLE/LibSceGnmDriver.cpp @@ -3,6 +3,8 @@ #include "../Loader/Elf.h" #include #include +#include +#include namespace HLE::Libs::LibSceGnmDriver { @@ -88,7 +90,8 @@ namespace HLE::Libs::LibSceGnmDriver { void sceGnmDriverTraceInProgress(){} void sceGnmDriverTriggerCapture(){} void sceGnmEndWorkload(){} - void sceGnmFlushGarlic() { PRINT_DUMMY_FUNCTION_NAME(); + void sceGnmFlushGarlic() { PRINT_FUNCTION_NAME(); + GPU::flushGarlic(Emulator::getGraphicCtx()); } void sceGnmGetEqEventType(){} void sceGnmGetEqTimeStamp(){} diff --git a/src/emulator.cpp b/src/emulator.cpp index 0fa5ca5d..74bd15ff 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,19 +1,30 @@ #include "emulator.h" +#include +#include +#include + #include "Core/PS4/HLE/Graphics/video_out.h" namespace Emulator { -static WindowCtx* g_window_ctx = nullptr; bool m_emu_needs_exit = false; void emuInit(u32 width, u32 height) { - g_window_ctx = new WindowCtx; + auto* window_ctx = Singleton::Instance(); - g_window_ctx->m_graphic_ctx.screen_width = width; - g_window_ctx->m_graphic_ctx.screen_height = height; + window_ctx->m_graphic_ctx.screen_width = width; + window_ctx->m_graphic_ctx.screen_height = height; } +void checkAndWaitForGraphicsInit() { + auto* window_ctx = Singleton::Instance(); + Lib::LockMutexGuard lock(window_ctx->m_mutex); + + while (!window_ctx->m_is_graphic_initialized) { + window_ctx->m_graphic_initialized_cond.WaitCondVar(&window_ctx->m_mutex); + } +} static void CreateSdlWindow(WindowCtx* ctx) { int width = static_cast(ctx->m_graphic_ctx.screen_width); int height = static_cast(ctx->m_graphic_ctx.screen_height); @@ -34,17 +45,18 @@ static void CreateSdlWindow(WindowCtx* ctx) { } SDL_SetWindowResizable(ctx->m_window, SDL_FALSE); // we don't support resizable atm - SDL_ShowWindow(g_window_ctx->m_window); // TODO should be removed just left it over to make it fancy :D } void emuRun() { - g_window_ctx->m_mutex.LockMutex(); + auto* window_ctx = Singleton::Instance(); + window_ctx->m_mutex.LockMutex(); { // init window and wait until init finishes - CreateSdlWindow(g_window_ctx); - g_window_ctx->m_is_graphic_initialized = true; - g_window_ctx->m_graphic_initialized_cond.SignalCondVar(); + CreateSdlWindow(window_ctx); + Graphics::Vulkan::vulkanCreate(window_ctx); + window_ctx->m_is_graphic_initialized = true; + window_ctx->m_graphic_initialized_cond.SignalCondVar(); } - g_window_ctx->m_mutex.UnlockMutex(); + window_ctx->m_mutex.UnlockMutex(); bool exit_loop = false; @@ -81,4 +93,108 @@ void emuRun() { } std::exit(0); } + +HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() { + auto* window_ctx = Singleton::Instance(); + Lib::LockMutexGuard lock(window_ctx->m_mutex); + + return &window_ctx->m_graphic_ctx; +} + +void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image) { + auto* window_ctx = Singleton::Instance(); + if (window_ctx->is_window_hidden) { + SDL_ShowWindow(window_ctx->m_window); + window_ctx->is_window_hidden = false; + } + + window_ctx->swapchain->current_index = static_cast(-1); + + auto result = vkAcquireNextImageKHR(window_ctx->m_graphic_ctx.m_device, window_ctx->swapchain->swapchain, UINT64_MAX, nullptr, + window_ctx->swapchain->present_complete_fence, &window_ctx->swapchain->current_index); + + if (result != VK_SUCCESS) { + printf("Can't aquireNextImage\n"); + std::exit(0); + } + if (window_ctx->swapchain->current_index == static_cast(-1)) { + printf("Unsupported:swapchain current index is -1\n"); + std::exit(0); + } + + do { + result = vkWaitForFences(window_ctx->m_graphic_ctx.m_device, 1, &window_ctx->swapchain->present_complete_fence, VK_TRUE, 100000000); + } while (result == VK_TIMEOUT); + if (result != VK_SUCCESS) { + printf("vkWaitForFences is not success\n"); + std::exit(0); + } + + vkResetFences(window_ctx->m_graphic_ctx.m_device, 1, &window_ctx->swapchain->present_complete_fence); + + auto* blt_src_image = image; + auto* blt_dst_image = window_ctx->swapchain; + + if (blt_src_image == nullptr) { + printf("blt_src_image is null\n"); + std::exit(0); + } + if (blt_dst_image == nullptr) { + printf("blt_dst_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(); + + 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) { + printf("queue.mutexe is null\n"); + std::exit(0); + } + + result = vkQueuePresentKHR(queue.vk_queue, &present); + if (result != VK_SUCCESS) { + printf("vkQueuePresentKHR failed\n"); + std::exit(0); + } +} + } // namespace Emulator \ No newline at end of file diff --git a/src/emulator.h b/src/emulator.h index d86b1ef6..b3d2c0e3 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -3,7 +3,58 @@ #include #include +#include + namespace Emulator { + +struct VulkanExt { + bool enable_validation_layers = false; + + std::vector required_extensions; + std::vector available_extensions; + std::vector required_layers; + std::vector available_layers; +}; + +struct VulkanSurfaceCapabilities { + VkSurfaceCapabilitiesKHR capabilities{}; + std::vector formats; + std::vector present_modes; + bool is_format_srgb_bgra32 = false; + bool is_format_unorm_bgra32 = false; +}; + +struct VulkanQueueInfo { + u32 family = 0; + u32 index = 0; + bool is_graphics = false; + bool is_compute = false; + bool is_transfer = false; + bool is_present = false; +}; + +struct VulkanQueues { + u32 family_count = 0; + std::vector available; + std::vector graphics; + std::vector compute; + std::vector transfer; + std::vector present; + std::vector family_used; +}; + +struct VulkanSwapchain { + VkSwapchainKHR swapchain = nullptr; + VkFormat swapchain_format = VK_FORMAT_UNDEFINED; + VkExtent2D swapchain_extent = {}; + VkImage* swapchain_images = nullptr; + VkImageView* swapchain_image_views = nullptr; + 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; Lib::Mutex m_mutex; @@ -11,6 +62,9 @@ struct WindowCtx { Lib::ConditionVariable m_graphic_initialized_cond; SDL_Window* m_window = nullptr; bool is_window_hidden = true; + VkSurfaceKHR m_surface = nullptr; + VulkanSurfaceCapabilities* m_surface_capabilities = nullptr; + VulkanSwapchain* swapchain = nullptr; }; struct EmuPrivate { @@ -24,4 +78,7 @@ struct EmuPrivate { }; void emuInit(u32 width, u32 height); void emuRun(); +void checkAndWaitForGraphicsInit(); +HLE::Libs::Graphics::GraphicCtx* getGraphicCtx(); +void DrawBuffer(HLE::Libs::Graphics::VideoOutVulkanImage* image); } // namespace Emulator \ No newline at end of file diff --git a/src/vulkan_util.cpp b/src/vulkan_util.cpp new file mode 100644 index 00000000..42bf21eb --- /dev/null +++ b/src/vulkan_util.cpp @@ -0,0 +1,604 @@ +#include "vulkan_util.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +constexpr bool log_file_vulkanutil = true; // disable it to disable logging + +void Graphics::Vulkan::vulkanCreate(Emulator::WindowCtx* ctx) { + Emulator::VulkanExt ext; + vulkanGetInstanceExtensions(&ext); + + VkApplicationInfo app_info{}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pNext = nullptr; + app_info.pApplicationName = "shadps4"; + app_info.applicationVersion = 1; + app_info.pEngineName = "shadps4"; + app_info.engineVersion = 1; + app_info.apiVersion = VK_API_VERSION_1_2; + + VkInstanceCreateInfo inst_info{}; + inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + inst_info.pNext = nullptr; + inst_info.flags = 0; + inst_info.pApplicationInfo = &app_info; + inst_info.enabledExtensionCount = ext.required_extensions.size(); + inst_info.ppEnabledExtensionNames = ext.required_extensions.data(); + inst_info.enabledLayerCount = 0; + inst_info.ppEnabledLayerNames = nullptr; + + VkResult result = vkCreateInstance(&inst_info, nullptr, &ctx->m_graphic_ctx.m_instance); + if (result == VK_ERROR_INCOMPATIBLE_DRIVER) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Can't find an compatiblie vulkan driver\n"); + std::exit(0); + } else if (result != VK_SUCCESS) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create an vulkan instance\n"); + std::exit(0); + } + + if (SDL_Vulkan_CreateSurface(ctx->m_window, ctx->m_graphic_ctx.m_instance, &ctx->m_surface) == SDL_FALSE) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create an vulkan surface\n"); + std::exit(0); + } + + // TODO i am not sure if it's that it is neccesary or if it needs more + std::vector device_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, "VK_KHR_maintenance1"}; + + ctx->m_surface_capabilities = new Emulator::VulkanSurfaceCapabilities{}; + Emulator::VulkanQueues queues; + + vulkanFindCompatiblePhysicalDevice(ctx->m_graphic_ctx.m_instance, ctx->m_surface, device_extensions, ctx->m_surface_capabilities, + &ctx->m_graphic_ctx.m_physical_device, &queues); + + if (ctx->m_graphic_ctx.m_physical_device == nullptr) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Can't find compatible vulkan device\n"); + std::exit(0); + } + + VkPhysicalDeviceProperties device_properties{}; + vkGetPhysicalDeviceProperties(ctx->m_graphic_ctx.m_physical_device, &device_properties); + + LOG_INFO_IF(log_file_vulkanutil, "GFX device to be used : {}\n", device_properties.deviceName); + + ctx->m_graphic_ctx.m_device = vulkanCreateDevice(ctx->m_graphic_ctx.m_physical_device, ctx->m_surface, &ext, queues, device_extensions); + if (ctx->m_graphic_ctx.m_device == nullptr) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create vulkan device\n"); + std::exit(0); + } + + vulkanCreateQueues(&ctx->m_graphic_ctx, queues); + ctx->swapchain = vulkanCreateSwapchain(&ctx->m_graphic_ctx, 2); +} + +Emulator::VulkanSwapchain* Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics::GraphicCtx* ctx, u32 image_count) { + auto* window_ctx = Singleton::Instance(); + Lib::LockMutexGuard lock(window_ctx->m_mutex); + + auto* s = new Emulator::VulkanSwapchain; + + VkExtent2D extent{}; + extent.width = clamp(ctx->screen_width, window_ctx->m_surface_capabilities->capabilities.minImageExtent.width, + window_ctx->m_surface_capabilities->capabilities.maxImageExtent.width); + extent.height = clamp(ctx->screen_height, window_ctx->m_surface_capabilities->capabilities.minImageExtent.height, + window_ctx->m_surface_capabilities->capabilities.maxImageExtent.height); + + image_count = clamp(image_count, window_ctx->m_surface_capabilities->capabilities.minImageCount, + window_ctx->m_surface_capabilities->capabilities.maxImageCount); + + VkSwapchainCreateInfoKHR create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + create_info.pNext = nullptr; + create_info.flags = 0; + create_info.surface = window_ctx->m_surface; + create_info.minImageCount = image_count; + + if (window_ctx->m_surface_capabilities->is_format_unorm_bgra32) { + create_info.imageFormat = VK_FORMAT_B8G8R8A8_UNORM; + create_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } else if (window_ctx->m_surface_capabilities->is_format_srgb_bgra32) { + create_info.imageFormat = VK_FORMAT_B8G8R8A8_SRGB; + create_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } else { + create_info.imageFormat = window_ctx->m_surface_capabilities->formats.at(0).format; + create_info.imageColorSpace = window_ctx->m_surface_capabilities->formats.at(0).colorSpace; + } + + create_info.imageExtent = extent; + create_info.imageArrayLayers = 1; + create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + create_info.queueFamilyIndexCount = 0; + create_info.pQueueFamilyIndices = nullptr; + create_info.preTransform = window_ctx->m_surface_capabilities->capabilities.currentTransform; + create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; + create_info.clipped = VK_TRUE; + create_info.oldSwapchain = nullptr; + + s->swapchain_format = create_info.imageFormat; + s->swapchain_extent = extent; + + vkCreateSwapchainKHR(ctx->m_device, &create_info, nullptr, &s->swapchain); + + vkGetSwapchainImagesKHR(ctx->m_device, s->swapchain, &s->swapchain_images_count, nullptr); + + s->swapchain_images = new VkImage[s->swapchain_images_count]; + vkGetSwapchainImagesKHR(ctx->m_device, s->swapchain, &s->swapchain_images_count, s->swapchain_images); + + s->swapchain_image_views = new VkImageView[s->swapchain_images_count]; + for (uint32_t i = 0; i < s->swapchain_images_count; i++) { + VkImageViewCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + create_info.pNext = nullptr; + create_info.flags = 0; + create_info.image = (s->swapchain_images)[i]; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + create_info.format = s->swapchain_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, &((s->swapchain_image_views)[i])); + } + if (s->swapchain == nullptr) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Could not create swapchain\n"); + std::exit(0); + } + + s->current_index = static_cast(-1); + + VkSemaphoreCreateInfo present_complete_info{}; + present_complete_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + present_complete_info.pNext = nullptr; + present_complete_info.flags = 0; + + auto result = vkCreateSemaphore(ctx->m_device, &present_complete_info, nullptr, &s->present_complete_semaphore); + + VkFenceCreateInfo fence_info{}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_info.pNext = nullptr; + fence_info.flags = 0; + + result = vkCreateFence(ctx->m_device, &fence_info, nullptr, &s->present_complete_fence); + if (result != VK_SUCCESS) { + LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create vulkan fence\n"); + std::exit(0); + } + + return s; +} + +void Graphics::Vulkan::vulkanCreateQueues(HLE::Libs::Graphics::GraphicCtx* ctx, const Emulator::VulkanQueues& queues) { + auto get_queue = [ctx](int id, const Emulator::VulkanQueueInfo& info, bool with_mutex = false) { + ctx->queues[id].family = info.family; + ctx->queues[id].index = info.index; + vkGetDeviceQueue(ctx->m_device, ctx->queues[id].family, ctx->queues[id].index, &ctx->queues[id].vk_queue); + if (with_mutex) { + ctx->queues[id].mutex = new Lib::Mutex; + } + }; + + get_queue(VULKAN_QUEUE_GFX, queues.graphics.at(0)); + get_queue(VULKAN_QUEUE_UTIL, queues.transfer.at(0)); + get_queue(VULKAN_QUEUE_PRESENT, queues.present.at(0)); + + for (int id = 0; id < VULKAN_QUEUE_COMPUTE_NUM; id++) { + bool with_mutex = (VULKAN_QUEUE_COMPUTE_NUM == queues.compute.size()); + get_queue(id, queues.compute.at(id % queues.compute.size()), with_mutex); + } +} + +VkDevice Graphics::Vulkan::vulkanCreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const Emulator::VulkanExt* r, + const Emulator::VulkanQueues& queues, const std::vector& device_extensions) { + std::vector queue_create_info(queues.family_count); + std::vector> queue_priority(queues.family_count); + uint32_t queue_create_info_num = 0; + + for (uint32_t i = 0; i < queues.family_count; i++) { + if (queues.family_used[i] != 0) { + for (uint32_t pi = 0; pi < queues.family_used[i]; pi++) { + queue_priority[queue_create_info_num].push_back(1.0f); + } + + queue_create_info[queue_create_info_num].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[queue_create_info_num].pNext = nullptr; + queue_create_info[queue_create_info_num].flags = 0; + queue_create_info[queue_create_info_num].queueFamilyIndex = i; + queue_create_info[queue_create_info_num].queueCount = queues.family_used[i]; + queue_create_info[queue_create_info_num].pQueuePriorities = queue_priority[queue_create_info_num].data(); + + queue_create_info_num++; + } + } + + VkPhysicalDeviceFeatures device_features{}; + // TODO add neccesary device features + + VkDeviceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_info.pNext = nullptr; + create_info.flags = 0; + create_info.pQueueCreateInfos = queue_create_info.data(); + create_info.queueCreateInfoCount = queue_create_info_num; + create_info.enabledLayerCount = 0; + create_info.ppEnabledLayerNames = nullptr; + create_info.enabledExtensionCount = device_extensions.size(); + create_info.ppEnabledExtensionNames = device_extensions.data(); + create_info.pEnabledFeatures = &device_features; + + VkDevice device = nullptr; + + vkCreateDevice(physical_device, &create_info, nullptr, &device); + + return device; +} +void Graphics::Vulkan::vulkanGetInstanceExtensions(Emulator::VulkanExt* ext) { + u32 required_extensions_count = 0; + u32 available_extensions_count = 0; + u32 available_layers_count = 0; + auto result = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count, nullptr); + + ext->required_extensions = std::vector(required_extensions_count); + + result = SDL_Vulkan_GetInstanceExtensions(&required_extensions_count, ext->required_extensions.data()); + + vkEnumerateInstanceExtensionProperties(nullptr, &available_extensions_count, nullptr); + + ext->available_extensions = std::vector(available_extensions_count); + + vkEnumerateInstanceExtensionProperties(nullptr, &available_extensions_count, ext->available_extensions.data()); + + vkEnumerateInstanceLayerProperties(&available_layers_count, nullptr); + ext->available_layers = std::vector(available_layers_count); + vkEnumerateInstanceLayerProperties(&available_layers_count, ext->available_layers.data()); + + for (const char* ext : ext->required_extensions) { + LOG_INFO_IF(log_file_vulkanutil, "Vulkan required extension = {}\n", ext); + } + + for (const auto& ext : ext->available_extensions) { + LOG_INFO_IF(log_file_vulkanutil, "Vulkan available extension: {}, version = {}\n", ext.extensionName, ext.specVersion); + } + + for (const auto& l : ext->available_layers) { + LOG_INFO_IF(log_file_vulkanutil, "Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}\n", l.layerName, l.specVersion, + l.implementationVersion, l.description); + } +} + +void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(VkInstance instance, VkSurfaceKHR surface, + const std::vector& device_extensions, + Emulator::VulkanSurfaceCapabilities* out_capabilities, VkPhysicalDevice* out_device, + Emulator::VulkanQueues* out_queues) { + u32 count_devices = 0; + vkEnumeratePhysicalDevices(instance, &count_devices, nullptr); + + std::vector devices(count_devices); + vkEnumeratePhysicalDevices(instance, &count_devices, devices.data()); + + VkPhysicalDevice found_best_device = nullptr; + Emulator::VulkanQueues found_best_queues; + + for (const auto& device : devices) { + VkPhysicalDeviceProperties device_properties{}; + VkPhysicalDeviceFeatures2 device_features2{}; + + vkGetPhysicalDeviceProperties(device, &device_properties); + vkGetPhysicalDeviceFeatures2(device, &device_features2); + if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { + continue; // we don't want integrated gpu for now .Later we will check the requirements and see what we can support (TODO fix me) + } + LOG_INFO_IF(log_file_vulkanutil, "Vulkan device: {}\n", device_properties.deviceName); + + auto qs = vulkanFindQueues(device, surface); + + vulkanGetSurfaceCapabilities(device, surface, out_capabilities); + + found_best_device = device; + found_best_queues = qs; + } + *out_device = found_best_device; + *out_queues = found_best_queues; +} + +Emulator::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device, VkSurfaceKHR surface) { + Emulator::VulkanQueues qs; + + u32 queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr); + std::vector queue_families(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data()); + + qs.family_count = queue_family_count; + + u32 family = 0; + for (auto& f : queue_families) { + VkBool32 presentation_supported = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(device, family, surface, &presentation_supported); + + LOG_INFO_IF(log_file_vulkanutil, "queue family: {}, count = {}, present = {}\n", string_VkQueueFlags(f.queueFlags).c_str(), f.queueCount, + (presentation_supported == VK_TRUE ? "true" : "false")); + for (uint32_t i = 0; i < f.queueCount; i++) { + Emulator::VulkanQueueInfo info; + info.family = family; + info.index = i; + info.is_graphics = (f.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0; + info.is_compute = (f.queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; + info.is_transfer = (f.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0; + info.is_present = (presentation_supported == VK_TRUE); + + qs.available.push_back(info); + } + + qs.family_used.push_back(0); + + family++; + } + u32 index = 0; + for (u32 i = 0; i < VULKAN_QUEUE_GRAPHICS_NUM; i++) { + for (const auto& idx : qs.available) { + if (idx.is_graphics) { + qs.family_used[qs.available.at(index).family]++; + qs.graphics.push_back(qs.available.at(index)); + qs.available.erase(qs.available.begin() + index); + break; + } + index++; + } + } + index = 0; + for (u32 i = 0; i < VULKAN_QUEUE_COMPUTE_NUM; i++) { + for (const auto& idx : qs.available) { + if (idx.is_graphics) { + qs.family_used[qs.available.at(index).family]++; + qs.compute.push_back(qs.available.at(index)); + qs.available.erase(qs.available.begin() + index); + break; + } + index++; + } + } + index = 0; + for (uint32_t i = 0; i < VULKAN_QUEUE_TRANSFER_NUM; i++) { + for (const auto& idx : qs.available) { + if (idx.is_graphics) { + qs.family_used[qs.available.at(index).family]++; + qs.transfer.push_back(qs.available.at(index)); + qs.available.erase(qs.available.begin() + index); + break; + } + index++; + } + } + index = 0; + for (uint32_t i = 0; i < VULKAN_QUEUE_PRESENT_NUM; i++) { + for (const auto& idx : qs.available) { + if (idx.is_graphics) { + qs.family_used[qs.available.at(index).family]++; + qs.present.push_back(qs.available.at(index)); + qs.available.erase(qs.available.begin() + index); + break; + } + index++; + } + } + return qs; +} + +void Graphics::Vulkan::vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + Emulator::VulkanSurfaceCapabilities* surfaceCap) { + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surfaceCap->capabilities); + + uint32_t formats_count = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, nullptr); + + surfaceCap->formats = std::vector(formats_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &formats_count, surfaceCap->formats.data()); + + uint32_t present_modes_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, nullptr); + + surfaceCap->present_modes = std::vector(present_modes_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, surfaceCap->present_modes.data()); + + for (const auto& f : surfaceCap->formats) { + if (f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surfaceCap->is_format_srgb_bgra32 = true; + break; + } + if (f.format == VK_FORMAT_B8G8R8A8_UNORM && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + surfaceCap->is_format_unorm_bgra32 = true; + break; + } + } +} + +static void set_image_layout(VkCommandBuffer buffer, HLE::Libs::Graphics::VulkanImage* dst_image, uint32_t base_level, uint32_t levels, + VkImageAspectFlags aspect_mask, VkImageLayout old_image_layout, VkImageLayout new_image_layout) { + VkImageMemoryBarrier imageMemoryBarrier{}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.pNext = nullptr; + imageMemoryBarrier.srcAccessMask = 0; + imageMemoryBarrier.dstAccessMask = 0; + imageMemoryBarrier.oldLayout = old_image_layout; + imageMemoryBarrier.newLayout = new_image_layout; + imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemoryBarrier.image = dst_image->image; + imageMemoryBarrier.subresourceRange.aspectMask = aspect_mask; + imageMemoryBarrier.subresourceRange.baseMipLevel = base_level; + imageMemoryBarrier.subresourceRange.levelCount = levels; + imageMemoryBarrier.subresourceRange.baseArrayLayer = 0; + imageMemoryBarrier.subresourceRange.layerCount = 1; + + if (old_image_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + imageMemoryBarrier.srcAccessMask = 0; + } + + if (new_image_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + imageMemoryBarrier.dstAccessMask = 0; + } + + if (new_image_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + imageMemoryBarrier.dstAccessMask = 0; + } + + if (old_image_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + imageMemoryBarrier.srcAccessMask = 0; + } + + if (old_image_layout == VK_IMAGE_LAYOUT_PREINITIALIZED) { + imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + } + + if (new_image_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + imageMemoryBarrier.srcAccessMask = 0; + imageMemoryBarrier.dstAccessMask = 0; + } + + if (new_image_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + imageMemoryBarrier.dstAccessMask = 0; + } + + if (new_image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + imageMemoryBarrier.dstAccessMask = 0; + } + + 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); + + dst_image->layout = new_image_layout; +} + +void Graphics::Vulkan::vulkanBlitImage(GPU::CommandBuffer* buffer, HLE::Libs::Graphics::VulkanImage* src_image, + Emulator::VulkanSwapchain* dst_swapchain) { + auto* vk_buffer = buffer->getPool()->buffers[buffer->getIndex()]; + + HLE::Libs::Graphics::VulkanImage swapchain_image(HLE::Libs::Graphics::VulkanImageType::Unknown); + + swapchain_image.image = dst_swapchain->swapchain_images[dst_swapchain->current_index]; + swapchain_image.layout = VK_IMAGE_LAYOUT_UNDEFINED; + + set_image_layout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + set_image_layout(vk_buffer, &swapchain_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + VkImageBlit region{}; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.mipLevel = 0; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + region.srcOffsets[0].x = 0; + region.srcOffsets[0].y = 0; + region.srcOffsets[0].z = 0; + region.srcOffsets[1].x = static_cast(src_image->extent.width); + region.srcOffsets[1].y = static_cast(src_image->extent.height); + region.srcOffsets[1].z = 1; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.dstSubresource.mipLevel = 0; + region.dstSubresource.baseArrayLayer = 0; + region.dstSubresource.layerCount = 1; + region.dstOffsets[0].x = 0; + region.dstOffsets[0].y = 0; + region.dstOffsets[0].z = 0; + region.dstOffsets[1].x = static_cast(dst_swapchain->swapchain_extent.width); + region.dstOffsets[1].y = static_cast(dst_swapchain->swapchain_extent.height); + region.dstOffsets[1].z = 1; + + vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, + ®ion, VK_FILTER_LINEAR); + + set_image_layout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); +} + +void Graphics::Vulkan::vulkanFillImage(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanImage* dst_image, const void* src_data, + u64 size, u32 src_pitch, u64 dst_layout) { + HLE::Libs::Graphics::VulkanBuffer staging_buffer{}; + staging_buffer.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + staging_buffer.memory.property = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + 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); + + GPU::CommandBuffer buffer(9); + + buffer.begin(); + vulkanBufferToImage(&buffer, &staging_buffer, src_pitch, dst_image, dst_layout); + buffer.end(); + buffer.execute(); + buffer.waitForFence(); + + vulkanDeleteBuffer(ctx, &staging_buffer); +} + +void Graphics::Vulkan::vulkanBufferToImage(GPU::CommandBuffer* buffer, HLE::Libs::Graphics::VulkanBuffer* src_buffer, u32 src_pitch, + HLE::Libs::Graphics::VulkanImage* dst_image, u64 dst_layout) { + auto* vk_buffer = buffer->getPool()->buffers[buffer->getIndex()]; + + set_image_layout(vk_buffer, dst_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = (src_pitch != dst_image->extent.width ? src_pitch : 0); + region.bufferImageHeight = 0; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + + region.imageOffset = {0, 0, 0}; + region.imageExtent = {dst_image->extent.width, dst_image->extent.height, 1}; + + vkCmdCopyBufferToImage(vk_buffer, src_buffer->buffer, dst_image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + set_image_layout(vk_buffer, dst_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + static_cast(dst_layout)); +} + +void Graphics::Vulkan::vulkanCreateBuffer(HLE::Libs::Graphics::GraphicCtx* ctx, u64 size, HLE::Libs::Graphics::VulkanBuffer* buffer) { + VkBufferCreateInfo buffer_info{}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = size; + buffer_info.usage = buffer->usage; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + vkCreateBuffer(ctx->m_device, &buffer_info, nullptr, &buffer->buffer); + + vkGetBufferMemoryRequirements(ctx->m_device, buffer->buffer, &buffer->memory.requirements); + + bool allocated = GPU::vulkanAllocateMemory(ctx, &buffer->memory); + if (!allocated) { + printf("Can't allocate vulkan\n"); + std::exit(0); + } + 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); + buffer->memory.memory = nullptr; + buffer->buffer = nullptr; +} diff --git a/src/vulkan_util.h b/src/vulkan_util.h new file mode 100644 index 00000000..e25d9880 --- /dev/null +++ b/src/vulkan_util.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include + +#include + +#include "emulator.h" + +namespace Graphics::Vulkan { + +constexpr int VULKAN_QUEUES_NUM = 11; // Total of the above +constexpr int VULKAN_QUEUE_GRAPHICS_NUM = 1; +constexpr int VULKAN_QUEUE_TRANSFER_NUM = 1; +constexpr int VULKAN_QUEUE_PRESENT_NUM = 1; +constexpr int VULKAN_QUEUE_COMPUTE_NUM = 8; + +constexpr int VULKAN_QUEUE_GFX = 8; +constexpr int VULKAN_QUEUE_UTIL = 9; +constexpr int VULKAN_QUEUE_PRESENT = 10; + +template +const T& clamp(const T& x, const T& min, const T& max) { + if (x < min) return min; + if (x > max) return max; + return x; +} + +void vulkanCreate(Emulator::WindowCtx* ctx); +void vulkanGetInstanceExtensions(Emulator::VulkanExt* ext); +void vulkanFindCompatiblePhysicalDevice(VkInstance instance, VkSurfaceKHR surface, const std::vector& device_extensions, + Emulator::VulkanSurfaceCapabilities* out_capabilities, VkPhysicalDevice* out_device, + Emulator::VulkanQueues* out_queues); +VkDevice vulkanCreateDevice(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const Emulator::VulkanExt* r, + const Emulator::VulkanQueues& queues, const std::vector& device_extensions); +Emulator::VulkanQueues vulkanFindQueues(VkPhysicalDevice device, VkSurfaceKHR surface); +void vulkanGetSurfaceCapabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, Emulator::VulkanSurfaceCapabilities* surfaceCap); +void vulkanCreateQueues(HLE::Libs::Graphics::GraphicCtx* ctx, const Emulator::VulkanQueues& queues); +Emulator::VulkanSwapchain* vulkanCreateSwapchain(HLE::Libs::Graphics::GraphicCtx* ctx, u32 image_count); +void vulkanBlitImage(GPU::CommandBuffer* buffer, HLE::Libs::Graphics::VulkanImage* src_image, Emulator::VulkanSwapchain* dst_swapchain); +void vulkanFillImage(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanImage* dst_image, const void* src_data, u64 size, u32 src_pitch, + u64 dst_layout); +void vulkanBufferToImage(GPU::CommandBuffer* buffer, HLE::Libs::Graphics::VulkanBuffer* src_buffer, u32 src_pitch, + HLE::Libs::Graphics::VulkanImage* dst_image, u64 dst_layout); +void vulkanCreateBuffer(HLE::Libs::Graphics::GraphicCtx* ctx, u64 size, HLE::Libs::Graphics::VulkanBuffer* buffer); +void vulkanDeleteBuffer(HLE::Libs::Graphics::GraphicCtx* ctx, HLE::Libs::Graphics::VulkanBuffer* buffer); +}; // namespace Graphics::Vulkan \ No newline at end of file diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index a3ee0a2f..0b0db59c 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -33,6 +33,8 @@ add_subdirectory(winpthread) add_subdirectory(discord-rpc) #=================== toml11 =================== add_subdirectory(toml11) +#=================== vulkan ================== +add_subdirectory(vulkan) #=================== IMGUI =================== set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/imgui) diff --git a/third-party/vulkan b/third-party/vulkan new file mode 160000 index 00000000..8c9feb4f --- /dev/null +++ b/third-party/vulkan @@ -0,0 +1 @@ +Subproject commit 8c9feb4f480b32f7c7421af546aa6ffb558bdd5e diff --git a/third-party/xxHash b/third-party/xxHash new file mode 160000 index 00000000..058e54b1 --- /dev/null +++ b/third-party/xxHash @@ -0,0 +1 @@ +Subproject commit 058e54b10b10c658fd50862e6f65f55522afa182