Fix: Implement RAII for Vulkan Resource Management

- Introduced RAII (Resource Acquisition Is Initialization)
for Vulkan objects to ensure proper resource cleanup.
- Added RAII wrappers for Vulkan buffers, images, pipelines,
command pools, and descriptor sets.
- Updated renderer_vulkan.cpp to replace manual resource management with RAII.
- Refactored vulkan_raii.h to include move constructors,
proper destruction, and memory allocation fixes.
- Ensured vkDeviceWaitIdle() is properly called before destroying Vulkan objects.
- Fixed synchronization issues by validating fence and semaphore handling.
- Improved swapchain recreation logic to prevent stale image usage.
- Updated CMakeLists.txt to properly link Vulkan RAII dependencies.
- Applied Turbo Mode optimizations with RAII-based memory handling.
- Verified Vulkan cleanup sequence to avoid crashes and memory leaks.
- Added EDEN Project Emulator Copyright header to all the files modified
- Implementations/Changes maded by @Camille LaVey
This commit is contained in:
CamilleLaVey 2025-04-02 14:03:23 -04:00 committed by JPikachu
parent e59f7922a1
commit 497279b6cf
11 changed files with 563 additions and 66 deletions

View file

@ -1,4 +1,6 @@
# SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project / Eden Emulator Project
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2023 EDEN Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
cmake_minimum_required(VERSION 3.22)
@ -90,8 +92,6 @@ if (ANDROID OR WIN32 OR APPLE)
# - macOS defaults to the SecureTransport backend.
# - Android currently has no SSL backend as the NDK doesn't include any SSL
# library; a proper 'native' backend would have to go through Java.
# But you can force builds for those platforms to use OpenSSL if you have
# your own copy of it.
set(DEFAULT_ENABLE_OPENSSL OFF)
endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
@ -651,3 +651,8 @@ if(ENABLE_QT AND UNIX AND NOT APPLE)
install(FILES "dist/org.yuzu_emu.yuzu.metainfo.xml"
DESTINATION "share/metainfo")
endif()
target_include_directories(video_core PRIVATE
${CMAKE_SOURCE_DIR}/src/video_core/vulkan_common
)
target_link_libraries(video_core PRIVATE Vulkan::Vulkan)

View file

@ -1,5 +1,7 @@
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# SPDX-FileCopyrightText: 2025 EDEN Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# Enable modules to include each other's files
include_directories(.)
@ -193,6 +195,15 @@ add_subdirectory(input_common)
add_subdirectory(frontend_common)
add_subdirectory(shader_recompiler)
# Ensure the Vulkan common directory is included
target_include_directories(video_core PRIVATE
${CMAKE_SOURCE_DIR}/src/video_core/vulkan_common
)
# Link Vulkan library
find_package(Vulkan REQUIRED)
target_link_libraries(video_core PRIVATE Vulkan::Vulkan)
if (YUZU_ROOM)
add_subdirectory(dedicated_room)
endif()

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <array>
@ -35,6 +37,7 @@
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vulkan_raii.h"
namespace Vulkan {
namespace {
@ -106,7 +109,9 @@ RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window,
debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
: vk::DebugUtilsMessenger{}),
surface(CreateSurface(instance, render_window.GetWindowInfo())),
device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(),
device(CreateDevice(instance, dld, *surface)),
memory_allocator(device),
state_tracker(),
scheduler(device, state_tracker),
swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
render_window.GetFramebufferLayout().height),
@ -257,10 +262,10 @@ std::vector<u8> RendererVulkan::GetAppletCaptureBuffer() {
void RendererVulkan::RenderAppletCaptureLayer(
std::span<const Tegra::FramebufferConfig> framebuffers) {
if (!applet_frame.image) {
applet_frame.image = CreateWrappedImage(memory_allocator, CaptureImageSize, CaptureFormat);
applet_frame.image_view = CreateWrappedImageView(device, applet_frame.image, CaptureFormat);
applet_frame.framebuffer = blit_applet.CreateFramebuffer(
VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat);
applet_frame.image = std::make_unique<VulkanImage>(device, CaptureImageSize, CaptureFormat);
applet_frame.image_view = std::make_unique<VulkanImageView>(device, applet_frame.image->Get(), CaptureFormat);
applet_frame.framebuffer = std::make_unique<VulkanFramebuffer>(
blit_applet.CreateFramebuffer(VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat));
}
blit_applet.DrawToFrame(rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1,

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
#include <adrenotools/driver.h>
@ -11,6 +13,8 @@
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_turbo_mode.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_raii.h"
namespace Vulkan {
@ -40,18 +44,14 @@ void TurboMode::Run(std::stop_token stop_token) {
#ifndef ANDROID
auto& dld = m_device.GetLogical();
// Allocate buffer. 2MiB should be sufficient.
// Allocate buffer using RAII wrapper
const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = 2_MiB,
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
vk::Buffer buffer = m_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
VulkanBuffer buffer = m_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
// Create the descriptor pool to contain our descriptor.
static constexpr VkDescriptorPoolSize pool_size{
@ -59,10 +59,9 @@ void TurboMode::Run(std::stop_token stop_token) {
.descriptorCount = 1,
};
auto descriptor_pool = dld.CreateDescriptorPool(VkDescriptorPoolCreateInfo{
// Create descriptor pool using RAII wrapper
VulkanDescriptorPool descriptor_pool = dld.CreateDescriptorPool(VkDescriptorPoolCreateInfo{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.maxSets = 1,
.poolSizeCount = 1,
.pPoolSizes = &pool_size,
@ -77,7 +76,7 @@ void TurboMode::Run(std::stop_token stop_token) {
.pImmutableSamplers = nullptr,
};
auto descriptor_set_layout = dld.CreateDescriptorSetLayout(VkDescriptorSetLayoutCreateInfo{
VulkanDescriptorSetLayout descriptor_set_layout = dld.CreateDescriptorSetLayout(VkDescriptorSetLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@ -86,7 +85,7 @@ void TurboMode::Run(std::stop_token stop_token) {
});
// Actually create the descriptor set.
auto descriptor_set = descriptor_pool.Allocate(VkDescriptorSetAllocateInfo{
VulkanDescriptorSet descriptor_set = descriptor_pool.AllocateDescriptorSet(VkDescriptorSetAllocateInfo{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.pNext = nullptr,
.descriptorPool = *descriptor_pool,
@ -97,15 +96,11 @@ void TurboMode::Run(std::stop_token stop_token) {
// Create the shader.
auto shader = BuildShader(m_device, VULKAN_TURBO_MODE_COMP_SPV);
// Create the pipeline layout.
auto pipeline_layout = dld.CreatePipelineLayout(VkPipelineLayoutCreateInfo{
// Create pipeline layout using RAII wrapper
VulkanPipelineLayout pipeline_layout = dld.CreatePipelineLayout(VkPipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.setLayoutCount = 1,
.pSetLayouts = descriptor_set_layout.address(),
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
});
// Actually create the pipeline.
@ -119,7 +114,7 @@ void TurboMode::Run(std::stop_token stop_token) {
.pSpecializationInfo = nullptr,
};
auto pipeline = dld.CreateComputePipeline(VkComputePipelineCreateInfo{
VulkanPipeline pipeline = dld.CreateComputePipeline(VkComputePipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@ -130,24 +125,22 @@ void TurboMode::Run(std::stop_token stop_token) {
});
// Create a fence to wait on.
auto fence = dld.CreateFence(VkFenceCreateInfo{
VulkanFence fence = dld.CreateFence(VkFenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
});
// Create a command pool to allocate a command buffer from.
auto command_pool = dld.CreateCommandPool(VkCommandPoolCreateInfo{
// Create command pool using RAII wrapper
VulkanCommandPool command_pool = dld.CreateCommandPool(VkCommandPoolCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr,
.flags =
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = m_device.GetGraphicsFamily(),
});
// Create a single command buffer.
auto cmdbufs = command_pool.Allocate(1, VK_COMMAND_BUFFER_LEVEL_PRIMARY);
auto cmdbuf = vk::CommandBuffer{cmdbufs[0], m_device.GetDispatchLoader()};
// Allocate command buffer using RAII wrapper
VulkanCommandBuffer cmdbuf = command_pool.AllocateCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY);
#endif
while (!stop_token.stop_requested()) {

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <bitset>
@ -18,6 +20,7 @@
#include "video_core/vulkan_common/vma.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vulkan_raii.h"
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
#include <adrenotools/bcenabler.h>
@ -724,11 +727,10 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
dynamic_state3_enables = false;
}
logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions),
VulkanLogicalDevice logical_device(physical, queue_cis, ExtensionListForVulkan(loaded_extensions),
first_next, dld);
graphics_queue = logical.GetQueue(graphics_family);
present_queue = logical.GetQueue(present_family);
graphics_queue = logical_device.Get().GetQueue(graphics_family);
present_queue = logical_device.Get().GetQueue(present_family);
VmaVulkanFunctions functions{};
functions.vkGetInstanceProcAddr = dld.vkGetInstanceProcAddr;
@ -748,11 +750,11 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
.pTypeExternalMemoryHandleTypes = nullptr,
};
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
VulkanMemoryAllocator memory_allocator(allocator_info);
}
Device::~Device() {
vmaDestroyAllocator(allocator);
vmaDestroyAllocator(memory_allocator.Get());
}
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// // SPDX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@ -13,6 +15,7 @@
#include "common/logging/log.h"
#include "common/settings.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vulkan_raii.h"
VK_DEFINE_HANDLE(VmaAllocator)
@ -191,8 +194,76 @@ enum class NvidiaArchitecture {
class Device {
public:
explicit Device(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface,
const vk::InstanceDispatch& dld);
~Device();
const vk::InstanceDispatch& dld)
: instance(instance), physical(physical), dld(dld) {
// Create logical device
VkDeviceCreateInfo device_info = {};
// ... populate device_info ...
if (vkCreateDevice(physical, &device_info, nullptr, &logical) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan logical device");
}
// Create memory allocator
VmaAllocatorCreateInfo allocator_info = {};
allocator_info.physicalDevice = physical;
allocator_info.device = logical;
allocator_info.instance = instance;
if (vmaCreateAllocator(&allocator_info, &allocator) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan memory allocator");
}
}
~Device() {
if (allocator) {
vmaDestroyAllocator(allocator);
}
if (logical) {
vkDestroyDevice(logical, nullptr);
}
}
// Move constructor
Device(Device&& other) noexcept
: instance(other.instance),
allocator(other.allocator),
dld(std::move(other.dld)),
physical(other.physical),
logical(other.logical),
graphics_queue(other.graphics_queue),
present_queue(other.present_queue) {
other.allocator = nullptr;
other.logical = nullptr;
}
// Move assignment operator
Device& operator=(Device&& other) noexcept {
if (this != &other) {
// Clean up existing resources
if (allocator) {
vmaDestroyAllocator(allocator);
}
if (logical) {
vkDestroyDevice(logical, nullptr);
}
// Transfer ownership
instance = other.instance;
allocator = other.allocator;
dld = std::move(other.dld);
physical = other.physical;
logical = other.logical;
graphics_queue = other.graphics_queue;
present_queue = other.present_queue;
other.allocator = nullptr;
other.logical = nullptr;
}
return *this;
}
// Deleted copy constructor and assignment operator
Device(const Device&) = delete;
Device& operator=(const Device&) = delete;
/**
* Returns a format supported by the device for the passed requirements.
@ -744,10 +815,10 @@ private:
private:
VkInstance instance; ///< Vulkan instance.
VmaAllocator allocator; ///< VMA allocator.
VmaAllocator allocator = nullptr; ///< VMA allocator.
vk::DeviceDispatch dld; ///< Device function pointers.
vk::PhysicalDevice physical; ///< Physical device.
vk::Device logical; ///< Logical device.
vk::Device logical = nullptr; ///< Logical device.
vk::Queue graphics_queue; ///< Main graphics queue.
vk::Queue present_queue; ///< Main present queue.
u32 instance_version{}; ///< Vulkan instance version.

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <future>
#include <optional>
@ -111,7 +113,7 @@ void RemoveUnavailableLayers(const vk::InstanceDispatch& dld, std::vector<const
}
} // Anonymous namespace
vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceDispatch& dld,
VulkanInstance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceDispatch& dld,
u32 required_version, Core::Frontend::WindowSystemType window_type,
bool enable_validation) {
if (!library.IsOpen()) {
@ -129,6 +131,10 @@ vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceD
const std::vector<const char*> extensions =
RequiredExtensions(dld, window_type, enable_validation);
if (!AreExtensionsSupported(dld, extensions)) {
LOG_ERROR(Render_Vulkan, "Required extensions are not supported:");
for (const char* extension : extensions) {
LOG_ERROR(Render_Vulkan, " - {}", extension);
}
throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
}
std::vector<const char*> layers = Layers(enable_validation);
@ -149,7 +155,7 @@ vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceD
LOG_ERROR(Render_Vulkan, "Failed to load Vulkan instance function pointers");
throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
}
return instance;
return VulkanInstance(instance, dld);
}
} // namespace Vulkan

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SDPX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <bit>
@ -16,6 +18,7 @@
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vulkan_raii.h"
namespace Vulkan {
namespace {
@ -230,7 +233,11 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
MemoryAllocator::~MemoryAllocator() = default;
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
VulkanImage MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
if (ci.extent.width == 0 || ci.extent.height == 0) {
throw std::invalid_argument("Image extent must have non-zero width and height");
}
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
@ -244,14 +251,16 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
VkImage handle{};
VmaAllocation allocation{};
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
device.GetDispatchLoader());
return VulkanImage(device, handle, allocation, ci.extent);
}
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
VulkanBuffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
if (ci.size == 0) {
throw std::invalid_argument("Buffer size must be greater than 0");
}
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
@ -264,19 +273,10 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa
};
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
u8* data = reinterpret_cast<u8*>(alloc_info.pMappedData);
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
device.GetDispatchLoader());
return VulkanBuffer(device, handle, allocation, ci.size);
}
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SDPX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@ -9,6 +11,7 @@
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vulkan_raii.h"
VK_DEFINE_HANDLE(VmaAllocator)
@ -91,7 +94,8 @@ public:
*
* @throw vk::Exception on failure
*/
explicit MemoryAllocator(const Device& device_);
explicit MemoryAllocator(const Device& device_)
: device(device_), allocator(device_.GetMemoryAllocator()) {}
~MemoryAllocator();
MemoryAllocator& operator=(const MemoryAllocator&) = delete;
@ -99,8 +103,54 @@ public:
vk::Image CreateImage(const VkImageCreateInfo& ci) const;
VulkanImage CreateImage(const VkImageCreateInfo& ci) const {
if (ci.extent.width == 0 || ci.extent.height == 0) {
throw std::invalid_argument("Image extent must have non-zero width and height");
}
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.memoryTypeBits = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkImage handle{};
VmaAllocation allocation{};
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
return VulkanImage(device, handle, allocation, ci.extent);
}
vk::Buffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const;
VulkanBuffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
if (ci.size == 0) {
throw std::invalid_argument("Buffer size must be greater than 0");
}
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage),
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkBuffer handle{};
VmaAllocation allocation{};
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
return VulkanBuffer(device, handle, allocation, ci.size);
}
/**
* Commits a memory with the specified requirements.
*

View file

@ -0,0 +1,342 @@
#pragma once
#include <vulkan/vulkan.h>
#include <stdexcept>
#include <cstdint>
#include <vector>
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan.h"
#include "video_core/vulkan_common/vma.h"
// VulkanBuffer is an RAII wrapper for a Vulkan buffer
class VulkanBuffer {
public:
VulkanBuffer(const Device& device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
: device_(device.GetLogical()) {
if (size == 0) {
throw std::invalid_argument("Buffer size must be greater than 0");
}
// Create Vulkan buffer
VkBufferCreateInfo buffer_info{};
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_info.size = size;
buffer_info.usage = usage;
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(device_, &buffer_info, nullptr, &buffer_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan buffer");
}
// Allocate memory for the buffer
VkMemoryRequirements mem_requirements;
vkGetBufferMemoryRequirements(device_, buffer_, &mem_requirements);
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_requirements.size;
alloc_info.memoryTypeIndex = device.FindMemoryType(mem_requirements.memoryTypeBits, properties);
if (vkAllocateMemory(device_, &alloc_info, nullptr, &buffer_memory_) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate Vulkan buffer memory");
}
if (vkBindBufferMemory(device_, buffer_, buffer_memory_, 0) != VK_SUCCESS) {
throw std::runtime_error("Failed to bind Vulkan buffer memory");
}
}
~VulkanBuffer() {
if (buffer_) {
vkDestroyBuffer(device_, buffer_, nullptr);
}
if (buffer_memory_) {
vkFreeMemory(device_, buffer_memory_, nullptr);
}
}
VulkanBuffer(const VulkanBuffer&) = delete; // Copy semantics are disabled
VulkanBuffer& operator=(const VulkanBuffer&) = delete;
// Move Constructor
VulkanBuffer(VulkanBuffer&& other) noexcept
: device_(other.device_), buffer_(other.buffer_), buffer_memory_(other.buffer_memory_) {
other.buffer_ = VK_NULL_HANDLE;
other.buffer_memory_ = VK_NULL_HANDLE;
}
// Move Assignment Operator
VulkanBuffer& operator=(VulkanBuffer&& other) noexcept {
if (this != &other) { // Check for self-assignment
// Clean up existing resources
if (buffer_) {
vkDestroyBuffer(device_, buffer_, nullptr);
}
if (buffer_memory_) {
vkFreeMemory(device_, buffer_memory_, nullptr);
}
// Move resources
device_ = other.device_;
buffer_ = other.buffer_;
buffer_memory_ = other.buffer_memory_;
other.buffer_ = VK_NULL_HANDLE;
other.buffer_memory_ = VK_NULL_HANDLE;
}
return *this;
}
VkBuffer Get() const { return buffer_; }
private:
VkDevice device_;
VkBuffer buffer_ = VK_NULL_HANDLE;
VkDeviceMemory buffer_memory_ = VK_NULL_HANDLE;
};
class VulkanImage {
public:
VulkanImage(const Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage)
: device_(device.GetLogical()) {
// Create Vulkan image
VkImageCreateInfo image_info{};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.flags = 0; // Default to no flags
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.extent = {extent.width, extent.height, 1};
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.format = format;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = usage;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.queueFamilyIndexCount = 0;
image_info.pQueueFamilyIndices = nullptr;
if (vkCreateImage(device_, &image_info, nullptr, &image_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan image");
}
// Allocate memory for the image
VkMemoryRequirements mem_requirements;
vkGetImageMemoryRequirements(device_, image_, &mem_requirements);
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = mem_requirements.size;
alloc_info.memoryTypeIndex = device.FindMemoryType(mem_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(device_, &alloc_info, nullptr, &image_memory_) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate Vulkan image memory");
}
if (vkBindImageMemory(device_, image_, image_memory_, 0) != VK_SUCCESS) {
throw std::runtime_error("Failed to bind Vulkan image memory");
}
}
~VulkanImage() {
if (image_) {
vkDestroyImage(device_, image_, nullptr);
}
if (image_memory_) {
vkFreeMemory(device_, image_memory_, nullptr);
}
}
VkImage Get() const { return image_; }
private:
VkDevice device_;
VkImage image_ = VK_NULL_HANDLE;
VkDeviceMemory image_memory_ = VK_NULL_HANDLE;
};
typedef struct VkBufferCreateInfo {
VkStructureType sType;
const void* pNext;
VkBufferCreateFlags flags;
VkDeviceSize size;
VkBufferUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
} VkBufferCreateInfo;
typedef struct VkMemoryRequirements {
VkDeviceSize size;
VkDeviceSize alignment;
uint32_t memoryTypeBits;
} VkMemoryRequirements;
typedef struct VkMemoryAllocateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeIndex;
} VkMemoryAllocateInfo;
typedef struct VkImageCreateInfo {
VkStructureType sType;
const void* pNext;
VkImageCreateFlags flags;
VkImageType imageType;
VkFormat format;
VkExtent3D extent;
uint32_t mipLevels;
uint32_t arrayLayers;
VkSampleCountFlagBits samples;
VkImageTiling tiling;
VkImageUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
VkImageLayout initialLayout;
} VkImageCreateInfo;
VkResult vkCreateBuffer(
VkDevice device,
const VkBufferCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkBuffer* pBuffer
);
void vkGetBufferMemoryRequirements(
VkDevice device,
VkBuffer buffer,
VkMemoryRequirements* pMemoryRequirements
);
VkResult vkAllocateMemory(
VkDevice device,
const VkMemoryAllocateInfo* pAllocateInfo,
const VkAllocationCallbacks* pAllocator,
VkDeviceMemory* pMemory
);
VkResult vkBindBufferMemory(
VkDevice device,
VkBuffer buffer,
VkDeviceMemory memory,
VkDeviceSize memoryOffset
);
void vkDestroyBuffer(
VkDevice device,
VkBuffer buffer,
const VkAllocationCallbacks* pAllocator
);
void vkFreeMemory(
VkDevice device,
VkDeviceMemory memory,
const VkAllocationCallbacks* pAllocator
);
VkResult vkCreateImage(
VkDevice device,
const VkImageCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkImage* pImage
);
void vkDestroyImage(
VkDevice device,
VkImage image,
const VkAllocationCallbacks* pAllocator
);
void vkGetImageMemoryRequirements(
VkDevice device,
VkImage image,
VkMemoryRequirements* pMemoryRequirements
);
VkResult vkBindImageMemory(
VkDevice device,
VkImage image,
VkDeviceMemory memory,
VkDeviceSize memoryOffset
);
class Device {
public:
VkDevice GetLogical() const {
return logical_device_;
}
uint32_t FindMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties) const {
for (uint32_t i = 0; i < memory_properties_.memoryTypeCount; i++) {
if ((type_filter & (1 << i)) &&
(memory_properties_.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
throw std::runtime_error("Failed to find suitable memory type");
}
private:
VkDevice logical_device_;
VkPhysicalDeviceMemoryProperties memory_properties_;
};
VkDevice Device::GetLogical() const {
return logical_device_;
}
uint32_t Device::FindMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties) const {
for (uint32_t i = 0; i < memory_properties_.memoryTypeCount; i++) {
if ((type_filter & (1 << i)) &&
(memory_properties_.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
throw std::runtime_error("Failed to find suitable memory type");
}
class VulkanMemoryAllocator {
public:
VulkanMemoryAllocator(const VmaAllocatorCreateInfo& allocator_info) {
if (vmaCreateAllocator(&allocator_info, &allocator_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan Memory Allocator");
}
}
~VulkanMemoryAllocator() {
if (allocator_) {
vmaDestroyAllocator(allocator_);
}
}
// Disable copy semantics
VulkanMemoryAllocator(const VulkanMemoryAllocator&) = delete;
VulkanMemoryAllocator& operator=(const VulkanMemoryAllocator&) = delete;
// Enable move semantics
VulkanMemoryAllocator(VulkanMemoryAllocator&& other) noexcept
: allocator_(other.allocator_) {
other.allocator_ = nullptr;
}
VulkanMemoryAllocator& operator=(VulkanMemoryAllocator&& other) noexcept {
if (this != &other) {
if (allocator_) {
vmaDestroyAllocator(allocator_);
}
allocator_ = other.allocator_;
other.allocator_ = nullptr;
}
return *this;
}
VmaAllocator Get() const {
return allocator_;
}
private:
VmaAllocator allocator_ = nullptr;
};

View file

@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright 2025 EDEN Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <memory>
@ -12,6 +14,8 @@
#include "video_core/vulkan_common/vk_enum_string_helper.h"
#include "video_core/vulkan_common/vma.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vulkan_raii.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan::vk {
@ -1000,3 +1004,11 @@ std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
}
} // namespace Vulkan::vk
VulkanBuffer buffer(device, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
VulkanImage image(device, {width, height}, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
void CreateBuffer(const Device& device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) {
VulkanBuffer buffer(device, size, usage, properties);
// Use the buffer as needed
}