From d2241a25bc829a704d2486870d879ab038b0a575 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 20:29:26 -0700 Subject: [PATCH 01/25] Stub Vulkan backend support A lot of the architecture of the emulator here does not allow for vulkan to initialize easily since it involves a bit of data to be exchanged between SDL and Vulkan. This commit just adds the foundational linkage and library code for vulkan support. --- CMakeLists.txt | 35 ++++++++++++++++++++++++++++ include/renderer_vk/renderer_vk.hpp | 18 ++++++++++++++ include/renderer_vk/vulkan_api.hpp | 10 ++++++++ src/core/PICA/gpu.cpp | 10 +++++--- src/core/renderer_vk/renderer_vk.cpp | 19 +++++++++++++++ src/core/renderer_vk/vulkan_api.cpp | 3 +++ src/emulator.cpp | 10 ++++++++ 7 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 include/renderer_vk/renderer_vk.hpp create mode 100644 include/renderer_vk/vulkan_api.hpp create mode 100644 src/core/renderer_vk/renderer_vk.cpp create mode 100644 src/core/renderer_vk/vulkan_api.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c84bf0c9..ae4b8c00 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 + ) + + set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp + src/core/renderer_vk/vulkan_api.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/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp new file mode 100644 index 00000000..b825692b --- /dev/null +++ b/include/renderer_vk/renderer_vk.hpp @@ -0,0 +1,18 @@ +#include "renderer.hpp" +#include "vulkan_api.hpp" + +class GPU; + +class RendererVK final : public Renderer { + public: + RendererVK(GPU& gpu, const std::array& internalRegs); + ~RendererVK() override; + + void reset() override; + void display() override; + void initGraphicsContext() 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/vulkan_api.hpp b/include/renderer_vk/vulkan_api.hpp new file mode 100644 index 00000000..7a606546 --- /dev/null +++ b/include/renderer_vk/vulkan_api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#define VK_NO_PROTOTYPES +#include + +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#define VULKAN_HPP_NO_EXCEPTIONS +#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_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp new file mode 100644 index 00000000..22922347 --- /dev/null +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -0,0 +1,19 @@ +#include "renderer_vk/renderer_vk.hpp" + +RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} + +RendererVK::~RendererVK() {} + +void RendererVK::reset() {} + +void RendererVK::display() {} + +void RendererVK::initGraphicsContext() { static vk::DynamicLoader dl; } + +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/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..8ad81eb6 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::panic("Window creation failed: %s", SDL_GetError()); + } + } +#endif + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { gameController = SDL_GameControllerOpen(0); From 870b6a21bf8bf411b8d628ca70ff3f2839256cc2 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 21:44:59 -0700 Subject: [PATCH 02/25] Add initial vulkan instance creation Headlessly creates a new vulkan instance, with conditional MacOS support, and enables the `VK_EXT_debug_utils` instance-extension with a debug-messenger to hook onto validation layer messages. --- CMakeLists.txt | 4 +- include/renderer_vk/renderer_vk.hpp | 6 ++ include/renderer_vk/vk_debug.hpp | 48 +++++++++ src/core/renderer_vk/renderer_vk.cpp | 123 ++++++++++++++++++++- src/core/renderer_vk/vk_debug.cpp | 156 +++++++++++++++++++++++++++ src/emulator.cpp | 4 +- 6 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 include/renderer_vk/vk_debug.hpp create mode 100644 src/core/renderer_vk/vk_debug.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ae4b8c00..a174a1ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,11 +214,11 @@ if(ENABLE_VULKAN) COMPONENTS glslangValidator ) set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp - include/renderer_vk/vulkan_api.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/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp ) set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES}) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index b825692b..7a2f9a55 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -4,6 +4,12 @@ class GPU; class RendererVK final : public Renderer { + vk::UniqueInstance instance = {}; + vk::PhysicalDevice physicalDevice = {}; + vk::UniqueDevice device = {}; + + vk::UniqueDebugUtilsMessengerEXT debugMessenger; + public: RendererVK(GPU& gpu, const std::array& internalRegs); ~RendererVK() override; 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/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 22922347..6c5f3362 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1,5 +1,8 @@ #include "renderer_vk/renderer_vk.hpp" +#include "helpers.hpp" +#include "renderer_vk/vk_debug.hpp" + RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} RendererVK::~RendererVK() {} @@ -8,7 +11,125 @@ void RendererVK::reset() {} void RendererVK::display() {} -void RendererVK::initGraphicsContext() { static vk::DynamicLoader dl; } +void RendererVK::initGraphicsContext() { + // Resolve all 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; + + static const std::array instanceExtensions = std::to_array({ +#if defined(__APPLE__) + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME +#endif + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + }); + +#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()); + } + } + + // Pick physical device + if (auto EnumerateResult = instance->enumeratePhysicalDevices(); EnumerateResult.result == vk::Result::eSuccess) { + std::vector PhysicalDevices = std::move(EnumerateResult.value); + + // Prefer Discrete GPUs + const auto IsDiscrete = [](const vk::PhysicalDevice& PhysicalDevice) -> bool { + return PhysicalDevice.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu; + }; + + std::partition(PhysicalDevices.begin(), PhysicalDevices.end(), IsDiscrete); + + // Pick the "best" out of all of the previous criteria + physicalDevice = PhysicalDevices.front(); + } else { + Helpers::panic("Error enumerating physical devices: %s\n", vk::to_string(EnumerateResult.result).c_str()); + } + + // Create Device + vk::DeviceCreateInfo deviceInfo = {}; + + static const char* deviceExtensions[] = { +#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(); + + static const float queuePriority = 1.0f; + + vk::DeviceQueueCreateInfo queueInfo = {}; + queueInfo.queueFamilyIndex = 0; + queueInfo.queueCount = 1; + queueInfo.pQueuePriorities = &queuePriority; + + deviceInfo.queueCreateInfoCount = 1; + deviceInfo.pQueueCreateInfos = &queueInfo; + + 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()); +} void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} 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/emulator.cpp b/src/emulator.cpp index 8ad81eb6..7355c2e0 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -54,10 +54,10 @@ 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); + // window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN); if (window == nullptr) { - Helpers::panic("Window creation failed: %s", SDL_GetError()); + // Helpers::panic("Window creation failed: %s", SDL_GetError()); } } #endif From b048d4dd6eb19c8601a123664bb73e7b3d34088a Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 22:00:28 -0700 Subject: [PATCH 03/25] Add `SDL_Window` to `initGraphicsContext` prototype This value is needed for vulkan to properly allocate a surface, and would benefit OpenGL to move more of its initialization code into here rather than in `emulator.cpp`. --- include/PICA/gpu.hpp | 6 ++---- include/renderer.hpp | 3 ++- include/renderer_gl/renderer_gl.hpp | 2 +- include/renderer_null/renderer_null.hpp | 2 +- include/renderer_vk/renderer_vk.hpp | 2 +- src/core/renderer_gl/renderer_gl.cpp | 2 +- src/core/renderer_null/renderer_null.cpp | 2 +- src/core/renderer_vk/renderer_vk.cpp | 6 +++++- src/emulator.cpp | 4 ++-- 9 files changed, 16 insertions(+), 13 deletions(-) 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..f8e57bb3 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -16,6 +16,7 @@ enum class RendererType : s8 { }; class GPU; +class 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_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 7a2f9a55..36ad17b3 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -16,7 +16,7 @@ class RendererVK 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/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_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 6c5f3362..36400fd7 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1,5 +1,6 @@ #include "renderer_vk/renderer_vk.hpp" +#include "SDL_vulkan.h" #include "helpers.hpp" #include "renderer_vk/vk_debug.hpp" @@ -11,7 +12,7 @@ void RendererVK::reset() {} void RendererVK::display() {} -void RendererVK::initGraphicsContext() { +void RendererVK::initGraphicsContext(SDL_Window* window) { // Resolve all function pointers static vk::DynamicLoader dl; VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); @@ -129,6 +130,9 @@ void RendererVK::initGraphicsContext() { // Initialize device-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); + + VkSurfaceKHR surface; + SDL_Vulkan_CreateSurface(window, instance.get(), &surface); } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} diff --git a/src/emulator.cpp b/src/emulator.cpp index 7355c2e0..26780852 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -54,7 +54,7 @@ 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); + window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN); if (window == nullptr) { // Helpers::panic("Window creation failed: %s", SDL_GetError()); @@ -457,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() { From d9afb12daf50865413ece77b6be2b17258eb5af7 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 22:05:14 -0700 Subject: [PATCH 04/25] Fix MacOS build --- src/core/renderer_vk/renderer_vk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 36400fd7..67d57edd 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -39,7 +39,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { }); #if defined(__APPLE__) - InstanceInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; + instanceInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; #endif instanceInfo.ppEnabledExtensionNames = instanceExtensions.data(); From eff62d3de7bcce3eaf076ea6582557a410678de5 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 22:20:20 -0700 Subject: [PATCH 05/25] Disable asserts on Vulkan result codes --- include/renderer_vk/vulkan_api.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/renderer_vk/vulkan_api.hpp b/include/renderer_vk/vulkan_api.hpp index 7a606546..e411220c 100644 --- a/include/renderer_vk/vulkan_api.hpp +++ b/include/renderer_vk/vulkan_api.hpp @@ -5,6 +5,8 @@ #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 From 444d50eaf542c1bd02f2d3eab1a446ad4d443250 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 22:26:56 -0700 Subject: [PATCH 06/25] Fix instance extensions on MacOS These preprocessors were combining into a singular string rather than the name of two individual extensions. Whoops. --- src/core/renderer_vk/renderer_vk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 67d57edd..c3c642e3 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -13,7 +13,7 @@ void RendererVK::reset() {} void RendererVK::display() {} void RendererVK::initGraphicsContext(SDL_Window* window) { - // Resolve all function pointers + // Resolve all instance function pointers static vk::DynamicLoader dl; VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); @@ -33,7 +33,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { static const std::array instanceExtensions = std::to_array({ #if defined(__APPLE__) - VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, #endif VK_EXT_DEBUG_UTILS_EXTENSION_NAME, }); From c7a93c4f520f0dd21e993223a1a4d4bfba45973c Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 22:38:08 -0700 Subject: [PATCH 07/25] Warn on failed surface creation for vulkan Vulkan can technically work just fine without presenting to the user, so consider these failed window procedures as "warnings" during iteration. --- include/renderer_vk/renderer_vk.hpp | 2 ++ src/core/renderer_vk/renderer_vk.cpp | 9 ++++++--- src/emulator.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 36ad17b3..0e98213c 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -8,6 +8,8 @@ class RendererVK final : public Renderer { vk::PhysicalDevice physicalDevice = {}; vk::UniqueDevice device = {}; + vk::UniqueSurfaceKHR surface = {}; + vk::UniqueDebugUtilsMessengerEXT debugMessenger; public: diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index c3c642e3..304ab73b 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -35,7 +35,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { #if defined(__APPLE__) VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, #endif - VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, }); #if defined(__APPLE__) @@ -131,8 +131,11 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { // Initialize device-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); - VkSurfaceKHR surface; - SDL_Vulkan_CreateSurface(window, instance.get(), &surface); + if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { + surface.reset(newSurface); + } else { + Helpers::warn("Error creating Vulkan surface"); + } } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} diff --git a/src/emulator.cpp b/src/emulator.cpp index 26780852..438f4b52 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -57,7 +57,7 @@ Emulator::Emulator() window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN); if (window == nullptr) { - // Helpers::panic("Window creation failed: %s", SDL_GetError()); + Helpers::warn("Window creation failed: %s", SDL_GetError()); } } #endif From 0ada1f4e386f03317a8eddca1cdcc6c0c91d64f9 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 23:11:50 -0700 Subject: [PATCH 08/25] Include additional vulkan-extensions that SDL requests Required for proper surface creation. This gets the surface-creation call to pass now. --- src/core/renderer_vk/renderer_vk.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 304ab73b..4bf47eae 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -31,12 +31,22 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { instanceInfo.pApplicationInfo = &applicationInfo; - static const std::array instanceExtensions = std::to_array({ + std::vector instanceExtensions = { #if defined(__APPLE__) VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, #endif - VK_EXT_DEBUG_UTILS_EXTENSION_NAME, - }); + 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; From 165134ca4099e46bfe81783769409f6658f2500c Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 23:32:21 -0700 Subject: [PATCH 09/25] Consider surface-support in device partitions Use a stable_partitioning of the list of devices the driver gave us with the addition of checking of the physical device supports the display-surface that SDL gave us as well. --- src/core/renderer_vk/renderer_vk.cpp | 48 ++++++++++++++++++---------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 4bf47eae..bd042131 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -84,21 +84,43 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } } - // Pick physical device - if (auto EnumerateResult = instance->enumeratePhysicalDevices(); EnumerateResult.result == vk::Result::eSuccess) { - std::vector PhysicalDevices = std::move(EnumerateResult.value); + // Create surface + if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { + surface.reset(newSurface); + } else { + Helpers::warn("Error creating Vulkan surface"); + } - // Prefer Discrete GPUs - const auto IsDiscrete = [](const vk::PhysicalDevice& PhysicalDevice) -> bool { - return PhysicalDevice.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu; + // 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; }; - std::partition(PhysicalDevices.begin(), PhysicalDevices.end(), IsDiscrete); + partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport); - // Pick the "best" out of all of the previous criteria - physicalDevice = PhysicalDevices.front(); + // 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()); + Helpers::panic("Error enumerating physical devices: %s\n", vk::to_string(enumerateResult.result).c_str()); } // Create Device @@ -140,12 +162,6 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { // Initialize device-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); - - if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) { - surface.reset(newSurface); - } else { - Helpers::warn("Error creating Vulkan surface"); - } } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} From 428a9d1f1af91aab1beb0d0d98e5f416c020c1e2 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Tue, 18 Jul 2023 23:39:17 -0700 Subject: [PATCH 10/25] Fix `SDL_Window` forward declaration Should be struct, not class. --- include/renderer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/renderer.hpp b/include/renderer.hpp index f8e57bb3..cd1ee53b 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -16,7 +16,7 @@ enum class RendererType : s8 { }; class GPU; -class SDL_Window; +struct SDL_Window; class Renderer { protected: From f715cb9478da19ab3baa11c63604d2746d31a1d2 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Wed, 19 Jul 2023 22:09:29 -0700 Subject: [PATCH 11/25] Install VulkanSDK into CI environment --- .github/workflows/Linux_Build.yml | 7 +++++++ .github/workflows/MacOS_Build.yml | 7 +++++++ .github/workflows/Windows_Build.yml | 7 +++++++ 3 files changed, 21 insertions(+) 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 From 8ace959d724e433a5f118c090c4c97560ae73f2d Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 13:20:57 -0700 Subject: [PATCH 12/25] Fix RendererSW `initGraphicsContext` prototype --- include/renderer_sw/renderer_sw.hpp | 2 +- src/core/renderer_sw/renderer_sw.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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) { From da6f8801283258622a870e842a9d327e3e5e59c7 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 13:29:58 -0700 Subject: [PATCH 13/25] Disable timeline semaphore features This is an optional feature that can be supported conditionally later --- src/core/renderer_vk/renderer_vk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index bd042131..8f18bb81 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -130,7 +130,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { #if defined(__APPLE__) "VK_KHR_portability_subset", #endif - VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME + //VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME }; deviceInfo.ppEnabledExtensionNames = deviceExtensions; deviceInfo.enabledExtensionCount = std::size(deviceExtensions); @@ -140,7 +140,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { auto& deviceFeatures = deviceFeatureChain.get().features; auto& deviceTimelineFeatures = deviceFeatureChain.get(); - deviceTimelineFeatures.timelineSemaphore = true; + //deviceTimelineFeatures.timelineSemaphore = true; deviceInfo.pNext = &deviceFeatureChain.get(); From 197e2a4bbd699dde966cf3c89f65e89386c0060a Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 14:23:37 -0700 Subject: [PATCH 14/25] Allocate present/graphics/compute/transfer queue families --- include/renderer_vk/renderer_vk.hpp | 15 ++++++- src/core/renderer_vk/renderer_vk.cpp | 64 ++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 0e98213c..fd33e95d 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -10,7 +10,20 @@ class RendererVK final : public Renderer { vk::UniqueSurfaceKHR surface = {}; - vk::UniqueDebugUtilsMessengerEXT debugMessenger; + vk::UniqueSwapchainKHR swapchain = {}; + std::vector swapchainImages = {}; + std::vector swapchainImageViews = {}; + + vk::UniqueDebugUtilsMessengerEXT debugMessenger = {}; + + vk::Queue presentQueue = {}; + u32 presentQueueFamily = ~0u; + vk::Queue graphicsQueue = {}; + u32 graphicsQueueFamily = ~0u; + vk::Queue computeQueue = {}; + u32 computeQueueFamily = ~0u; + vk::Queue transferQueue = {}; + u32 transferQueueFamily = ~0u; public: RendererVK(GPU& gpu, const std::array& internalRegs); diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 8f18bb81..37103ea2 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1,9 +1,26 @@ #include "renderer_vk/renderer_vk.hpp" +#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 1; + } + } + return -1; +} + RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} RendererVK::~RendererVK() {} @@ -123,14 +140,43 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { 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; + } + } + } + + graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics); + computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute); + transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer); + + static const float queuePriority = 1.0f; + + deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, presentQueueFamily, 1, {&queuePriority})); + deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, graphicsQueueFamily, 1, {&queuePriority})); + deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, computeQueueFamily, 1, {&queuePriority})); + deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, transferQueueFamily, 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 + // VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME }; deviceInfo.ppEnabledExtensionNames = deviceExtensions; deviceInfo.enabledExtensionCount = std::size(deviceExtensions); @@ -140,19 +186,14 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { auto& deviceFeatures = deviceFeatureChain.get().features; auto& deviceTimelineFeatures = deviceFeatureChain.get(); - //deviceTimelineFeatures.timelineSemaphore = true; + // deviceTimelineFeatures.timelineSemaphore = true; deviceInfo.pNext = &deviceFeatureChain.get(); static const float queuePriority = 1.0f; - vk::DeviceQueueCreateInfo queueInfo = {}; - queueInfo.queueFamilyIndex = 0; - queueInfo.queueCount = 1; - queueInfo.pQueuePriorities = &queuePriority; - - deviceInfo.queueCreateInfoCount = 1; - deviceInfo.pQueueCreateInfos = &queueInfo; + deviceInfo.queueCreateInfoCount = deviceQueueInfos.size(); + deviceInfo.pQueueCreateInfos = deviceQueueInfos.data(); if (auto createResult = physicalDevice.createDeviceUnique(deviceInfo); createResult.result == vk::Result::eSuccess) { device = std::move(createResult.value); @@ -160,6 +201,11 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { Helpers::panic("Error creating logical device: %s\n", vk::to_string(createResult.result).c_str()); } + presentQueue = device->getQueue(presentQueueFamily, 0); + graphicsQueue = device->getQueue(presentQueueFamily, 0); + computeQueue = device->getQueue(computeQueueFamily, 0); + transferQueue = device->getQueue(transferQueueFamily, 0); + // Initialize device-specific function pointers VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); } From 26c97eb71667223c086fdf9fd0ffa08dbff1452a Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 15:24:41 -0700 Subject: [PATCH 15/25] Implement initial swapchain creation --- include/renderer_vk/renderer_vk.hpp | 17 ++-- src/core/renderer_vk/renderer_vk.cpp | 121 ++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index fd33e95d..09fc4591 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -4,17 +4,13 @@ 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::PhysicalDevice physicalDevice = {}; - vk::UniqueDevice device = {}; + vk::UniqueDebugUtilsMessengerEXT debugMessenger = {}; vk::UniqueSurfaceKHR surface = {}; - vk::UniqueSwapchainKHR swapchain = {}; - std::vector swapchainImages = {}; - std::vector swapchainImageViews = {}; - - vk::UniqueDebugUtilsMessengerEXT debugMessenger = {}; + vk::PhysicalDevice physicalDevice = {}; vk::Queue presentQueue = {}; u32 presentQueueFamily = ~0u; @@ -25,6 +21,13 @@ class RendererVK final : public Renderer { vk::Queue transferQueue = {}; u32 transferQueueFamily = ~0u; + vk::UniqueDevice device = {}; + + + vk::UniqueSwapchainKHR swapchain = {}; + std::vector swapchainImages = {}; + std::vector swapchainImageViews = {}; + public: RendererVK(GPU& gpu, const std::array& internalRegs); ~RendererVK() override; diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 37103ea2..c2de9fad 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -201,13 +201,130 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { 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); - // Initialize device-specific function pointers - VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); + // 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::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + SDL_Vulkan_GetDrawableSize(window, &windowHeight, &windowWidth); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + + // Extent + Image count + Usage + Surface Transform + u8 swapchainImageCount; + 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()); + } } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} From fa804ae4c7af3f0920217459a846682a79f57d63 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 16:04:06 -0700 Subject: [PATCH 16/25] Implement swapchain draw loop Verified on Windows and MacOS(M2) --- include/renderer_vk/renderer_vk.hpp | 4 +- src/core/renderer_vk/renderer_vk.cpp | 116 ++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 09fc4591..36fa46b3 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -23,9 +23,11 @@ class RendererVK final : public Renderer { vk::UniqueDevice device = {}; + vk::UniqueSemaphore presetWaitSemaphore = {}; + vk::UniqueSemaphore renderDoneSemaphore = {}; vk::UniqueSwapchainKHR swapchain = {}; - std::vector swapchainImages = {}; + std::vector swapchainImages = {}; std::vector swapchainImageViews = {}; public: diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index c2de9fad..135a07da 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1,6 +1,7 @@ #include "renderer_vk/renderer_vk.hpp" #include +#include #include "SDL_vulkan.h" #include "helpers.hpp" @@ -14,8 +15,8 @@ static s32 findQueueFamily( 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 1; + if (((queueFamilies[i].queueFlags & queueMask) == queueMask) && !(queueFamilies[i].queueFlags & queueExcludeMask)) { + return i; } } return -1; @@ -27,7 +28,58 @@ RendererVK::~RendererVK() {} void RendererVK::reset() {} -void RendererVK::display() {} +void RendererVK::display() { + u32 swapchainImageIndex; + if (const auto acquireResult = device->acquireNextImageKHR(swapchain.get(), ~0ULL, presetWaitSemaphore.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::SubmitInfo submitInfo = {}; + submitInfo.setWaitSemaphores(presetWaitSemaphore.get()); + + static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eAllCommands; + submitInfo.setWaitDstStageMask(waitStageMask); + + submitInfo.setCommandBuffers({}); + submitInfo.setSignalSemaphores(renderDoneSemaphore.get()); + + if (const vk::Result submitResult = presentQueue.submit({submitInfo}); submitResult != vk::Result::eSuccess) { + Helpers::panic("Error submitting to present queue: %s\n", vk::to_string(submitResult).c_str()); + } + + vk::PresentInfoKHR presentInfo = {}; + presentInfo.setWaitSemaphores(renderDoneSemaphore.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()); + } + } + } +} void RendererVK::initGraphicsContext(SDL_Window* window) { // Resolve all instance function pointers @@ -141,6 +193,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } // Get device queues + std::vector deviceQueueInfos; { const std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); @@ -156,16 +209,17 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } } + static const float queuePriority = 1.0f; + graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics); computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute); transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer); - static const float queuePriority = 1.0f; - - deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, presentQueueFamily, 1, {&queuePriority})); - deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, graphicsQueueFamily, 1, {&queuePriority})); - deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, computeQueueFamily, 1, {&queuePriority})); - deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, transferQueueFamily, 1, {&queuePriority})); + // 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 @@ -190,10 +244,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { deviceInfo.pNext = &deviceFeatureChain.get(); - static const float queuePriority = 1.0f; - - deviceInfo.queueCreateInfoCount = deviceQueueInfos.size(); - deviceInfo.pQueueCreateInfos = deviceQueueInfos.data(); + deviceInfo.setQueueCreateInfos(deviceQueueInfos); if (auto createResult = physicalDevice.createDeviceUnique(deviceInfo); createResult.result == vk::Result::eSuccess) { device = std::move(createResult.value); @@ -209,6 +260,21 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { computeQueue = device->getQueue(computeQueueFamily, 0); transferQueue = device->getQueue(transferQueueFamily, 0); + // Synchronization primitives + vk::SemaphoreCreateInfo semaphoreInfo = {}; + + if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { + presetWaitSemaphore = 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) { + renderDoneSemaphore = std::move(createResult.value); + } else { + Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str()); + } + // 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 @@ -325,6 +391,30 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } 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()); + } } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} From 90a4c9cf8dca04a4c456b51421377ea2f8d9349b Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 17:11:06 -0700 Subject: [PATCH 17/25] Initialize command buffer allocationFix `SDL_Vulkan_GetDrawableSize` call --- include/renderer_vk/renderer_vk.hpp | 3 +++ src/core/renderer_vk/renderer_vk.cpp | 28 +++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 36fa46b3..1ffa2578 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -30,6 +30,9 @@ class RendererVK final : public Renderer { std::vector swapchainImages = {}; std::vector swapchainImageViews = {}; + vk::UniqueCommandPool commandPool = {}; + vk::UniqueCommandBuffer presentCommandBuffer = {}; + public: RendererVK(GPU& gpu, const std::array& internalRegs); ~RendererVK() override; diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 135a07da..57dfb0b8 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -29,7 +29,7 @@ RendererVK::~RendererVK() {} void RendererVK::reset() {} void RendererVK::display() { - u32 swapchainImageIndex; + u32 swapchainImageIndex = ~0u; if (const auto acquireResult = device->acquireNextImageKHR(swapchain.get(), ~0ULL, presetWaitSemaphore.get()); acquireResult.result == vk::Result::eSuccess) { swapchainImageIndex = acquireResult.value; @@ -53,7 +53,7 @@ void RendererVK::display() { static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eAllCommands; submitInfo.setWaitDstStageMask(waitStageMask); - submitInfo.setCommandBuffers({}); + submitInfo.setCommandBuffers(presentCommandBuffer.get()); submitInfo.setSignalSemaphores(renderDoneSemaphore.get()); if (const vk::Result submitResult = presentQueue.submit({submitInfo}); submitResult != vk::Result::eSuccess) { @@ -284,7 +284,7 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { vk::Extent2D swapchainExtent; { int windowWidth, windowHeight; - SDL_Vulkan_GetDrawableSize(window, &windowHeight, &windowWidth); + SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); swapchainExtent.width = windowWidth; swapchainExtent.height = windowHeight; } @@ -415,6 +415,28 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } 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()); + } + + // Command buffer(s) + vk::CommandBufferAllocateInfo commandBuffersInfo = {}; + commandBuffersInfo.commandPool = commandPool.get(); + commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary; + commandBuffersInfo.commandBufferCount = 1; + + if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) { + presentCommandBuffer = std::move(allocateResult.value[0]); + } else { + Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str()); + } } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} From af4163de197ba26faa2b93f6553a8c3094769d2b Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 18:54:02 -0700 Subject: [PATCH 18/25] Implement simple swapchain image clearing Creates a swapchain, and per-swapchain image data for safe parallelism and synchronization. --- include/renderer_vk/renderer_vk.hpp | 18 ++-- src/core/renderer_vk/renderer_vk.cpp | 125 +++++++++++++++++++++------ 2 files changed, 110 insertions(+), 33 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index 1ffa2578..fb17916f 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -23,16 +23,24 @@ class RendererVK final : public Renderer { vk::UniqueDevice device = {}; - vk::UniqueSemaphore presetWaitSemaphore = {}; - vk::UniqueSemaphore renderDoneSemaphore = {}; - vk::UniqueSwapchainKHR swapchain = {}; + u32 swapchainImageCount = ~0u; std::vector swapchainImages = {}; std::vector swapchainImageViews = {}; - vk::UniqueCommandPool commandPool = {}; - vk::UniqueCommandBuffer presentCommandBuffer = {}; + // 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; diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index 57dfb0b8..ef4a8ff2 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -29,8 +29,13 @@ 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, ~0ULL); waitResult != vk::Result::eSuccess) { + Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str()); + } + u32 swapchainImageIndex = ~0u; - if (const auto acquireResult = device->acquireNextImageKHR(swapchain.get(), ~0ULL, presetWaitSemaphore.get()); + if (const auto acquireResult = device->acquireNextImageKHR(swapchain.get(), ~0ULL, swapImageFreeSemaphore[currentFrame].get(), {}); acquireResult.result == vk::Result::eSuccess) { swapchainImageIndex = acquireResult.value; } else { @@ -47,21 +52,69 @@ void RendererVK::display() { } } - vk::SubmitInfo submitInfo = {}; - submitInfo.setWaitSemaphores(presetWaitSemaphore.get()); + vk::UniqueCommandBuffer& presentCommandBuffer = presentCommandBuffers.at(currentFrame); - static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eAllCommands; + 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()); - submitInfo.setSignalSemaphores(renderDoneSemaphore.get()); - if (const vk::Result submitResult = presentQueue.submit({submitInfo}); submitResult != vk::Result::eSuccess) { - Helpers::panic("Error submitting to present queue: %s\n", vk::to_string(submitResult).c_str()); + 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(renderDoneSemaphore.get()); + presentInfo.setWaitSemaphores(renderFinishedSemaphore[currentFrame].get()); presentInfo.setSwapchains(swapchain.get()); presentInfo.setImageIndices(swapchainImageIndex); @@ -79,6 +132,8 @@ void RendererVK::display() { } } } + + currentFrame = ((currentFrame + 1) % swapchainImageCount); } void RendererVK::initGraphicsContext(SDL_Window* window) { @@ -260,26 +315,11 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { computeQueue = device->getQueue(computeQueueFamily, 0); transferQueue = device->getQueue(transferQueueFamily, 0); - // Synchronization primitives - vk::SemaphoreCreateInfo semaphoreInfo = {}; - - if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) { - presetWaitSemaphore = 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) { - renderDoneSemaphore = std::move(createResult.value); - } else { - Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str()); - } - // 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::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst); vk::Extent2D swapchainExtent; { @@ -290,7 +330,6 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { } // Extent + Image count + Usage + Surface Transform - u8 swapchainImageCount; vk::ImageUsageFlags swapchainImageUsage; vk::SurfaceTransformFlagBitsKHR swapchainSurfaceTransform; if (const auto getResult = physicalDevice.getSurfaceCapabilitiesKHR(surface.get()); getResult.result == vk::Result::eSuccess) { @@ -426,17 +465,47 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { Helpers::panic("Error creating command pool: %s\n", vk::to_string(createResult.result).c_str()); } - // Command buffer(s) + // Swapchain Command buffer(s) vk::CommandBufferAllocateInfo commandBuffersInfo = {}; commandBuffersInfo.commandPool = commandPool.get(); commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary; - commandBuffersInfo.commandBufferCount = 1; + commandBuffersInfo.commandBufferCount = swapchainImageCount; if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) { - presentCommandBuffer = std::move(allocateResult.value[0]); + 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) {} From c49fd71722e831480c98d04b24aa04d1b618c2d9 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sat, 22 Jul 2023 19:44:09 -0700 Subject: [PATCH 19/25] Use `numeric_limits` rather than `~0ULL` --- src/core/renderer_vk/renderer_vk.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index ef4a8ff2..fc6553b2 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "SDL_vulkan.h" #include "helpers.hpp" @@ -30,12 +31,13 @@ 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, ~0ULL); waitResult != vk::Result::eSuccess) { + 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 = ~0u; - if (const auto acquireResult = device->acquireNextImageKHR(swapchain.get(), ~0ULL, swapImageFreeSemaphore[currentFrame].get(), {}); + 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 { From 34b87e50bd39f89a3d0e58d7c3663f842e1d3089 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 24 Jul 2023 00:04:51 +0300 Subject: [PATCH 20/25] Mac pls --- .github/mac-bundle.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/mac-bundle.sh b/.github/mac-bundle.sh index 83947a24..c932c905 100755 --- a/.github/mac-bundle.sh +++ b/.github/mac-bundle.sh @@ -37,7 +37,7 @@ PlistBuddy Alber.app/Contents/Info.plist -c "add NSHighResolutionCapable bool tr PlistBuddy Alber.app/Contents/version.plist -c "add ProjectName string Alber" # Bundle dylibs -dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath +dylibbundler -od -b -x Alber.app/Contents/MacOS/Alber -d Alber.app/Contents/Frameworks/ -p @rpath -s /Users/runner/work/Panda3DS/Panda3DS/VULKAN_SDK/lib # relative rpath -install_name_tool -add_rpath @loader_path/../Frameworks Alber.app/Contents/MacOS/Alber \ No newline at end of file +install_name_tool -add_rpath @loader_path/../Frameworks Alber.app/Contents/MacOS/Alber From e2e49b729195656366fb0f6493b373502a1a6c78 Mon Sep 17 00:00:00 2001 From: Wunkolo Date: Sun, 23 Jul 2023 21:44:09 -0700 Subject: [PATCH 21/25] Add in-place swapchain recreation Lots of todos, this should probably just be its own self-contained object to allow the emulator to render "headlessly" regardless of a swapchain existing or not. --- include/renderer_vk/renderer_vk.hpp | 14 +- src/core/renderer_vk/renderer_vk.cpp | 399 ++++++++++++++------------- 2 files changed, 221 insertions(+), 192 deletions(-) diff --git a/include/renderer_vk/renderer_vk.hpp b/include/renderer_vk/renderer_vk.hpp index fb17916f..5e621bdc 100644 --- a/include/renderer_vk/renderer_vk.hpp +++ b/include/renderer_vk/renderer_vk.hpp @@ -4,6 +4,8 @@ class GPU; class RendererVK final : public Renderer { + SDL_Window* targetWindow; + // The order of these `Unique*` members is important, they will be destroyed in RAII order vk::UniqueInstance instance = {}; vk::UniqueDebugUtilsMessengerEXT debugMessenger = {}; @@ -12,6 +14,8 @@ class RendererVK final : public Renderer { vk::PhysicalDevice physicalDevice = {}; + vk::UniqueDevice device = {}; + vk::Queue presentQueue = {}; u32 presentQueueFamily = ~0u; vk::Queue graphicsQueue = {}; @@ -21,18 +25,13 @@ class RendererVK final : public Renderer { vk::Queue transferQueue = {}; u32 transferQueueFamily = ~0u; - vk::UniqueDevice device = {}; + vk::UniqueCommandPool commandPool = {}; 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 = {}; @@ -40,6 +39,9 @@ class RendererVK final : public Renderer { std::vector renderFinishedSemaphore = {}; std::vector frameFinishedFences = {}; + // Recreate the swapchain, possibly re-using the old one in the case of a resize + vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent); + u64 currentFrame = 0; public: RendererVK(GPU& gpu, const std::array& internalRegs); diff --git a/src/core/renderer_vk/renderer_vk.cpp b/src/core/renderer_vk/renderer_vk.cpp index fc6553b2..e13a1597 100644 --- a/src/core/renderer_vk/renderer_vk.cpp +++ b/src/core/renderer_vk/renderer_vk.cpp @@ -1,8 +1,8 @@ #include "renderer_vk/renderer_vk.hpp" +#include #include #include -#include #include "SDL_vulkan.h" #include "helpers.hpp" @@ -23,6 +23,183 @@ static s32 findQueueFamily( return -1; } +vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent) { + 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); + + // Extent + Image count + Usage + Surface Transform + vk::ImageUsageFlags swapchainImageUsage; + vk::SurfaceTransformFlagBitsKHR swapchainSurfaceTransform; + if (const auto getResult = physicalDevice.getSurfaceCapabilitiesKHR(surface); 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); 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); 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; + 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 = swapchain.get(); + + 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()); + } + + // 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()); + } + } + + return vk::Result::eSuccess; +} + RendererVK::RendererVK(GPU& gpu, const std::array& internalRegs) : Renderer(gpu, internalRegs) {} RendererVK::~RendererVK() {} @@ -31,7 +208,8 @@ 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) { + 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()); } @@ -45,7 +223,19 @@ void RendererVK::display() { case vk::Result::eSuboptimalKHR: case vk::Result::eErrorOutOfDateKHR: { // Surface resized - // Todo: Recreate swapchain and get a valid image index + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + // Block until we have a valid surface-area to present to + // Usually this is because the window has been minimized + // Todo: We should still be rendering even without a valid swapchain + do { + SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); + } while (!windowWidth || !windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + recreateSwapchain(surface.get(), swapchainExtent); break; } default: { @@ -64,7 +254,7 @@ void RendererVK::display() { } { - static const std::array presentScopeColor = {{1.0f, currentFrame / 2.0f, 1.0f, 1.0f}}; + static const std::array presentScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}}; Vulkan::DebugLabelScope debugScope(presentCommandBuffer.get(), presentScopeColor, "Present"); @@ -111,7 +301,8 @@ void RendererVK::display() { device->resetFences({frameFinishedFences[currentFrame].get()}); - if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[currentFrame].get()); submitResult != vk::Result::eSuccess) { + 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()); } @@ -126,7 +317,14 @@ void RendererVK::display() { case vk::Result::eSuboptimalKHR: case vk::Result::eErrorOutOfDateKHR: { // Surface resized - // Todo: Recreate swapchain and get a valid image index + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; + } + recreateSwapchain(surface.get(), swapchainExtent); break; } default: { @@ -139,6 +337,7 @@ void RendererVK::display() { } void RendererVK::initGraphicsContext(SDL_Window* window) { + targetWindow = window; // Resolve all instance function pointers static vk::DynamicLoader dl; VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress("vkGetInstanceProcAddr")); @@ -317,146 +516,6 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { 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; @@ -467,47 +526,15 @@ void RendererVK::initGraphicsContext(SDL_Window* window) { 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()); - } + // Create swapchain + vk::Extent2D swapchainExtent; + { + int windowWidth, windowHeight; + SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight); + swapchainExtent.width = windowWidth; + swapchainExtent.height = windowHeight; } + recreateSwapchain(surface.get(), swapchainExtent); } void RendererVK::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {} From 71bcfbab6974d8103c6b19d4de3d80771bb3ab28 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:23:47 +0300 Subject: [PATCH 22/25] [Vulkan] Respect GPU_DEBUG_INFO --- src/core/renderer_vk/vk_debug.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/renderer_vk/vk_debug.cpp b/src/core/renderer_vk/vk_debug.cpp index 973d2a09..f3f099c8 100644 --- a/src/core/renderer_vk/vk_debug.cpp +++ b/src/core/renderer_vk/vk_debug.cpp @@ -70,6 +70,7 @@ namespace Vulkan { return VK_FALSE; } + #ifdef GPU_DEBUG_INFO void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...) { va_list args; va_start(args, format); @@ -152,5 +153,11 @@ namespace Vulkan { } void endDebugLabel(vk::CommandBuffer commandBuffer) { commandBuffer.endDebugUtilsLabelEXT(); } + #else + void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...) {} + 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) {} + #endif // GPU_DEBUG_INFO } // namespace Vulkan \ No newline at end of file From 4a1ed0652512857e56ee2d0aa07c239ae8af7104 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 24 Jul 2023 21:04:36 +0300 Subject: [PATCH 23/25] Update build instructions --- CMakeLists.txt | 1 + readme.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a174a1ad..aef7fa46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,7 @@ if(ENABLE_VULKAN) 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 ) diff --git a/readme.md b/readme.md index 854267b6..c334572b 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ The 3DS emulation scene is already pretty mature, with offerings such as [Citra] Keep in mind, these are all long-term plans. Until then, the main focus is just improving compatibility # How to build -Panda3DS compiles on Windows, Linux and MacOS, without needing to download any system dependencies. +Panda3DS compiles on Windows, Linux and MacOS, with only 1 system dependency, the Vulkan SDK. However, if you don't want to install the Vulkan SDK you can always build the emulator with only OpenGL support, by adding `-DENABLE_VULKAN=OFF` to the `cmake` command All you need is CMake and a generator of your choice (Make, Visual Studio, Ninja, etc). Simply clone the repo recursively and build it like your average CMake project. From 5b90cb7a03dc09aab54dbb5af2e243f677b98c22 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 24 Jul 2023 23:56:53 +0300 Subject: [PATCH 24/25] [MIC] Stub SetIIRFilter (Fixes Hatsune Miku) --- include/services/mic.hpp | 1 + src/core/services/mic.cpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/services/mic.hpp b/include/services/mic.hpp index b8b4d5c9..568d498c 100644 --- a/include/services/mic.hpp +++ b/include/services/mic.hpp @@ -15,6 +15,7 @@ class MICService { void mapSharedMem(u32 messagePointer); void setClamp(u32 messagePointer); void setGain(u32 messagePointer); + void setIirFilter(u32 messagePointer); void setPower(u32 messagePointer); void startSampling(u32 messagePointer); void theCaptainToadFunction(u32 messagePointer); diff --git a/src/core/services/mic.cpp b/src/core/services/mic.cpp index 7f283ce8..0648ad53 100644 --- a/src/core/services/mic.cpp +++ b/src/core/services/mic.cpp @@ -8,8 +8,9 @@ namespace MICCommands { SetGain = 0x00080040, GetGain = 0x00090000, SetPower = 0x000A0040, + SetIirFilter = 0x000C0042, SetClamp = 0x000D0040, - CaptainToadFunction = 0x00100040 + CaptainToadFunction = 0x00100040, }; } @@ -26,6 +27,7 @@ void MICService::handleSyncRequest(u32 messagePointer) { case MICCommands::MapSharedMem: mapSharedMem(messagePointer); break; case MICCommands::SetClamp: setClamp(messagePointer); break; case MICCommands::SetGain: setGain(messagePointer); break; + case MICCommands::SetIirFilter: setIirFilter(messagePointer); break; case MICCommands::SetPower: setPower(messagePointer); break; case MICCommands::StartSampling: startSampling(messagePointer); break; case MICCommands::CaptainToadFunction: theCaptainToadFunction(messagePointer); break; @@ -90,6 +92,15 @@ void MICService::startSampling(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void MICService::setIirFilter(u32 messagePointer) { + const u32 size = mem.read32(messagePointer + 4); + const u32 pointer = mem.read32(messagePointer + 12); + log("MIC::SetIirFilter (size = %X, pointer = %08X) (Stubbed)\n", size, pointer); + + mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 2)); + mem.write32(messagePointer + 4, Result::Success); +} + // Found in Captain Toad: Treasure Tracker // This is what 3DBrew says: // When the input value is 0, value 1 is written to an u8 MIC module state field. From 6c983b16d325aecec6e0fb811f5165caed16a116 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 24 Jul 2023 23:57:17 +0300 Subject: [PATCH 25/25] [AC] Stub GetLastErrorCode --- include/services/ac.hpp | 1 + src/core/services/ac.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/services/ac.hpp b/include/services/ac.hpp index e9aa8920..42129662 100644 --- a/include/services/ac.hpp +++ b/include/services/ac.hpp @@ -11,6 +11,7 @@ class ACService { MAKE_LOG_FUNCTION(log, acLogger) // Service commands + void getLastErrorCode(u32 messagePointer); void setClientVersion(u32 messagePointer); public: diff --git a/src/core/services/ac.cpp b/src/core/services/ac.cpp index 4c533e05..68868dbf 100644 --- a/src/core/services/ac.cpp +++ b/src/core/services/ac.cpp @@ -3,7 +3,8 @@ namespace ACCommands { enum : u32 { - SetClientVersion = 0x00400042 + GetLastErrorCode = 0x000A0000, + SetClientVersion = 0x00400042, }; } @@ -12,11 +13,20 @@ void ACService::reset() {} void ACService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { + case ACCommands::GetLastErrorCode: getLastErrorCode(messagePointer); break; case ACCommands::SetClientVersion: setClientVersion(messagePointer); break; default: Helpers::panic("AC service requested. Command: %08X\n", command); } } +void ACService::getLastErrorCode(u32 messagePointer) { + log("AC::GetLastErrorCode (stubbed)\n"); + + mem.write32(messagePointer, IPC::responseHeader(0x0A, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, 0); // Hopefully this means no error? +} + void ACService::setClientVersion(u32 messagePointer) { u32 version = mem.read32(messagePointer + 4); log("AC::SetClientVersion (version = %d)\n", version);