diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index d58c3c94..57862a04 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -23,6 +23,13 @@ jobs: - name: Fetch submodules run: git submodule update --init --recursive + - name: Setup Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: latest + vulkan-use-cache: true + vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type diff --git a/.github/workflows/MacOS_Build.yml b/.github/workflows/MacOS_Build.yml index d3443faf..7e54c5a5 100644 --- a/.github/workflows/MacOS_Build.yml +++ b/.github/workflows/MacOS_Build.yml @@ -23,6 +23,13 @@ jobs: - name: Fetch submodules run: git submodule update --init --recursive + - name: Setup Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: latest + vulkan-use-cache: true + vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type diff --git a/.github/workflows/Windows_Build.yml b/.github/workflows/Windows_Build.yml index 2e8a8562..653692f3 100644 --- a/.github/workflows/Windows_Build.yml +++ b/.github/workflows/Windows_Build.yml @@ -23,6 +23,13 @@ jobs: - name: Fetch submodules run: git submodule update --init --recursive + - name: Setup Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: latest + vulkan-use-cache: true + vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type diff --git a/CMakeLists.txt b/CMakeLists.txt index c84bf0c9..a174a1ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ endif() option(DISABLE_PANIC_DEV "Make a build with fewer and less intrusive asserts" OFF) option(GPU_DEBUG_INFO "Enable additional GPU debugging info" OFF) option(ENABLE_OPENGL "Enable OpenGL rendering backend" ON) +option(ENABLE_VULKAN "Enable Vulkan rendering backend" ON) option(ENABLE_LTO "Enable link-time optimization" OFF) option(ENABLE_USER_BUILD "Make a user-facing build. These builds have various assertions disabled, LTO, and more" OFF) option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF) @@ -177,6 +178,7 @@ source_group("Source Files\\Core\\Software Renderer" FILES ${RENDERER_SW_SOURCE_ source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES}) set(RENDERER_GL_SOURCE_FILES "") # Empty by default unless we are compiling with the GL renderer +set(RENDERER_VK_SOURCE_FILES "") # Empty by default unless we are compiling with the VK renderer if(ENABLE_OPENGL) set(RENDERER_GL_INCLUDE_FILES include/renderer_gl/opengl.hpp @@ -206,6 +208,29 @@ if(ENABLE_OPENGL) ) endif() +if(ENABLE_VULKAN) + find_package( + Vulkan 1.3.206 REQUIRED + COMPONENTS glslangValidator + ) + set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp + include/renderer_vk/vulkan_api.hpp include/renderer_vk/vk_debug.hpp + ) + + set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp + src/core/renderer_vk/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp + ) + + set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES}) + source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES}) + + cmrc_add_resource_library( + resources_renderer_vk + NAMESPACE RendererVK + WHENCE "src/host_shaders/" + ) +endif() + source_group("Header Files\\Core" FILES ${HEADER_FILES}) set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES} ${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES}) @@ -215,6 +240,11 @@ if(ENABLE_OPENGL) set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_GL_SOURCE_FILES}) endif() +if(ENABLE_VULKAN) + # Add the Vulkan source files to ALL_SOURCES + set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_VK_SOURCE_FILES}) +endif() + add_executable(Alber ${ALL_SOURCES}) if(ENABLE_LTO OR ENABLE_USER_BUILD) @@ -228,6 +258,11 @@ if(ENABLE_OPENGL) target_link_libraries(Alber PRIVATE resources_renderer_gl) endif() +if(ENABLE_VULKAN) + target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_VULKAN=1") + target_link_libraries(Alber PRIVATE Vulkan::Vulkan resources_renderer_vk) +endif() + if(GPU_DEBUG_INFO) target_compile_definitions(Alber PRIVATE GPU_DEBUG_INFO=1) endif() diff --git a/include/PICA/gpu.hpp b/include/PICA/gpu.hpp index 4304a2de..b4236ee0 100644 --- a/include/PICA/gpu.hpp +++ b/include/PICA/gpu.hpp @@ -83,7 +83,7 @@ class GPU { bool lightingLUTDirty = false; GPU(Memory& mem, EmulatorConfig& config); - void initGraphicsContext() { renderer->initGraphicsContext(); } + void initGraphicsContext(SDL_Window* window) { renderer->initGraphicsContext(window); } void display() { renderer->display(); } void screenshot(const std::string& name) { renderer->screenshot(name); } @@ -103,9 +103,7 @@ class GPU { // TODO: Emulate the transfer engine & its registers // Then this can be emulated by just writing the appropriate values there - void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { - renderer->clearBuffer(startAddress, endAddress, value, control); - } + void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { renderer->clearBuffer(startAddress, endAddress, value, control); } // TODO: Emulate the transfer engine & its registers // Then this can be emulated by just writing the appropriate values there diff --git a/include/renderer.hpp b/include/renderer.hpp index 970b8933..cd1ee53b 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -16,6 +16,7 @@ enum class RendererType : s8 { }; class GPU; +struct SDL_Window; class Renderer { protected: @@ -42,7 +43,7 @@ class Renderer { virtual void reset() = 0; virtual void display() = 0; // Display the 3DS screen contents to the window - virtual void initGraphicsContext() = 0; // Initialize graphics context + virtual void initGraphicsContext(SDL_Window* window) = 0; // Initialize graphics context virtual void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) = 0; // Clear a GPU buffer in VRAM virtual void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) = 0; // Perform display transfer virtual void drawVertices(PICA::PrimType primType, std::span vertices) = 0; // Draw the given vertices diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 15d12ade..3c729d76 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -72,7 +72,7 @@ class RendererGL final : public Renderer { void reset() override; void display() override; // Display the 3DS screen contents to the window - void initGraphicsContext() override; // Initialize graphics context + void initGraphicsContext(SDL_Window* window) override; // Initialize graphics context void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; // Clear a GPU buffer in VRAM void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; // Perform display transfer void drawVertices(PICA::PrimType primType, std::span vertices) override; // Draw the given vertices diff --git a/include/renderer_null/renderer_null.hpp b/include/renderer_null/renderer_null.hpp index 29080786..553af035 100644 --- a/include/renderer_null/renderer_null.hpp +++ b/include/renderer_null/renderer_null.hpp @@ -9,7 +9,7 @@ class RendererNull final : public Renderer { void reset() override; void display() override; - void initGraphicsContext() override; + void initGraphicsContext(SDL_Window* window) override; void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; void drawVertices(PICA::PrimType primType, std::span vertices) override; diff --git a/include/renderer_sw/renderer_sw.hpp b/include/renderer_sw/renderer_sw.hpp index f638a51e..5c42e188 100644 --- a/include/renderer_sw/renderer_sw.hpp +++ b/include/renderer_sw/renderer_sw.hpp @@ -9,7 +9,7 @@ class RendererSw final : public Renderer { void reset() override; void display() override; - void initGraphicsContext() override; + void initGraphicsContext(SDL_Window* window) override; void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; void drawVertices(PICA::PrimType primType, std::span vertices) override; diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp new file mode 100644 index 00000000..fb17916f --- /dev/null +++ b/include/renderer_vk/renderer_vk.hpp @@ -0,0 +1,55 @@ +#include "renderer.hpp" +#include "vulkan_api.hpp" + +class GPU; + +class RendererVK final : public Renderer { + // The order of these `Unique*` members is important, they will be destroyed in RAII order + vk::UniqueInstance instance = {}; + vk::UniqueDebugUtilsMessengerEXT debugMessenger = {}; + + vk::UniqueSurfaceKHR surface = {}; + + vk::PhysicalDevice physicalDevice = {}; + + vk::Queue presentQueue = {}; + u32 presentQueueFamily = ~0u; + vk::Queue graphicsQueue = {}; + u32 graphicsQueueFamily = ~0u; + vk::Queue computeQueue = {}; + u32 computeQueueFamily = ~0u; + vk::Queue transferQueue = {}; + u32 transferQueueFamily = ~0u; + + vk::UniqueDevice device = {}; + + vk::UniqueSwapchainKHR swapchain = {}; + u32 swapchainImageCount = ~0u; + std::vector swapchainImages = {}; + std::vector swapchainImageViews = {}; + + + // Global synchronization primitives + + vk::UniqueCommandPool commandPool = {}; + + // Per-swapchain-image data + // Each vector is `swapchainImageCount` in size + std::vector presentCommandBuffers = {}; + std::vector swapImageFreeSemaphore = {}; + std::vector renderFinishedSemaphore = {}; + std::vector frameFinishedFences = {}; + + u64 currentFrame = 0; + public: + RendererVK(GPU& gpu, const std::array& internalRegs); + ~RendererVK() override; + + void reset() override; + void display() override; + void initGraphicsContext(SDL_Window* window) override; + void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; + void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; + void drawVertices(PICA::PrimType primType, std::span vertices) override; + void screenshot(const std::string& name) override; +}; \ No newline at end of file diff --git a/include/renderer_vk/vk_debug.hpp b/include/renderer_vk/vk_debug.hpp new file mode 100644 index 00000000..afc367dc --- /dev/null +++ b/include/renderer_vk/vk_debug.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +#include "vulkan_api.hpp" + +namespace Vulkan { + + VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData + ); + + void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...); + + template ::value == true>, typename... ArgsT> + inline void setObjectName(vk::Device device, const T objectHandle, const char* format, ArgsT&&... args) { + setObjectName(device, T::objectType, objectHandle, format, std::forward(args)...); + } + + void beginDebugLabel(vk::CommandBuffer commandBuffer, std::span color, const char* format, ...); + + void insertDebugLabel(vk::CommandBuffer commandBuffer, std::span color, const char* format, ...); + + void endDebugLabel(vk::CommandBuffer commandBuffer); + + class DebugLabelScope { + private: + const vk::CommandBuffer commandBuffer; + + public: + template + DebugLabelScope(vk::CommandBuffer targetCommandBuffer, std::span color, const char* format, ArgsT&&... args) + : commandBuffer(targetCommandBuffer) { + beginDebugLabel(commandBuffer, color, format, std::forward(args)...); + } + + template + void operator()(std::span color, const char* format, ArgsT&&... args) const { + insertDebugLabel(commandBuffer, color, format, std::forward(args)...); + } + + ~DebugLabelScope() { endDebugLabel(commandBuffer); } + }; + +} // namespace Vulkan \ No newline at end of file diff --git a/include/renderer_vk/vulkan_api.hpp b/include/renderer_vk/vulkan_api.hpp new file mode 100644 index 00000000..e411220c --- /dev/null +++ b/include/renderer_vk/vulkan_api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#define VK_NO_PROTOTYPES +#include + +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#define VULKAN_HPP_NO_EXCEPTIONS +// Disable asserts on result-codes +#define VULKAN_HPP_ASSERT_ON_RESULT +#include +#include +#include \ No newline at end of file diff --git a/src/core/PICA/gpu.cpp b/src/core/PICA/gpu.cpp index d7c37925..20fe4946 100644 --- a/src/core/PICA/gpu.cpp +++ b/src/core/PICA/gpu.cpp @@ -12,6 +12,9 @@ #ifdef PANDA3DS_ENABLE_OPENGL #include "renderer_gl/renderer_gl.hpp" #endif +#ifdef PANDA3DS_ENABLE_VULKAN +#include "renderer_vk/renderer_vk.hpp" +#endif using namespace Floats; @@ -38,11 +41,12 @@ GPU::GPU(Memory& mem, EmulatorConfig& config) : mem(mem), config(config) { break; } #endif - +#ifdef PANDA3DS_ENABLE_VULKAN case RendererType::Vulkan: { - Helpers::panic("Vulkan is not supported yet, please pick another renderer"); + renderer.reset(new RendererVK(*this, regs)); + break; } - +#endif default: { Helpers::panic("Rendering backend not supported: %s", Renderer::typeToString(config.rendererType)); break; diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index bef3fe93..0126b3b8 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -45,7 +45,7 @@ void RendererGL::reset() { } } -void RendererGL::initGraphicsContext() { +void RendererGL::initGraphicsContext(SDL_Window* window) { gl.reset(); auto gl_resources = cmrc::RendererGL::get_filesystem(); diff --git a/src/core/renderer_null/renderer_null.cpp b/src/core/renderer_null/renderer_null.cpp index 9df2ddeb..272ce4e3 100644 --- a/src/core/renderer_null/renderer_null.cpp +++ b/src/core/renderer_null/renderer_null.cpp @@ -5,7 +5,7 @@ RendererNull::~RendererNull() {} void RendererNull::reset() {} void RendererNull::display() {} -void RendererNull::initGraphicsContext() {} +void RendererNull::initGraphicsContext(SDL_Window* window) {} void RendererNull::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} void RendererNull::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {} void RendererNull::drawVertices(PICA::PrimType primType, std::span vertices) {} diff --git a/src/core/renderer_sw/renderer_sw.cpp b/src/core/renderer_sw/renderer_sw.cpp index 77689699..9c15d6f8 100644 --- a/src/core/renderer_sw/renderer_sw.cpp +++ b/src/core/renderer_sw/renderer_sw.cpp @@ -6,7 +6,7 @@ RendererSw::~RendererSw() {} void RendererSw::reset() { printf("RendererSW: Unimplemented reset call\n"); } void RendererSw::display() { printf("RendererSW: Unimplemented display call\n"); } -void RendererSw::initGraphicsContext() { printf("RendererSW: Unimplemented initGraphicsContext call\n"); } +void RendererSw::initGraphicsContext(SDL_Window* window) { printf("RendererSW: Unimplemented initGraphicsContext call\n"); } void RendererSw::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { printf("RendererSW: Unimplemented clearBuffer call\n"); } void RendererSw::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) { diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp new file mode 100644 index 00000000..fc6553b2 --- /dev/null +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -0,0 +1,519 @@ +#include "renderer_vk/renderer_vk.hpp" + +#include +#include +#include + +#include "SDL_vulkan.h" +#include "helpers.hpp" +#include "renderer_vk/vk_debug.hpp" + +// Finds the first queue family that satisfies `queueMask` and excludes `queueExcludeMask` bits +// Returns -1 if not found +// Todo: Smarter selection for present/graphics/compute/transfer +static s32 findQueueFamily( + std::span queueFamilies, vk::QueueFlags queueMask, + vk::QueueFlags queueExcludeMask = vk::QueueFlagBits::eProtected +) { + for (usize i = 0; i < queueFamilies.size(); ++i) { + if (((queueFamilies[i].queueFlags & queueMask) == queueMask) && !(queueFamilies[i].queueFlags & queueExcludeMask)) { + return i; + } + } + return -1; +} + +RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} + +RendererVK::~RendererVK() {} + +void RendererVK::reset() {} + +void RendererVK::display() { + // Block, on the CPU, to ensure that this swapchain-frame is ready for more work + if (auto waitResult = device->waitForFences({frameFinishedFences[currentFrame].get()}, true, std::numeric_limits::max()); waitResult != vk::Result::eSuccess) { + Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str()); + } + + u32 swapchainImageIndex = std::numeric_limits::max(); + if (const auto acquireResult = + device->acquireNextImageKHR(swapchain.get(), std::numeric_limits::max(), swapImageFreeSemaphore[currentFrame].get(), {}); + acquireResult.result == vk::Result::eSuccess) { + swapchainImageIndex = acquireResult.value; + } else { + switch (acquireResult.result) { + case vk::Result::eSuboptimalKHR: + case vk::Result::eErrorOutOfDateKHR: { + // Surface resized + // Todo: Recreate swapchain and get a valid image index + break; + } + default: { + Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str()); + } + } + } + + vk::UniqueCommandBuffer& presentCommandBuffer = presentCommandBuffers.at(currentFrame); + + vk::CommandBufferBeginInfo beginInfo = {}; + beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse; + + if (const vk::Result beginResult = presentCommandBuffer->begin(beginInfo); beginResult != vk::Result::eSuccess) { + Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str()); + } + + { + static const std::array presentScopeColor = {{1.0f, currentFrame / 2.0f, 1.0f, 1.0f}}; + + Vulkan::DebugLabelScope debugScope(presentCommandBuffer.get(), presentScopeColor, "Present"); + + // Prepare for color-clear + presentCommandBuffer->pipelineBarrier( + vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {}, + {vk::ImageMemoryBarrier( + vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined, + vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + )} + ); + + presentCommandBuffer->clearColorImage( + swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, presentScopeColor, + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + ); + + // Prepare for present + presentCommandBuffer->pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {}, + {vk::ImageMemoryBarrier( + vk::AccessFlagBits::eNone, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal, + vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex], + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) + )} + ); + } + + if (const vk::Result endResult = presentCommandBuffer->end(); endResult != vk::Result::eSuccess) { + Helpers::panic("Error ending command buffer recording: %s\n", vk::to_string(endResult).c_str()); + } + + vk::SubmitInfo submitInfo = {}; + // Wait for any previous uses of the image image to finish presenting + submitInfo.setWaitSemaphores(swapImageFreeSemaphore[currentFrame].get()); + // Signal when finished + submitInfo.setSignalSemaphores(renderFinishedSemaphore[currentFrame].get()); + + static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + submitInfo.setWaitDstStageMask(waitStageMask); + + submitInfo.setCommandBuffers(presentCommandBuffer.get()); + + device->resetFences({frameFinishedFences[currentFrame].get()}); + + if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[currentFrame].get()); submitResult != vk::Result::eSuccess) { + Helpers::panic("Error submitting to graphics queue: %s\n", vk::to_string(submitResult).c_str()); + } + + vk::PresentInfoKHR presentInfo = {}; + presentInfo.setWaitSemaphores(renderFinishedSemaphore[currentFrame].get()); + presentInfo.setSwapchains(swapchain.get()); + presentInfo.setImageIndices(swapchainImageIndex); + + if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) { + } else { + switch (presentResult) { + case vk::Result::eSuboptimalKHR: + case vk::Result::eErrorOutOfDateKHR: { + // Surface resized + // Todo: Recreate swapchain and get a valid image index + break; + } + default: { + Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str()); + } + } + } + + currentFrame = ((currentFrame + 1) % swapchainImageCount); +} + +void RendererVK::initGraphicsContext(SDL_Window* window) { + // Resolve all instance function pointers + static vk::DynamicLoader dl; + VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); + + // Create Instance + vk::ApplicationInfo applicationInfo = {}; + applicationInfo.apiVersion = VK_API_VERSION_1_1; + + applicationInfo.pEngineName = "Alber"; + applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + + applicationInfo.pApplicationName = "Alber"; + applicationInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + + vk::InstanceCreateInfo instanceInfo = {}; + + instanceInfo.pApplicationInfo = &applicationInfo; + + std::vector instanceExtensions = { +#if defined(__APPLE__) + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, +#endif + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + }; + + // Get any additional extensions that SDL wants as well + { + unsigned int extensionCount = 0; + SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr); + std::vector sdlInstanceExtensions(extensionCount); + SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, sdlInstanceExtensions.data()); + + instanceExtensions.insert(instanceExtensions.end(), sdlInstanceExtensions.begin(), sdlInstanceExtensions.end()); + } + +#if defined(__APPLE__) + instanceInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; +#endif + + instanceInfo.ppEnabledExtensionNames = instanceExtensions.data(); + instanceInfo.enabledExtensionCount = instanceExtensions.size(); + + if (auto createResult = vk::createInstanceUnique(instanceInfo); createResult.result == vk::Result::eSuccess) { + instance = std::move(createResult.value); + } else { + Helpers::panic("Error creating Vulkan instance: %s\n", vk::to_string(createResult.result).c_str()); + } + // Initialize instance-specific function pointers + VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); + + // Enable debug messenger if the instance was able to be created with debug_utils + if (std::find( + instanceExtensions.begin(), instanceExtensions.end(), + // std::string_view has a way to compare itself to `const char*` + // so by casting it, we get the actual string comparisons + // and not pointer-comparisons + std::string_view(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) + ) != instanceExtensions.end()) { + vk::DebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + debugCreateInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning; + debugCreateInfo.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral; + debugCreateInfo.pfnUserCallback = &Vulkan::debugMessageCallback; + if (auto createResult = instance->createDebugUtilsMessengerEXTUnique(debugCreateInfo); createResult.result == vk::Result::eSuccess) { + debugMessenger = std::move(createResult.value); + } else { + Helpers::warn("Error registering debug messenger: %s", vk::to_string(createResult.result).c_str()); + } + } + + // Create surface + if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { + surface.reset(newSurface); + } else { + Helpers::warn("Error creating Vulkan surface"); + } + + // Pick physical device + if (auto enumerateResult = instance->enumeratePhysicalDevices(); enumerateResult.result == vk::Result::eSuccess) { + std::vector physicalDevices = std::move(enumerateResult.value); + std::vector::iterator partitionEnd = physicalDevices.end(); + + // Prefer GPUs that can access the surface + const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool { + const usize queueCount = physicalDevice.getQueueFamilyProperties().size(); + for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) { + if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, surface.get()); + supportResult.result == vk::Result::eSuccess) { + return supportResult.value; + } + } + return false; + }; + + partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport); + + // Prefer Discrete GPUs + const auto isDiscrete = [](const vk::PhysicalDevice& physicalDevice) -> bool { + return physicalDevice.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu; + }; + partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, isDiscrete); + + // Pick the "best" out of all of the previous criteria, preserving the order that the + // driver gave us the devices in(ex: optimus configuration) + physicalDevice = physicalDevices.front(); + } else { + Helpers::panic("Error enumerating physical devices: %s\n", vk::to_string(enumerateResult.result).c_str()); + } + + // Get device queues + + std::vector deviceQueueInfos; + { + const std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + // Get present queue family + for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) { + if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface.get()); + supportResult.result == vk::Result::eSuccess) { + if (supportResult.value) { + presentQueueFamily = queueFamilyIndex; + break; + } + } + } + + static const float queuePriority = 1.0f; + + graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics); + computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute); + transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer); + + // Requests a singular queue for each unique queue-family + const std::unordered_set queueFamilyRequests = {presentQueueFamily, graphicsQueueFamily, computeQueueFamily, transferQueueFamily}; + for (const u32 queueFamilyIndex : queueFamilyRequests) { + deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, queueFamilyIndex, 1, &queuePriority)); + } + } + + // Create Device + vk::DeviceCreateInfo deviceInfo = {}; + + static const char* deviceExtensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, +#if defined(__APPLE__) + "VK_KHR_portability_subset", +#endif + // VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME + }; + deviceInfo.ppEnabledExtensionNames = deviceExtensions; + deviceInfo.enabledExtensionCount = std::size(deviceExtensions); + + vk::StructureChain deviceFeatureChain = {}; + + auto& deviceFeatures = deviceFeatureChain.get().features; + + auto& deviceTimelineFeatures = deviceFeatureChain.get(); + // deviceTimelineFeatures.timelineSemaphore = true; + + deviceInfo.pNext = &deviceFeatureChain.get(); + + deviceInfo.setQueueCreateInfos(deviceQueueInfos); + + if (auto createResult = physicalDevice.createDeviceUnique(deviceInfo); createResult.result == vk::Result::eSuccess) { + device = std::move(createResult.value); + } else { + Helpers::panic("Error creating logical device: %s\n", vk::to_string(createResult.result).c_str()); + } + + // Initialize device-specific function pointers + VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); + + presentQueue = device->getQueue(presentQueueFamily, 0); + graphicsQueue = device->getQueue(presentQueueFamily, 0); + computeQueue = device->getQueue(computeQueueFamily, 0); + transferQueue = device->getQueue(transferQueueFamily, 0); + + // Create swapchain + static constexpr u32 screenTextureWidth = 400; // Top screen is 400 pixels wide, bottom is 320 + static constexpr u32 screenTextureHeight = 2 * 240; // Both screens are 240 pixels tall + static constexpr vk::ImageUsageFlags swapchainUsageFlagsRequired = + (vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst); + + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + + // Extent + Image count + Usage + Surface Transform + vk::ImageUsageFlags swapchainImageUsage; + vk::SurfaceTransformFlagBitsKHR swapchainSurfaceTransform; + if (const auto getResult = physicalDevice.getSurfaceCapabilitiesKHR(surface.get()); getResult.result == vk::Result::eSuccess) { + const vk::SurfaceCapabilitiesKHR& surfaceCapabilities = getResult.value; + + // In the case if width == height == -1, we define the extent ourselves but must fit within the limits + if (surfaceCapabilities.currentExtent.width == -1 || surfaceCapabilities.currentExtent.height == -1) { + swapchainExtent.width = std::max(swapchainExtent.width, surfaceCapabilities.minImageExtent.width); + swapchainExtent.height = std::max(swapchainExtent.height, surfaceCapabilities.minImageExtent.height); + swapchainExtent.width = std::min(swapchainExtent.width, surfaceCapabilities.maxImageExtent.width); + swapchainExtent.height = std::min(swapchainExtent.height, surfaceCapabilities.maxImageExtent.height); + } + + swapchainImageCount = surfaceCapabilities.minImageCount + 1; + if ((surfaceCapabilities.maxImageCount > 0) && (swapchainImageCount > surfaceCapabilities.maxImageCount)) { + swapchainImageCount = surfaceCapabilities.maxImageCount; + } + + swapchainImageUsage = surfaceCapabilities.supportedUsageFlags & swapchainUsageFlagsRequired; + + if ((swapchainImageUsage & swapchainUsageFlagsRequired) != swapchainUsageFlagsRequired) { + Helpers::panic( + "Unsupported swapchain image usage. Could not acquire %s\n", vk::to_string(swapchainImageUsage ^ swapchainUsageFlagsRequired).c_str() + ); + } + + if (surfaceCapabilities.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) { + swapchainSurfaceTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; + } else { + swapchainSurfaceTransform = surfaceCapabilities.currentTransform; + } + } else { + Helpers::panic("Error getting surface capabilities: %s\n", vk::to_string(getResult.result).c_str()); + } + + // Preset Mode + // Fifo support is required by all vulkan implementations, waits for vsync + vk::PresentModeKHR swapchainPresentMode = vk::PresentModeKHR::eFifo; + if (auto getResult = physicalDevice.getSurfacePresentModesKHR(surface.get()); getResult.result == vk::Result::eSuccess) { + std::vector& presentModes = getResult.value; + + // Use mailbox if available, lowest-latency vsync-enabled mode + if (std::find(presentModes.begin(), presentModes.end(), vk::PresentModeKHR::eMailbox) != presentModes.end()) { + swapchainPresentMode = vk::PresentModeKHR::eMailbox; + } + } else { + Helpers::panic("Error enumerating surface present modes: %s\n", vk::to_string(getResult.result).c_str()); + } + + // Surface format + vk::SurfaceFormatKHR swapchainSurfaceFormat; + if (auto getResult = physicalDevice.getSurfaceFormatsKHR(surface.get()); getResult.result == vk::Result::eSuccess) { + std::vector& surfaceFormats = getResult.value; + + // A singular undefined surface format means we can use any format we want + if ((surfaceFormats.size() == 1) && surfaceFormats[0].format == vk::Format::eUndefined) { + // Assume R8G8B8A8-SRGB by default + swapchainSurfaceFormat = {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear}; + } else { + // Find the next-best R8G8B8A8-SRGB format + std::vector::iterator partitionEnd = surfaceFormats.end(); + + const auto preferR8G8B8A8 = [](const vk::SurfaceFormatKHR& surfaceFormat) -> bool { + return surfaceFormat.format == vk::Format::eR8G8B8A8Snorm; + }; + partitionEnd = std::stable_partition(surfaceFormats.begin(), partitionEnd, preferR8G8B8A8); + + const auto preferSrgbNonLinear = [](const vk::SurfaceFormatKHR& surfaceFormat) -> bool { + return surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }; + partitionEnd = std::stable_partition(surfaceFormats.begin(), partitionEnd, preferSrgbNonLinear); + + swapchainSurfaceFormat = surfaceFormats.front(); + } + + } else { + Helpers::panic("Error enumerating surface formats: %s\n", vk::to_string(getResult.result).c_str()); + } + + vk::SwapchainCreateInfoKHR swapchainInfo = {}; + + swapchainInfo.surface = surface.get(); + swapchainInfo.minImageCount = swapchainImageCount; + swapchainInfo.imageFormat = swapchainSurfaceFormat.format; + swapchainInfo.imageColorSpace = swapchainSurfaceFormat.colorSpace; + swapchainInfo.imageExtent = swapchainExtent; + swapchainInfo.imageArrayLayers = 1; + swapchainInfo.imageUsage = swapchainImageUsage; + swapchainInfo.imageSharingMode = vk::SharingMode::eExclusive; + swapchainInfo.preTransform = swapchainSurfaceTransform; + swapchainInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + swapchainInfo.presentMode = swapchainPresentMode; + swapchainInfo.clipped = true; + swapchainInfo.oldSwapchain = nullptr; // Todo + + if (auto createResult = device->createSwapchainKHRUnique(swapchainInfo); createResult.result == vk::Result::eSuccess) { + swapchain = std::move(createResult.value); + } else { + Helpers::panic("Error creating swapchain: %s\n", vk::to_string(createResult.result).c_str()); + } + + // Get swapchain images + if (auto getResult = device->getSwapchainImagesKHR(swapchain.get()); getResult.result == vk::Result::eSuccess) { + swapchainImages = getResult.value; + swapchainImageViews.resize(swapchainImages.size()); + + // Create image-views + for (usize i = 0; i < swapchainImages.size(); i++) { + vk::ImageViewCreateInfo viewInfo = {}; + viewInfo.image = swapchainImages[i]; + viewInfo.viewType = vk::ImageViewType::e2D; + viewInfo.format = swapchainSurfaceFormat.format; + viewInfo.components = vk::ComponentMapping(); + viewInfo.subresourceRange = vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1); + + if (auto createResult = device->createImageViewUnique(viewInfo); createResult.result == vk::Result::eSuccess) { + swapchainImageViews[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating swapchain image-view: #%zu %s\n", i, vk::to_string(getResult.result).c_str()); + } + } + } else { + Helpers::panic("Error creating acquiring swapchain images: %s\n", vk::to_string(getResult.result).c_str()); + } + + // Command pool + vk::CommandPoolCreateInfo commandPoolInfo = {}; + commandPoolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + + if (auto createResult = device->createCommandPoolUnique(commandPoolInfo); createResult.result == vk::Result::eSuccess) { + commandPool = std::move(createResult.value); + } else { + Helpers::panic("Error creating command pool: %s\n", vk::to_string(createResult.result).c_str()); + } + + // Swapchain Command buffer(s) + vk::CommandBufferAllocateInfo commandBuffersInfo = {}; + commandBuffersInfo.commandPool = commandPool.get(); + commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary; + commandBuffersInfo.commandBufferCount = swapchainImageCount; + + if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) { + presentCommandBuffers = std::move(allocateResult.value); + } else { + Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str()); + } + + // Swapchain synchronization primitives + vk::FenceCreateInfo fenceInfo = {}; + fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; + + vk::SemaphoreCreateInfo semaphoreInfo = {}; + + swapImageFreeSemaphore.resize(swapchainImageCount); + renderFinishedSemaphore.resize(swapchainImageCount); + frameFinishedFences.resize(swapchainImageCount); + + for (usize i = 0; i < swapchainImageCount; i++) { + if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { + swapImageFreeSemaphore[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str()); + } + + if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { + renderFinishedSemaphore[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str()); + } + + if (auto createResult = device->createFenceUnique(fenceInfo); createResult.result == vk::Result::eSuccess) { + frameFinishedFences[i] = std::move(createResult.value); + } else { + Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str()); + } + } +} + +void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} + +void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {} + +void RendererVK::drawVertices(PICA::PrimType primType, std::span vertices) {} + +void RendererVK::screenshot(const std::string& name) {} \ No newline at end of file diff --git a/src/core/renderer_vk/vk_debug.cpp b/src/core/renderer_vk/vk_debug.cpp new file mode 100644 index 00000000..973d2a09 --- /dev/null +++ b/src/core/renderer_vk/vk_debug.cpp @@ -0,0 +1,156 @@ +#include "renderer_vk/vk_debug.hpp" + +#include +#include +#include +#include +#include + +#include "helpers.hpp" + +static std::uint8_t severityColor(vk::DebugUtilsMessageSeverityFlagBitsEXT Severity) { + switch (Severity) { + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose: { + // Dark Gray + return 90u; + } + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo: { + // Light Gray + return 90u; + } + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning: { + // Light Magenta + return 95u; + } + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eError: { + // Light red + return 91u; + } + } + // Default Foreground Color + return 39u; +} + +static std::uint8_t messageTypeColor(vk::DebugUtilsMessageTypeFlagsEXT MessageType) { + if (MessageType & vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral) { + // Dim + return 2u; + } + if (MessageType & vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance) { + // Bold/Bright + return 1u; + } + if (MessageType & vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation) { + // Light Gray + return 90u; + } + // Default Foreground Color + return 39u; +} + +namespace Vulkan { + + static void debugMessageCallback( + vk::DebugUtilsMessageSeverityFlagBitsEXT MessageSeverity, vk::DebugUtilsMessageTypeFlagsEXT MessageType, + const vk::DebugUtilsMessengerCallbackDataEXT& CallbackData + ) { + Helpers::debug_printf( + "\033[%um[vk][%s]: \033[%um%s\033[0m\n", severityColor(MessageSeverity), CallbackData.pMessageIdName, messageTypeColor(MessageType), + CallbackData.pMessage + ); + } + + VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* callbackData, void* userData + ) { + debugMessageCallback( + vk::DebugUtilsMessageSeverityFlagBitsEXT(messageSeverity), vk::DebugUtilsMessageTypeFlagsEXT(messageType), *callbackData + ); + return VK_FALSE; + } + + void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...) { + va_list args; + va_start(args, format); + const auto nameLength = std::vsnprintf(nullptr, 0, format, args); + va_end(args); + if (nameLength < 0) { + // Invalid vsnprintf + return; + } + + std::unique_ptr objectName = std::make_unique(std::size_t(nameLength) + 1u); + + // Write formatted object name + va_start(args, format); + std::vsnprintf(objectName.get(), std::size_t(nameLength) + 1u, format, args); + va_end(args); + + vk::DebugUtilsObjectNameInfoEXT nameInfo = {}; + nameInfo.objectType = objectType; + nameInfo.objectHandle = reinterpret_cast(objectHandle); + nameInfo.pObjectName = objectName.get(); + + if (device.setDebugUtilsObjectNameEXT(nameInfo) != vk::Result::eSuccess) { + // Failed to set object name + } + } + + void beginDebugLabel(vk::CommandBuffer commandBuffer, std::span color, const char* format, ...) { + va_list args; + va_start(args, format); + const auto nameLength = std::vsnprintf(nullptr, 0, format, args); + va_end(args); + if (nameLength < 0) { + // Invalid vsnprintf + return; + } + + std::unique_ptr objectName = std::make_unique(std::size_t(nameLength) + 1u); + + // Write formatted object name + va_start(args, format); + std::vsnprintf(objectName.get(), std::size_t(nameLength) + 1u, format, args); + va_end(args); + + vk::DebugUtilsLabelEXT labelInfo = {}; + labelInfo.pLabelName = objectName.get(); + labelInfo.color[0] = color[0]; + labelInfo.color[1] = color[1]; + labelInfo.color[2] = color[2]; + labelInfo.color[3] = color[3]; + + commandBuffer.beginDebugUtilsLabelEXT(labelInfo); + } + + void insertDebugLabel(vk::CommandBuffer commandBuffer, std::span color, const char* format, ...) { + va_list args; + va_start(args, format); + const auto nameLength = std::vsnprintf(nullptr, 0, format, args); + va_end(args); + if (nameLength < 0) { + // Invalid vsnprintf + return; + } + + std::unique_ptr objectName = std::make_unique(std::size_t(nameLength) + 1u); + + // Write formatted object name + va_start(args, format); + std::vsnprintf(objectName.get(), std::size_t(nameLength) + 1u, format, args); + va_end(args); + + vk::DebugUtilsLabelEXT labelInfo = {}; + labelInfo.pLabelName = objectName.get(); + labelInfo.color[0] = color[0]; + labelInfo.color[1] = color[1]; + labelInfo.color[2] = color[2]; + labelInfo.color[3] = color[3]; + + commandBuffer.insertDebugUtilsLabelEXT(labelInfo); + } + + void endDebugLabel(vk::CommandBuffer commandBuffer) { commandBuffer.endDebugUtilsLabelEXT(); } + +} // namespace Vulkan \ No newline at end of file diff --git a/src/core/renderer_vk/vulkan_api.cpp b/src/core/renderer_vk/vulkan_api.cpp new file mode 100644 index 00000000..c207eea7 --- /dev/null +++ b/src/core/renderer_vk/vulkan_api.cpp @@ -0,0 +1,3 @@ +#include "renderer_vk/vulkan_api.hpp" + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE; \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 98c4c67f..438f4b52 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -52,6 +52,16 @@ Emulator::Emulator() } } +#ifdef PANDA3DS_ENABLE_VULKAN + if (config.rendererType == RendererType::Vulkan) { + window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN); + + if (window == nullptr) { + Helpers::warn("Window creation failed: %s", SDL_GetError()); + } + } +#endif + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { gameController = SDL_GameControllerOpen(0); @@ -447,7 +457,7 @@ bool Emulator::loadELF(std::ifstream& file) { } // Reset our graphics context and initialize the GPU's graphics context -void Emulator::initGraphicsContext() { gpu.initGraphicsContext(); } +void Emulator::initGraphicsContext() { gpu.initGraphicsContext(window); } #ifdef PANDA3DS_ENABLE_HTTP_SERVER void Emulator::pollHttpServer() {