aurora: Dawn initialization

This commit is contained in:
Luke Street 2022-02-16 02:05:42 -05:00
parent 7b9f893a49
commit ea3641153e
5 changed files with 508 additions and 11 deletions

View File

@ -484,11 +484,6 @@ target_include_directories(hecl-light PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(hecl-full PRIVATE zeus nod) target_link_libraries(hecl-full PRIVATE zeus nod)
target_link_libraries(hecl-light PRIVATE zeus nod) target_link_libraries(hecl-light PRIVATE zeus nod)
add_subdirectory(extern/SDL EXCLUDE_FROM_ALL)
if (NOT MSVC)
target_compile_options(SDL2-static PRIVATE -Wno-implicit-fallthrough)
endif ()
if(NOT TARGET atdna) if(NOT TARGET atdna)
# Import native atdna if cross-compiling # Import native atdna if cross-compiling
find_package(atdna REQUIRED) find_package(atdna REQUIRED)

View File

@ -1,3 +1,8 @@
add_subdirectory(../extern/SDL SDL2 EXCLUDE_FROM_ALL)
if (NOT MSVC)
target_compile_options(SDL2-static PRIVATE -Wno-implicit-fallthrough)
endif ()
add_subdirectory(../extern/dawn dawn EXCLUDE_FROM_ALL) add_subdirectory(../extern/dawn dawn EXCLUDE_FROM_ALL)
if (NOT MSVC) if (NOT MSVC)
target_compile_options(SPIRV-Tools-static PRIVATE -Wno-implicit-fallthrough) target_compile_options(SPIRV-Tools-static PRIVATE -Wno-implicit-fallthrough)
@ -7,4 +12,5 @@ endif ()
add_library(aurora STATIC lib/aurora.cpp lib/imgui.cpp lib/gfx/common.cpp) add_library(aurora STATIC lib/aurora.cpp lib/imgui.cpp lib/gfx/common.cpp)
target_include_directories(aurora PUBLIC include ../Runtime) target_include_directories(aurora PUBLIC include ../Runtime)
target_include_directories(aurora PRIVATE ../imgui ../extern/imgui) target_include_directories(aurora PRIVATE ../imgui ../extern/imgui)
target_link_libraries(aurora PRIVATE webgpu_dawn zeus logvisor) # TODO remove logvisor from rstl.hpp target_link_libraries(aurora PRIVATE dawn_native dawncpp webgpu_dawn zeus logvisor SDL2-static)
target_compile_definitions(aurora PRIVATE AURORA_ENABLE_VULKAN AURORA_ENABLE_OPENGL)

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "common.hpp"
#include <array> #include <array>
#include <cinttypes> #include <cinttypes>
#include <type_traits> #include <type_traits>
@ -220,7 +222,7 @@ struct AppDelegate {
void app_run(std::unique_ptr<AppDelegate> app, Icon icon) noexcept; void app_run(std::unique_ptr<AppDelegate> app, Icon icon) noexcept;
[[nodiscard]] std::vector<std::string> get_args() noexcept; [[nodiscard]] std::vector<std::string> get_args() noexcept;
[[nodiscard]] WindowSize get_window_size() noexcept; [[nodiscard]] WindowSize get_window_size() noexcept;
void set_window_title(std::string_view title) noexcept; void set_window_title(zstring_view title) noexcept;
[[nodiscard]] Backend get_backend() noexcept; [[nodiscard]] Backend get_backend() noexcept;
[[nodiscard]] std::string_view get_backend_string() noexcept; [[nodiscard]] std::string_view get_backend_string() noexcept;
void set_fullscreen(bool fullscreen) noexcept; void set_fullscreen(bool fullscreen) noexcept;

View File

@ -69,4 +69,330 @@ private:
const T* ptr = nullptr; const T* ptr = nullptr;
size_t length = 0; size_t length = 0;
}; };
template <typename CharT, typename Traits = std::char_traits<CharT>>
class basic_zstring_view;
using zstring_view = basic_zstring_view<char>;
using wzstring_view = basic_zstring_view<wchar_t>;
using u16zstring_view = basic_zstring_view<char16_t>;
using u32zstring_view = basic_zstring_view<char32_t>;
template <typename CharT, typename Traits>
class basic_zstring_view {
public:
using underlying_type = std::basic_string_view<CharT, Traits>;
using traits_type = typename underlying_type::traits_type;
using value_type = typename underlying_type::value_type;
using pointer = typename underlying_type::pointer;
using const_pointer = typename underlying_type::const_pointer;
using reference = typename underlying_type::reference;
using const_reference = typename underlying_type::const_reference;
using const_iterator = typename underlying_type::const_iterator;
using iterator = typename underlying_type::iterator;
using const_reverse_iterator = typename underlying_type::const_reverse_iterator;
using reverse_iterator = typename underlying_type::reverse_iterator;
using size_type = typename underlying_type::size_type;
using difference_type = typename underlying_type::difference_type;
static constexpr size_type npos = -1;
constexpr basic_zstring_view() noexcept = default;
constexpr basic_zstring_view(const basic_zstring_view& other) noexcept = default;
constexpr basic_zstring_view(const CharT* s) : m_view{s} {}
constexpr basic_zstring_view(const std::string& str) : m_view{str.c_str()} {}
// Needed as a workaround for gcc bug #61648
// Allows non-friend string literal operators the ability to indirectly call the private constructor
// Safe calling of this requires *certainty* that s[count] is a null character
// Has to be public thusly, but don't call this
static constexpr basic_zstring_view _INTERNAL_unsafe_make_from_string_range(const CharT* s, size_type count) {
return {s, count};
}
constexpr basic_zstring_view& operator=(const basic_zstring_view& view) noexcept = default;
constexpr const_iterator begin() const noexcept { return m_view.begin(); }
constexpr const_iterator cbegin() const noexcept { return m_view.cbegin(); }
constexpr const_iterator end() const noexcept { return m_view.end(); }
constexpr const_iterator cend() const noexcept { return m_view.cend(); }
constexpr const_reverse_iterator rbegin() const noexcept { return m_view.rbegin(); }
constexpr const_reverse_iterator crbegin() const noexcept { return m_view.crbegin(); }
constexpr const_reverse_iterator rend() const noexcept { return m_view.rend(); }
constexpr const_reverse_iterator crend() const noexcept { return m_view.crend(); }
constexpr const_reference operator[](size_type pos) const { return m_view[pos]; }
constexpr const_reference at(size_type pos) const { return m_view.at(pos); }
constexpr const_reference front() const { return m_view.front(); }
constexpr const_reference back() const { return m_view.back(); }
constexpr const_pointer data() const noexcept { return m_view.data(); }
// Additional function
constexpr const_pointer c_str() const noexcept { return m_view.data(); }
constexpr size_type size() const noexcept { return m_view.size(); }
constexpr size_type length() const noexcept { return m_view.length(); }
constexpr size_type max_size() const noexcept { return m_view.max_size(); }
constexpr bool empty() const noexcept { return m_view.empty(); }
constexpr void remove_prefix(size_type n) { m_view.remove_prefix(n); }
constexpr void swap(basic_zstring_view& v) noexcept { swap(m_view, v.m_view); }
size_type copy(CharT* dest, size_type count, size_type pos = 0) const { return m_view.copy(dest, count, pos); }
constexpr underlying_type substr(size_type pos = 0, size_type count = npos) const {
return m_view.substr(pos, count);
}
// Additional function
constexpr basic_zstring_view suffix(size_type start = 0) const {
return basic_zstring_view{m_view.substr(start, npos)};
}
constexpr int compare(underlying_type v) const noexcept { return m_view.compare(v); }
constexpr int compare(size_type pos1, size_type count1, underlying_type v) const {
return m_view.compare(pos1, count1, v);
}
constexpr int compare(size_type pos1, size_type count1, underlying_type v, size_type pos2, size_type count2) const {
return m_view.compare(pos1, count1, v, pos2, count2);
}
constexpr int compare(const CharT* s) const { return m_view.compare(s); }
constexpr int compare(size_type pos1, size_type count1, const CharT* s, size_type count2) const {
return m_view.compare(pos1, count1, s, count2);
}
constexpr bool starts_with(underlying_type x) const noexcept { return m_view.starts_with(x); }
constexpr bool starts_with(CharT x) const noexcept { return m_view.starts_with(x); }
constexpr bool starts_with(const CharT* x) const { return m_view.starts_with(x); }
constexpr bool ends_with(underlying_type x) const noexcept { return m_view.ends_with(x); }
constexpr bool ends_with(CharT x) const noexcept { return m_view.ends_with(x); }
constexpr bool ends_with(const CharT* x) const { return m_view.ends_with(x); }
constexpr size_type find(underlying_type v, size_type pos = 0) const noexcept { return m_view.find(v, pos); }
constexpr size_type find(CharT ch, size_type pos = 0) const noexcept { return m_view.find(ch, pos); }
constexpr size_type find(const CharT* s, size_type pos, size_type count) const { return m_view.find(s, pos, count); }
constexpr size_type find(const CharT* s, size_type pos = 0) const { return m_view.find(s, pos); }
constexpr size_type rfind(underlying_type v, size_type pos = npos) const noexcept { return m_view.rfind(v, pos); }
constexpr size_type rfind(CharT ch, size_type pos = npos) const noexcept { return m_view.rfind(ch, pos); }
constexpr size_type rfind(const CharT* s, size_type pos, size_type count) const {
return m_view.rfind(s, pos, count);
}
constexpr size_type rfind(const CharT* s, size_type pos = npos) const { return m_view.rfind(s, pos); }
constexpr size_type find_first_of(underlying_type v, size_type pos = 0) const noexcept {
return m_view.find_first_of(v, pos);
}
constexpr size_type find_first_of(CharT c, size_type pos = 0) const noexcept { return m_view.find_first_of(c, pos); }
constexpr size_type find_first_of(const CharT* s, size_type pos, size_type count) const {
return m_view.find_first_of(s, pos, count);
}
constexpr size_type find_first_of(const CharT* s, size_type pos = 0) const { return m_view.find_first_of(s, pos); }
constexpr size_type find_last_of(underlying_type v, size_type pos = npos) const noexcept {
return m_view.find_last_of(v, pos);
}
constexpr size_type find_last_of(CharT c, size_type pos = npos) const noexcept { return m_view.find_last_of(c, pos); }
constexpr size_type find_last_of(const CharT* s, size_type pos, size_type count) const {
return m_view.find_last_of(s, pos, count);
}
constexpr size_type find_last_of(const CharT* s, size_type pos = npos) const { return m_view.find_last_of(s, pos); }
constexpr size_type find_first_not_of(underlying_type v, size_type pos = 0) const noexcept {
return m_view.find_first_not_of(v, pos);
}
constexpr size_type find_first_not_of(CharT c, size_type pos = 0) const noexcept {
return m_view.find_first_not_of(c, pos);
}
constexpr size_type find_first_not_of(const CharT* s, size_type pos, size_type count) const {
return m_view.find_first_not_of(s, pos, count);
}
constexpr size_type find_first_not_of(const CharT* s, size_type pos = 0) const {
return m_view.find_first_not_of(s, pos);
}
constexpr size_type find_last_not_of(underlying_type v, size_type pos = npos) const noexcept {
return m_view.find_last_not_of(v, pos);
}
constexpr size_type find_last_not_of(CharT c, size_type pos = npos) const noexcept {
return m_view.find_last_not_of(c, pos);
}
constexpr size_type find_last_not_of(const CharT* s, size_type pos, size_type count) const {
return m_view.find_last_not_of(s, pos, count);
}
constexpr size_type find_last_not_of(const CharT* s, size_type pos = npos) const {
return m_view.find_last_not_of(s, pos);
}
constexpr const underlying_type& view() const noexcept { return m_view; }
constexpr operator underlying_type() const noexcept { return m_view; }
private:
// Private constructor, called by the string literal operators
constexpr basic_zstring_view(const CharT* s, size_type count) : m_view{s, count} {}
// Private constructor, needed by suffix()
explicit constexpr basic_zstring_view(const underlying_type& v) noexcept : m_view{v} {}
underlying_type m_view;
};
template <typename CharT, typename Traits>
constexpr bool operator==(basic_zstring_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs.view() == rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator==(basic_zstring_view<CharT, Traits> lhs, std::basic_string_view<CharT, Traits> rhs) noexcept {
return lhs.view() == rhs;
}
template <typename CharT, typename Traits>
constexpr bool operator==(std::basic_string_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs == rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator!=(basic_zstring_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs.view() != rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator!=(basic_zstring_view<CharT, Traits> lhs, std::basic_string_view<CharT, Traits> rhs) noexcept {
return lhs.view() != rhs;
}
template <typename CharT, typename Traits>
constexpr bool operator!=(std::basic_string_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs != rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator<(basic_zstring_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs.view() < rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator<(basic_zstring_view<CharT, Traits> lhs, std::basic_string_view<CharT, Traits> rhs) noexcept {
return lhs.view() < rhs;
}
template <typename CharT, typename Traits>
constexpr bool operator<(std::basic_string_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs < rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator<=(basic_zstring_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs.view() <= rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator<=(basic_zstring_view<CharT, Traits> lhs, std::basic_string_view<CharT, Traits> rhs) noexcept {
return lhs.view() <= rhs;
}
template <typename CharT, typename Traits>
constexpr bool operator<=(std::basic_string_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs <= rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator>(basic_zstring_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs.view() > rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator>(basic_zstring_view<CharT, Traits> lhs, std::basic_string_view<CharT, Traits> rhs) noexcept {
return lhs.view() > rhs;
}
template <typename CharT, typename Traits>
constexpr bool operator>(std::basic_string_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs > rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator>=(basic_zstring_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs.view() >= rhs.view();
}
template <typename CharT, typename Traits>
constexpr bool operator>=(basic_zstring_view<CharT, Traits> lhs, std::basic_string_view<CharT, Traits> rhs) noexcept {
return lhs.view() >= rhs;
}
template <typename CharT, typename Traits>
constexpr bool operator>=(std::basic_string_view<CharT, Traits> lhs, basic_zstring_view<CharT, Traits> rhs) noexcept {
return lhs >= rhs.view();
}
template <typename CharT, typename Traits>
auto operator<<(std::basic_ostream<CharT, Traits>& os, basic_zstring_view<CharT, Traits> v) -> decltype(os) {
return os << v.view();
}
inline namespace literals {
inline namespace zstring_view_literals {
constexpr zstring_view operator""_zsv(const char* str, std::size_t len) noexcept {
return zstring_view::_INTERNAL_unsafe_make_from_string_range(str, len);
}
constexpr u16zstring_view operator""_zsv(const char16_t* str, std::size_t len) noexcept {
return u16zstring_view::_INTERNAL_unsafe_make_from_string_range(str, len);
}
constexpr u32zstring_view operator""_zsv(const char32_t* str, std::size_t len) noexcept {
return u32zstring_view::_INTERNAL_unsafe_make_from_string_range(str, len);
}
constexpr wzstring_view operator""_zsv(const wchar_t* str, std::size_t len) noexcept {
return wzstring_view::_INTERNAL_unsafe_make_from_string_range(str, len);
}
} // namespace zstring_view_literals
} // namespace literals
} // namespace aurora } // namespace aurora

View File

@ -1,17 +1,185 @@
#include <SDL.h>
#include <aurora/aurora.hpp> #include <aurora/aurora.hpp>
#include <logvisor/logvisor.hpp>
#include <memory> #include <memory>
#include <dawn/native/DawnNative.h>
#ifdef AURORA_ENABLE_VULKAN
#include <SDL_vulkan.h>
#include <dawn/native/VulkanBackend.h>
#endif
#ifdef AURORA_ENABLE_OPENGL
#include <SDL_opengl.h>
#include <dawn/native/OpenGLBackend.h>
#endif
#include <dawn/webgpu_cpp.h>
#include <magic_enum.hpp>
namespace aurora { namespace aurora {
void app_run(std::unique_ptr<AppDelegate> app, Icon icon) noexcept {} static logvisor::Module Log("aurora");
// SDL
static SDL_Window* g_Window;
// Dawn / WebGPU
#ifdef AURORA_ENABLE_VULKAN
static wgpu::BackendType backendType = wgpu::BackendType::Vulkan;
#elif AURORA_ENABLE_METAL
static wgpu::BackendType backendType = wgpu::BackendType::Metal;
#else
static wgpu::BackendType backendType = wgpu::BackendType::OpenGL;
#endif
static wgpu::SwapChain g_SwapChain;
static DawnSwapChainImplementation g_SwapChainImpl;
static wgpu::Queue g_Queue;
static wgpu::Device g_Device;
static wgpu::TextureFormat g_SwapChainFormat;
static void set_window_icon(Icon icon) noexcept {
SDL_Surface* iconSurface = SDL_CreateRGBSurfaceFrom(icon.data.get(), icon.width, icon.height, 32, 4 * icon.width,
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
#else
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
#endif
);
if (iconSurface == nullptr) {
Log.report(logvisor::Fatal, FMT_STRING("Failed to create icon surface: {}"), SDL_GetError());
}
SDL_SetWindowIcon(g_Window, iconSurface);
SDL_FreeSurface(iconSurface);
}
static bool poll_events() noexcept {
SDL_Event event;
while (SDL_PollEvent(&event) != 0) {
switch (event.type) {
case SDL_QUIT:
Log.report(logvisor::Info, FMT_STRING("Received quit request"));
return false;
}
// TODO why doesn't this work?
const auto typedEvent = magic_enum::enum_cast<SDL_EventType>(event.type);
if (typedEvent) {
Log.report(logvisor::Info, FMT_STRING("Received SDL event: {}"), magic_enum::enum_name(typedEvent.value()));
} else {
Log.report(logvisor::Info, FMT_STRING("Received SDL event: {}"), event.type);
}
}
return true;
}
void app_run(std::unique_ptr<AppDelegate> app, Icon icon) noexcept {
Log.report(logvisor::Info, FMT_STRING("Creating Dawn instance"));
auto instance = std::make_unique<dawn::native::Instance>();
instance->DiscoverDefaultAdapters();
dawn::native::Adapter backendAdapter;
{
std::vector<dawn::native::Adapter> adapters = instance->GetAdapters();
auto adapterIt = std::find_if(adapters.begin(), adapters.end(), [](const dawn::native::Adapter adapter) -> bool {
wgpu::AdapterProperties properties;
adapter.GetProperties(&properties);
return properties.backendType == backendType;
});
if (adapterIt == adapters.end()) {
Log.report(logvisor::Fatal, FMT_STRING("Failed to find usable graphics backend"));
}
backendAdapter = *adapterIt;
}
wgpu::AdapterProperties adapterProperties;
backendAdapter.GetProperties(&adapterProperties);
const auto backendName = magic_enum::enum_name(adapterProperties.backendType);
Log.report(logvisor::Info, FMT_STRING("Using {} graphics backend"), backendName);
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
Log.report(logvisor::Fatal, FMT_STRING("Error initializing SDL: {}"), SDL_GetError());
}
Uint32 flags = SDL_WINDOW_SHOWN;
switch (adapterProperties.backendType) {
#ifdef AURORA_ENABLE_VULKAN
case wgpu::BackendType::Vulkan:
flags |= SDL_WINDOW_VULKAN;
break;
#endif
#ifdef AURORA_ENABLE_METAL
case wgpu::BackendType::Metal:
flags |= SDL_WINDOW_METAL;
break;
#endif
#ifdef AURORA_ENABLE_OPENGL
case wgpu::BackendType::OpenGL:
flags |= SDL_WINDOW_OPENGL;
break;
#endif
default:
break;
}
g_Window = SDL_CreateWindow("Metaforce", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, flags);
if (g_Window == nullptr) {
Log.report(logvisor::Fatal, FMT_STRING("Error creating window: {}"), SDL_GetError());
}
set_window_icon(std::move(icon));
g_Device = wgpu::Device::Acquire(backendAdapter.CreateDevice());
switch (adapterProperties.backendType) {
#ifdef AURORA_ENABLE_VULKAN
case wgpu::BackendType::Vulkan: {
VkSurfaceKHR surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(g_Window, dawn::native::vulkan::GetInstance(g_Device.Get()), &surface) != SDL_TRUE) {
Log.report(logvisor::Fatal, FMT_STRING("Failed to create Vulkan surface: {}"), SDL_GetError());
}
g_SwapChainImpl = dawn::native::vulkan::CreateNativeSwapChainImpl(g_Device.Get(), surface);
g_SwapChainFormat =
static_cast<wgpu::TextureFormat>(dawn::native::vulkan::GetNativeSwapChainPreferredFormat(&g_SwapChainImpl));
break;
}
#endif
#ifdef AURORA_ENABLE_METAL
case wgpu::BackendType::Metal: {
// TODO
g_SwapChainFormat = WGPUTextureFormat_BGRA8Unorm;
break;
}
#endif
#ifdef AURORA_ENABLE_OPENGL
case wgpu::BackendType::OpenGL: {
g_SwapChainImpl = dawn::native::opengl::CreateNativeSwapChainImpl(
g_Device.Get(), [](void* userdata) { SDL_GL_SwapWindow(static_cast<SDL_Window*>(userdata)); }, g_Window);
g_SwapChainFormat =
static_cast<wgpu::TextureFormat>(dawn::native::opengl::GetNativeSwapChainPreferredFormat(&g_SwapChainImpl));
break;
}
#endif
default:
Log.report(logvisor::Fatal, FMT_STRING("Unsupported backend {}"), backendName);
}
g_Queue = g_Device.GetQueue();
{
wgpu::SwapChainDescriptor descriptor{};
descriptor.implementation = reinterpret_cast<uint64_t>(&g_SwapChainImpl);
g_SwapChain = g_Device.CreateSwapChain(nullptr, &descriptor);
}
{
int width, height;
SDL_GetWindowSize(g_Window, &width, &height);
g_SwapChain.Configure(g_SwapChainFormat, wgpu::TextureUsage::RenderAttachment, width, height);
}
while (poll_events()) {}
SDL_DestroyWindow(g_Window);
SDL_Quit();
}
std::vector<std::string> get_args() noexcept { std::vector<std::string> get_args() noexcept {
return {}; // TODO return {}; // TODO
} }
WindowSize get_window_size() noexcept { WindowSize get_window_size() noexcept {
return {}; // TODO return {}; // TODO
} }
void set_window_title(std::string_view title) noexcept { void set_window_title(zstring_view title) noexcept { SDL_SetWindowTitle(g_Window, title.c_str()); }
// TODO
}
Backend get_backend() noexcept { Backend get_backend() noexcept {
return Backend::Vulkan; // TODO return Backend::Vulkan; // TODO
} }