Merge pull request #125 from Wunkolo/vulkan-init

Initialize Vulkan Context
This commit is contained in:
wheremyfoodat 2023-07-24 21:05:26 +03:00 committed by GitHub
commit c87501f86f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 917 additions and 18 deletions

View file

@ -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
install_name_tool -add_rpath @loader_path/../Frameworks Alber.app/Contents/MacOS/Alber

View file

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

View file

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

View file

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

View file

@ -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,30 @@ if(ENABLE_OPENGL)
)
endif()
if(ENABLE_VULKAN)
find_package(
Vulkan 1.3.206 REQUIRED
COMPONENTS glslangValidator
)
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
include/renderer_vk/vulkan_api.hpp include/renderer_vk/vk_debug.hpp
)
set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp
src/core/renderer_vk/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp
)
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES})
source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES})
cmrc_add_resource_library(
resources_renderer_vk
NAMESPACE RendererVK
WHENCE "src/host_shaders/"
)
endif()
source_group("Header Files\\Core" FILES ${HEADER_FILES})
set(ALL_SOURCES ${SOURCE_FILES} ${FS_SOURCE_FILES} ${CRYPTO_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
${RENDERER_SW_SOURCE_FILES} ${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
@ -215,6 +241,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 +259,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()

View file

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

View file

@ -16,6 +16,7 @@ enum class RendererType : s8 {
};
class GPU;
struct SDL_Window;
class Renderer {
protected:
@ -42,7 +43,7 @@ class Renderer {
virtual void reset() = 0;
virtual void display() = 0; // Display the 3DS screen contents to the window
virtual void initGraphicsContext() = 0; // Initialize graphics context
virtual void initGraphicsContext(SDL_Window* window) = 0; // Initialize graphics context
virtual void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) = 0; // Clear a GPU buffer in VRAM
virtual void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) = 0; // Perform display transfer
virtual void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) = 0; // Draw the given vertices

View file

@ -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<const PICA::Vertex> vertices) override; // Draw the given vertices

View file

@ -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<const PICA::Vertex> vertices) override;

View file

@ -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<const PICA::Vertex> vertices) override;

View file

@ -0,0 +1,57 @@
#include "renderer.hpp"
#include "vulkan_api.hpp"
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 = {};
vk::UniqueSurfaceKHR surface = {};
vk::PhysicalDevice physicalDevice = {};
vk::UniqueDevice device = {};
vk::Queue presentQueue = {};
u32 presentQueueFamily = ~0u;
vk::Queue graphicsQueue = {};
u32 graphicsQueueFamily = ~0u;
vk::Queue computeQueue = {};
u32 computeQueueFamily = ~0u;
vk::Queue transferQueue = {};
u32 transferQueueFamily = ~0u;
vk::UniqueCommandPool commandPool = {};
vk::UniqueSwapchainKHR swapchain = {};
u32 swapchainImageCount = ~0u;
std::vector<vk::Image> swapchainImages = {};
std::vector<vk::UniqueImageView> swapchainImageViews = {};
// Per-swapchain-image data
// Each vector is `swapchainImageCount` in size
std::vector<vk::UniqueCommandBuffer> presentCommandBuffers = {};
std::vector<vk::UniqueSemaphore> swapImageFreeSemaphore = {};
std::vector<vk::UniqueSemaphore> renderFinishedSemaphore = {};
std::vector<vk::UniqueFence> 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<u32, regNum>& internalRegs);
~RendererVK() override;
void reset() override;
void display() override;
void initGraphicsContext(SDL_Window* window) override;
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override;
void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override;
void drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) override;
void screenshot(const std::string& name) override;
};

View file

@ -0,0 +1,48 @@
#pragma once
#include <span>
#include <type_traits>
#include <utility>
#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 <typename T, typename = std::enable_if_t<vk::isVulkanHandleType<T>::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<ArgsT>(args)...);
}
void beginDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> color, const char* format, ...);
void insertDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> color, const char* format, ...);
void endDebugLabel(vk::CommandBuffer commandBuffer);
class DebugLabelScope {
private:
const vk::CommandBuffer commandBuffer;
public:
template <typename... ArgsT>
DebugLabelScope(vk::CommandBuffer targetCommandBuffer, std::span<const float, 4> color, const char* format, ArgsT&&... args)
: commandBuffer(targetCommandBuffer) {
beginDebugLabel(commandBuffer, color, format, std::forward<ArgsT>(args)...);
}
template <typename... ArgsT>
void operator()(std::span<const float, 4> color, const char* format, ArgsT&&... args) const {
insertDebugLabel(commandBuffer, color, format, std::forward<ArgsT>(args)...);
}
~DebugLabelScope() { endDebugLabel(commandBuffer); }
};
} // namespace Vulkan

View file

@ -0,0 +1,12 @@
#pragma once
#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
#define VULKAN_HPP_NO_EXCEPTIONS
// Disable asserts on result-codes
#define VULKAN_HPP_ASSERT_ON_RESULT
#include <vulkan/vulkan.hpp>
#include <vulkan/vulkan_format_traits.hpp>
#include <vulkan/vulkan_hash.hpp>

View file

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

View file

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

View file

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

View file

@ -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<const PICA::Vertex> vertices) {}

View file

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

View file

@ -0,0 +1,546 @@
#include "renderer_vk/renderer_vk.hpp"
#include <limits>
#include <span>
#include <unordered_set>
#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<const vk::QueueFamilyProperties> queueFamilies, vk::QueueFlags queueMask,
vk::QueueFlags queueExcludeMask = vk::QueueFlagBits::eProtected
) {
for (usize i = 0; i < queueFamilies.size(); ++i) {
if (((queueFamilies[i].queueFlags & queueMask) == queueMask) && !(queueFamilies[i].queueFlags & queueExcludeMask)) {
return i;
}
}
return -1;
}
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<vk::PresentModeKHR>& 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<vk::SurfaceFormatKHR>& 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<vk::SurfaceFormatKHR>::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<u32, regNum>& internalRegs) : Renderer(gpu, internalRegs) {}
RendererVK::~RendererVK() {}
void RendererVK::reset() {}
void RendererVK::display() {
// Block, on the CPU, to ensure that this swapchain-frame is ready for more work
if (auto waitResult = device->waitForFences({frameFinishedFences[currentFrame].get()}, true, std::numeric_limits<u64>::max());
waitResult != vk::Result::eSuccess) {
Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str());
}
u32 swapchainImageIndex = std::numeric_limits<u32>::max();
if (const auto acquireResult =
device->acquireNextImageKHR(swapchain.get(), std::numeric_limits<u64>::max(), swapImageFreeSemaphore[currentFrame].get(), {});
acquireResult.result == vk::Result::eSuccess) {
swapchainImageIndex = acquireResult.value;
} else {
switch (acquireResult.result) {
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR: {
// Surface resized
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: {
Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str());
}
}
}
vk::UniqueCommandBuffer& presentCommandBuffer = presentCommandBuffers.at(currentFrame);
vk::CommandBufferBeginInfo beginInfo = {};
beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse;
if (const vk::Result beginResult = presentCommandBuffer->begin(beginInfo); beginResult != vk::Result::eSuccess) {
Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str());
}
{
static const std::array<float, 4> presentScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}};
Vulkan::DebugLabelScope debugScope(presentCommandBuffer.get(), presentScopeColor, "Present");
// Prepare for color-clear
presentCommandBuffer->pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {},
{vk::ImageMemoryBarrier(
vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex],
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
)}
);
presentCommandBuffer->clearColorImage(
swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, presentScopeColor,
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
);
// Prepare for present
presentCommandBuffer->pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {},
{vk::ImageMemoryBarrier(
vk::AccessFlagBits::eNone, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex],
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
)}
);
}
if (const vk::Result endResult = presentCommandBuffer->end(); endResult != vk::Result::eSuccess) {
Helpers::panic("Error ending command buffer recording: %s\n", vk::to_string(endResult).c_str());
}
vk::SubmitInfo submitInfo = {};
// Wait for any previous uses of the image image to finish presenting
submitInfo.setWaitSemaphores(swapImageFreeSemaphore[currentFrame].get());
// Signal when finished
submitInfo.setSignalSemaphores(renderFinishedSemaphore[currentFrame].get());
static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
submitInfo.setWaitDstStageMask(waitStageMask);
submitInfo.setCommandBuffers(presentCommandBuffer.get());
device->resetFences({frameFinishedFences[currentFrame].get()});
if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[currentFrame].get());
submitResult != vk::Result::eSuccess) {
Helpers::panic("Error submitting to graphics queue: %s\n", vk::to_string(submitResult).c_str());
}
vk::PresentInfoKHR presentInfo = {};
presentInfo.setWaitSemaphores(renderFinishedSemaphore[currentFrame].get());
presentInfo.setSwapchains(swapchain.get());
presentInfo.setImageIndices(swapchainImageIndex);
if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) {
} else {
switch (presentResult) {
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR: {
// Surface resized
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;
SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight);
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
}
recreateSwapchain(surface.get(), swapchainExtent);
break;
}
default: {
Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str());
}
}
}
currentFrame = ((currentFrame + 1) % swapchainImageCount);
}
void RendererVK::initGraphicsContext(SDL_Window* window) {
targetWindow = window;
// Resolve all instance function pointers
static vk::DynamicLoader dl;
VULKAN_HPP_DEFAULT_DISPATCHER.init(dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"));
// Create Instance
vk::ApplicationInfo applicationInfo = {};
applicationInfo.apiVersion = VK_API_VERSION_1_1;
applicationInfo.pEngineName = "Alber";
applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
applicationInfo.pApplicationName = "Alber";
applicationInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
vk::InstanceCreateInfo instanceInfo = {};
instanceInfo.pApplicationInfo = &applicationInfo;
std::vector<const char*> instanceExtensions = {
#if defined(__APPLE__)
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
#endif
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
};
// Get any additional extensions that SDL wants as well
{
unsigned int extensionCount = 0;
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr);
std::vector<const char*> sdlInstanceExtensions(extensionCount);
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, sdlInstanceExtensions.data());
instanceExtensions.insert(instanceExtensions.end(), sdlInstanceExtensions.begin(), sdlInstanceExtensions.end());
}
#if defined(__APPLE__)
instanceInfo.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
#endif
instanceInfo.ppEnabledExtensionNames = instanceExtensions.data();
instanceInfo.enabledExtensionCount = instanceExtensions.size();
if (auto createResult = vk::createInstanceUnique(instanceInfo); createResult.result == vk::Result::eSuccess) {
instance = std::move(createResult.value);
} else {
Helpers::panic("Error creating Vulkan instance: %s\n", vk::to_string(createResult.result).c_str());
}
// Initialize instance-specific function pointers
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get());
// Enable debug messenger if the instance was able to be created with debug_utils
if (std::find(
instanceExtensions.begin(), instanceExtensions.end(),
// std::string_view has a way to compare itself to `const char*`
// so by casting it, we get the actual string comparisons
// and not pointer-comparisons
std::string_view(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)
) != instanceExtensions.end()) {
vk::DebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
debugCreateInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning;
debugCreateInfo.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral;
debugCreateInfo.pfnUserCallback = &Vulkan::debugMessageCallback;
if (auto createResult = instance->createDebugUtilsMessengerEXTUnique(debugCreateInfo); createResult.result == vk::Result::eSuccess) {
debugMessenger = std::move(createResult.value);
} else {
Helpers::warn("Error registering debug messenger: %s", vk::to_string(createResult.result).c_str());
}
}
// Create surface
if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) {
surface.reset(newSurface);
} else {
Helpers::warn("Error creating Vulkan surface");
}
// Pick physical device
if (auto enumerateResult = instance->enumeratePhysicalDevices(); enumerateResult.result == vk::Result::eSuccess) {
std::vector<vk::PhysicalDevice> physicalDevices = std::move(enumerateResult.value);
std::vector<vk::PhysicalDevice>::iterator partitionEnd = physicalDevices.end();
// Prefer GPUs that can access the surface
const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool {
const usize queueCount = physicalDevice.getQueueFamilyProperties().size();
for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) {
if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, surface.get());
supportResult.result == vk::Result::eSuccess) {
return supportResult.value;
}
}
return false;
};
partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport);
// Prefer Discrete GPUs
const auto isDiscrete = [](const vk::PhysicalDevice& physicalDevice) -> bool {
return physicalDevice.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu;
};
partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, isDiscrete);
// Pick the "best" out of all of the previous criteria, preserving the order that the
// driver gave us the devices in(ex: optimus configuration)
physicalDevice = physicalDevices.front();
} else {
Helpers::panic("Error enumerating physical devices: %s\n", vk::to_string(enumerateResult.result).c_str());
}
// Get device queues
std::vector<vk::DeviceQueueCreateInfo> deviceQueueInfos;
{
const std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
// Get present queue family
for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) {
if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface.get());
supportResult.result == vk::Result::eSuccess) {
if (supportResult.value) {
presentQueueFamily = queueFamilyIndex;
break;
}
}
}
static const float queuePriority = 1.0f;
graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics);
computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute);
transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer);
// Requests a singular queue for each unique queue-family
const std::unordered_set<u32> queueFamilyRequests = {presentQueueFamily, graphicsQueueFamily, computeQueueFamily, transferQueueFamily};
for (const u32 queueFamilyIndex : queueFamilyRequests) {
deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, queueFamilyIndex, 1, &queuePriority));
}
}
// Create Device
vk::DeviceCreateInfo deviceInfo = {};
static const char* deviceExtensions[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
#if defined(__APPLE__)
"VK_KHR_portability_subset",
#endif
// VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME
};
deviceInfo.ppEnabledExtensionNames = deviceExtensions;
deviceInfo.enabledExtensionCount = std::size(deviceExtensions);
vk::StructureChain<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTimelineSemaphoreFeatures> deviceFeatureChain = {};
auto& deviceFeatures = deviceFeatureChain.get<vk::PhysicalDeviceFeatures2>().features;
auto& deviceTimelineFeatures = deviceFeatureChain.get<vk::PhysicalDeviceTimelineSemaphoreFeatures>();
// deviceTimelineFeatures.timelineSemaphore = true;
deviceInfo.pNext = &deviceFeatureChain.get();
deviceInfo.setQueueCreateInfos(deviceQueueInfos);
if (auto createResult = physicalDevice.createDeviceUnique(deviceInfo); createResult.result == vk::Result::eSuccess) {
device = std::move(createResult.value);
} else {
Helpers::panic("Error creating logical device: %s\n", vk::to_string(createResult.result).c_str());
}
// Initialize device-specific function pointers
VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get());
presentQueue = device->getQueue(presentQueueFamily, 0);
graphicsQueue = device->getQueue(presentQueueFamily, 0);
computeQueue = device->getQueue(computeQueueFamily, 0);
transferQueue = device->getQueue(transferQueueFamily, 0);
// 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());
}
// 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) {}
void RendererVK::displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) {}
void RendererVK::drawVertices(PICA::PrimType primType, std::span<const PICA::Vertex> vertices) {}
void RendererVK::screenshot(const std::string& name) {}

View file

@ -0,0 +1,163 @@
#include "renderer_vk/vk_debug.hpp"
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <memory>
#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;
}
#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);
const auto nameLength = std::vsnprintf(nullptr, 0, format, args);
va_end(args);
if (nameLength < 0) {
// Invalid vsnprintf
return;
}
std::unique_ptr<char[]> objectName = std::make_unique<char[]>(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<std::uintptr_t>(objectHandle);
nameInfo.pObjectName = objectName.get();
if (device.setDebugUtilsObjectNameEXT(nameInfo) != vk::Result::eSuccess) {
// Failed to set object name
}
}
void beginDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> 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<char[]> objectName = std::make_unique<char[]>(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<const float, 4> 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<char[]> objectName = std::make_unique<char[]>(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(); }
#else
void setObjectName(vk::Device device, vk::ObjectType objectType, const void* objectHandle, const char* format, ...) {}
void beginDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> color, const char* format, ...) {}
void insertDebugLabel(vk::CommandBuffer commandBuffer, std::span<const float, 4> color, const char* format, ...) {}
void endDebugLabel(vk::CommandBuffer commandBuffer) {}
#endif // GPU_DEBUG_INFO
} // namespace Vulkan

View file

@ -0,0 +1,3 @@
#include "renderer_vk/vulkan_api.hpp"
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;

View file

@ -52,6 +52,16 @@ Emulator::Emulator()
}
}
#ifdef PANDA3DS_ENABLE_VULKAN
if (config.rendererType == RendererType::Vulkan) {
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN);
if (window == nullptr) {
Helpers::warn("Window creation failed: %s", SDL_GetError());
}
}
#endif
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
gameController = SDL_GameControllerOpen(0);
@ -447,7 +457,7 @@ bool Emulator::loadELF(std::ifstream& file) {
}
// Reset our graphics context and initialize the GPU's graphics context
void Emulator::initGraphicsContext() { gpu.initGraphicsContext(); }
void Emulator::initGraphicsContext() { gpu.initGraphicsContext(window); }
#ifdef PANDA3DS_ENABLE_HTTP_SERVER
void Emulator::pollHttpServer() {