mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-11 08:39:48 +12:00
Merge remote-tracking branch 'upstream/master' into http-server-improvements
This commit is contained in:
commit
a0a821a1ac
26 changed files with 942 additions and 20 deletions
4
.github/mac-bundle.sh
vendored
4
.github/mac-bundle.sh
vendored
|
@ -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
|
||||
|
|
7
.github/workflows/Linux_Build.yml
vendored
7
.github/workflows/Linux_Build.yml
vendored
|
@ -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
|
||||
|
|
7
.github/workflows/MacOS_Build.yml
vendored
7
.github/workflows/MacOS_Build.yml
vendored
|
@ -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
|
||||
|
|
7
.github/workflows/Windows_Build.yml
vendored
7
.github/workflows/Windows_Build.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
57
include/renderer_vk/renderer_vk.hpp
Normal file
57
include/renderer_vk/renderer_vk.hpp
Normal 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;
|
||||
};
|
48
include/renderer_vk/vk_debug.hpp
Normal file
48
include/renderer_vk/vk_debug.hpp
Normal 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
|
12
include/renderer_vk/vulkan_api.hpp
Normal file
12
include/renderer_vk/vulkan_api.hpp
Normal 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>
|
|
@ -11,6 +11,7 @@ class ACService {
|
|||
MAKE_LOG_FUNCTION(log, acLogger)
|
||||
|
||||
// Service commands
|
||||
void getLastErrorCode(u32 messagePointer);
|
||||
void setClientVersion(u32 messagePointer);
|
||||
|
||||
public:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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) {
|
||||
|
|
546
src/core/renderer_vk/renderer_vk.cpp
Normal file
546
src/core/renderer_vk/renderer_vk.cpp
Normal 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) {}
|
163
src/core/renderer_vk/vk_debug.cpp
Normal file
163
src/core/renderer_vk/vk_debug.cpp
Normal 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
|
3
src/core/renderer_vk/vulkan_api.cpp
Normal file
3
src/core/renderer_vk/vulkan_api.cpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "renderer_vk/vulkan_api.hpp"
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -56,6 +56,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,4 +457,4 @@ 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); }
|
||||
|
|
Loading…
Add table
Reference in a new issue