Improve surface and gfx context creation
This is laying some groundwork for a much better way of determining the best GFX api to use at runtime, and making it easier to support more GFX backends in the future.
This commit is contained in:
parent
f0a72530b7
commit
a3704eb7a9
10 changed files with 238 additions and 64 deletions
|
@ -28,6 +28,12 @@ using prism::engine;
|
|||
engine::engine(const int argc, char* argv[]) {
|
||||
log("Prism Engine loading...");
|
||||
|
||||
log("starting gfx debug:");
|
||||
|
||||
for(auto [t, t_name] : magic_enum::enum_entries<GFXContext>()) {
|
||||
log("does platform support {}? {}", t_name, platform::supports_context(t));
|
||||
}
|
||||
|
||||
console::register_command("echo", console::argument_format(1), [](const console::arguments& args) {
|
||||
log(std::get<std::string>(args[0].data));
|
||||
});
|
||||
|
@ -99,6 +105,8 @@ void engine::prepare_quit() {
|
|||
void engine::set_gfx(GFX* p_gfx) {
|
||||
Expects(p_gfx != nullptr);
|
||||
|
||||
prism::log("this gfx has a context of {}", utility::enum_to_string(p_gfx->required_context()));
|
||||
|
||||
this->gfx = p_gfx;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ add_custom_target(GFXInterface SOURCES
|
|||
public/gfx_framebuffer.hpp
|
||||
public/gfx_renderpass.hpp
|
||||
public/gfx_object.hpp
|
||||
public/gfx_sampler.hpp)
|
||||
public/gfx_sampler.hpp
|
||||
public/gfx_context.hpp)
|
||||
set_target_properties(GFXInterface PROPERTIES CMAKE_FOLDER "GFX")
|
||||
|
||||
add_subdirectory(dummy)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "shadercompiler.hpp"
|
||||
#include "platform.hpp"
|
||||
#include "gfx_context.hpp"
|
||||
|
||||
class GFXBuffer;
|
||||
class GFXPipeline;
|
||||
|
@ -280,14 +281,6 @@ struct GFXSamplerCreateInfo {
|
|||
|
||||
using GFXSize = uint64_t;
|
||||
|
||||
enum class GFXContext {
|
||||
None,
|
||||
Metal,
|
||||
OpenGL,
|
||||
DirectX,
|
||||
Vulkan
|
||||
};
|
||||
|
||||
struct GFXCreateInfo {
|
||||
bool api_validation_enabled = false;
|
||||
};
|
||||
|
|
9
engine/gfx/public/gfx_context.hpp
Normal file
9
engine/gfx/public/gfx_context.hpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
enum class GFXContext {
|
||||
None,
|
||||
Metal,
|
||||
OpenGL,
|
||||
DirectX,
|
||||
Vulkan
|
||||
};
|
|
@ -220,7 +220,9 @@ bool GFXVulkan::initialize(const GFXCreateInfo& info) {
|
|||
enabledExtensions.push_back("VK_EXT_debug_utils");
|
||||
}
|
||||
|
||||
auto required_extensions = platform::get_native_surface_extension();
|
||||
auto ctx_info = (vulkan_information*)platform::get_context_information();
|
||||
|
||||
auto required_extensions = ctx_info->surface_extensions;
|
||||
enabledExtensions.insert(enabledExtensions.end(), required_extensions.begin(), required_extensions.end());
|
||||
|
||||
createInstance({}, enabledExtensions);
|
||||
|
@ -1957,7 +1959,11 @@ void GFXVulkan::createLogicalDevice(std::vector<const char*> extensions) {
|
|||
|
||||
void GFXVulkan::createSwapchain(NativeSurface* native_surface, VkSwapchainKHR oldSwapchain) {
|
||||
if(native_surface->surface == VK_NULL_HANDLE) {
|
||||
native_surface->surface = (VkSurfaceKHR)platform::create_native_surface(native_surface->identifier, (void*)instance);
|
||||
auto surface_creation_info = new vulkan_surface_creation_info();
|
||||
surface_creation_info->instance = instance;
|
||||
|
||||
auto vk_surface = (vulkan_surface*)platform::create_surface(native_surface->identifier, surface_creation_info);
|
||||
native_surface->surface = vk_surface->surface;
|
||||
}
|
||||
|
||||
// TODO: fix this pls
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <string_view>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "gfx_context.hpp"
|
||||
|
||||
/// Requestable window flags, which may or may not be respected by the platform.
|
||||
enum class WindowFlags {
|
||||
|
@ -73,6 +74,46 @@ enum class PlatformTheme {
|
|||
Dark
|
||||
};
|
||||
|
||||
// note: a platform may be built with support for a specific context (e.g. ENABLE_VULKAN, ENABLE_DIRECTX, etc)
|
||||
// this only determines it at build-time, not at runtime.
|
||||
#ifdef ENABLE_VULKAN
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
struct vulkan_information {
|
||||
std::vector<const char*> surface_extensions;
|
||||
};
|
||||
|
||||
struct vulkan_surface_creation_info {
|
||||
VkInstance instance;
|
||||
};
|
||||
|
||||
struct vulkan_surface {
|
||||
VkSurfaceKHR surface;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DIRECTX
|
||||
struct directx_surface_creation_info {
|
||||
// TODO: stub
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
#import <Metal/Metal.hpp>
|
||||
|
||||
struct metal_surface_creation_info {
|
||||
MTLDevice device;
|
||||
};
|
||||
|
||||
struct metal_surface {
|
||||
MTL::PixelFormat format;
|
||||
};
|
||||
|
||||
struct metal_next_image {
|
||||
CA::MetalDrawable* next_drawable;
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace platform {
|
||||
using window_ptr = void*;
|
||||
|
||||
|
@ -85,6 +126,28 @@ namespace platform {
|
|||
/// Queries whether or not the platform supports a certain feature.
|
||||
bool supports_feature(PlatformFeature feature);
|
||||
|
||||
/*
|
||||
* This queries whether the context is supported by the platform at build-time. This does not mean the
|
||||
* context can be used properly at runtime, this is handled by GFX::is_supported().
|
||||
*/
|
||||
bool supports_context(GFXContext context);
|
||||
|
||||
/**
|
||||
* This initializes before window creation, for a specific gfx context type. This is designed to be called within
|
||||
* helper classes such as gfx_chooser, but may also be called within a platform implementation as well.
|
||||
* @param context
|
||||
*/
|
||||
void initialize_context(GFXContext context);
|
||||
|
||||
/*
|
||||
* This is used in specific cases where the gfx backend may need to know important information before window creation.
|
||||
* A good example is the surface extension required by a specific window backend, but this is between initialize_context
|
||||
* and create_surface calls.
|
||||
*
|
||||
* the returned structs are in a naming scheme of X_information.
|
||||
*/
|
||||
void* get_context_information();
|
||||
|
||||
/** Opens a new window.
|
||||
@param title The title of the window.
|
||||
@param rect The requested size and position of the window.
|
||||
|
@ -97,10 +160,23 @@ namespace platform {
|
|||
|
||||
bool is_main_window(window_ptr index);
|
||||
|
||||
// for vulkan usage
|
||||
void* create_native_surface(window_ptr window, void* instance);
|
||||
/*
|
||||
* This creates a new surface, with the gfx context type already set ahead of time (see initialize_context)
|
||||
* The parameter 'surface_creation_info' must be a type of the gfx context you want to use, and support must be
|
||||
* built at build-time (ENABLE_VULKAN, gfx context is Vulkan, and the struct would be vulkan_surface_creation_info.)
|
||||
*
|
||||
* The return type is usually {Your GFX Context}_surface, such as vulkan_surface. See each gfx context's struct for
|
||||
* more information.
|
||||
*/
|
||||
void* create_surface(window_ptr window, void* surface_creation_info);
|
||||
|
||||
std::vector<const char*> get_native_surface_extension();
|
||||
/*
|
||||
* This needs to be implemented under certain gfx contexts (such as metal) which calls [layer nextDrawable] underneath
|
||||
* can be no-op for contexts such as vulkan which can happen within it's own API.
|
||||
*
|
||||
* the return type is named as X_next_image. may be null.
|
||||
*/
|
||||
void* get_next_image(window_ptr window);
|
||||
|
||||
/** Closes a window.
|
||||
@param index The window to close.
|
||||
|
@ -177,10 +253,4 @@ namespace platform {
|
|||
void begin_text_input();
|
||||
|
||||
void end_text_input();
|
||||
|
||||
// metal specific stuff
|
||||
#ifdef ENABLE_METAL
|
||||
unsigned int initialize_metal_layer(window_ptr window, void* device);
|
||||
void* get_next_drawable(window_ptr window);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -7,6 +7,10 @@ if(ENABLE_MACOS)
|
|||
set(EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sdl_metal.mm)
|
||||
endif()
|
||||
|
||||
if(ENABLE_VULKAN)
|
||||
set(EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sdl_vulkan.cpp)
|
||||
endif()
|
||||
|
||||
if(TARGET SDL2::Main)
|
||||
set(EXTRA_LIBRARIES SDL2::Main)
|
||||
endif()
|
||||
|
|
|
@ -46,6 +46,15 @@ SDL_Window* get_window_by_sdl_id(const Uint32 id) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
void* create_metal_surface(platform::window_ptr window, void* surface_creation_info);
|
||||
void* get_next_metal_drawable(platform::window_ptr window);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VULKAN
|
||||
void* create_vulkan_surface(platform::window_ptr window, void* surface_creation_info);
|
||||
#endif
|
||||
|
||||
static std::map<InputButton, int> inputToKeyCode = { {
|
||||
{InputButton::C, SDL_SCANCODE_C},
|
||||
{InputButton::V, SDL_SCANCODE_V},
|
||||
|
@ -89,9 +98,8 @@ platform::window_ptr platform::open_window(const std::string_view title, const p
|
|||
auto& win = windows.emplace_back();
|
||||
|
||||
int sdl_flags = SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
#ifndef ENABLE_METAL
|
||||
if(gfx_interface->required_context() == GFXContext::Vulkan)
|
||||
sdl_flags |= SDL_WINDOW_VULKAN;
|
||||
#endif
|
||||
|
||||
if(flags & WindowFlags::Borderless)
|
||||
sdl_flags |= SDL_WINDOW_BORDERLESS;
|
||||
|
@ -128,8 +136,10 @@ platform::window_ptr platform::open_window(const std::string_view title, const p
|
|||
main_window = win;
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
if(gfx_interface->required_context() == GFXContext::Metal) {
|
||||
SDL_Renderer* renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
renderers[win] = renderer;
|
||||
}
|
||||
#endif
|
||||
|
||||
engine->add_window((void*)win, win, {static_cast<uint32_t>(real_width), static_cast<uint32_t>(real_height)});
|
||||
|
@ -306,13 +316,93 @@ void platform::end_text_input() {
|
|||
SDL_StopTextInput();
|
||||
}
|
||||
|
||||
bool platform::supports_context(GFXContext context) {
|
||||
#ifdef ENABLE_VULKAN
|
||||
if(context == GFXContext::Vulkan)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
if(context == GFXContext::Metal)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
if(context == GFXContext::None)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void platform::initialize_context(const GFXContext context) {
|
||||
#ifdef ENABLE_METAL
|
||||
if(context == GFXContext::Metal) {
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void* platform::get_context_information() {
|
||||
#ifdef ENABLE_VULKAN
|
||||
if(gfx_interface->required_context() == GFXContext::Vulkan) {
|
||||
// dummy window
|
||||
auto dummy = SDL_CreateWindow("", 0, 0, 1, 1, SDL_WINDOW_VULKAN);
|
||||
|
||||
unsigned int count = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(dummy, &count, nullptr);
|
||||
|
||||
std::vector<const char*> extensions(count);
|
||||
SDL_Vulkan_GetInstanceExtensions(dummy, &count, extensions.data());
|
||||
|
||||
SDL_DestroyWindow(dummy);
|
||||
|
||||
auto info = new vulkan_information();
|
||||
info->surface_extensions = extensions;
|
||||
|
||||
return (void*)info;
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* platform::create_surface(window_ptr window, void* surface_creation_info) {
|
||||
#ifdef ENABLE_VULKAN
|
||||
if(gfx_interface->required_context() == GFXContext::Vulkan) {
|
||||
return create_vulkan_surface(window, surface_creation_info);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
if(gfx_interface->required_context() == GFXContext::Metal) {
|
||||
return create_metal_surface(window, surface_creation_info);
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* platform::get_next_image(window_ptr window) {
|
||||
#ifdef ENABLE_METAL
|
||||
if(gfx_interface->required_context() == GFXContext::Metal) {
|
||||
return get_next_metal_drawable(window);
|
||||
}
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
|
||||
|
||||
// determine gfx context at the beginning
|
||||
#ifdef ENABLE_METAL
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
|
||||
gfx_interface = new GFXMetal();
|
||||
#else
|
||||
gfx_interface = new GFXVulkan();
|
||||
#endif
|
||||
|
||||
platform::initialize_context(gfx_interface->required_context());
|
||||
|
||||
engine = new prism::engine(argc, argv);
|
||||
|
||||
app = new @APP_CLASS@();
|
||||
|
@ -320,11 +410,6 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
GFXCreateInfo info = {};
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
gfx_interface = new GFXMetal();
|
||||
#else
|
||||
gfx_interface = new GFXVulkan();
|
||||
#endif
|
||||
if(gfx_interface->initialize(info)) {
|
||||
engine->set_gfx(gfx_interface);
|
||||
} else {
|
||||
|
@ -432,28 +517,3 @@ PlatformTheme platform::get_theme() {
|
|||
return PlatformTheme::Light;
|
||||
}
|
||||
#endif
|
||||
|
||||
void* platform::create_native_surface(platform::window_ptr index, void* instance) {
|
||||
auto window = get_window(index);
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
SDL_Vulkan_CreateSurface(window, (VkInstance)instance, &surface);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
std::vector<const char*> platform::get_native_surface_extension() {
|
||||
// dummy window
|
||||
auto dummy = SDL_CreateWindow("", 0, 0, 1, 1, SDL_WINDOW_VULKAN);
|
||||
|
||||
unsigned int count = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(dummy, &count, nullptr);
|
||||
|
||||
std::vector<const char*> extensions(count);
|
||||
SDL_Vulkan_GetInstanceExtensions(dummy, &count, extensions.data());
|
||||
|
||||
SDL_DestroyWindow(dummy);
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
|
|
@ -15,17 +15,22 @@ CAMetalLayer* get_layer(platform::window_ptr index) {
|
|||
return (__bridge CAMetalLayer*)SDL_RenderGetMetalLayer(renderers[get_window(index)]);
|
||||
}
|
||||
|
||||
// instance == MTL::Device
|
||||
unsigned int platform::initialize_metal_layer(platform::window_ptr index, void* device) {
|
||||
metal_surface* create_metal_surface(platform::window_ptr window, void* surface_creation_info) {
|
||||
auto layer = get_layer(index);
|
||||
|
||||
layer.device = (__bridge id<MTLDevice>)device;
|
||||
auto layer = (__bridge CAMetalLayer*)SDL_RenderGetMetalLayer(renderers[get_window(window)]);
|
||||
auto metal_surface_info = (metal_surface_creation_info*)surface_creation_info;
|
||||
|
||||
layer.device = (__bridge id<MTLDevice>)metal_surface_info->device;
|
||||
layer.allowsNextDrawableTimeout = true;
|
||||
|
||||
return (unsigned int)layer.pixelFormat;
|
||||
auto return_surface = new metal_surface();
|
||||
return_surface->format = layer.pixelFormat;
|
||||
|
||||
return return_surface;
|
||||
}
|
||||
|
||||
void* platform::get_next_drawable(window_ptr window) {
|
||||
void* get_next_metal_drawable(platform::window_ptr window) {
|
||||
auto renderer = renderers[get_window(window)];
|
||||
auto layer = get_layer(window);
|
||||
|
||||
|
|
18
platforms/sdl/sdl_vulkan.cpp
Normal file
18
platforms/sdl/sdl_vulkan.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include "platform.hpp"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_vulkan.h>
|
||||
|
||||
extern std::vector<SDL_Window*> windows;
|
||||
|
||||
SDL_Window* get_window(platform::window_ptr index);
|
||||
|
||||
void* create_vulkan_surface(platform::window_ptr window, void* surface_creation_info) {
|
||||
auto vulkan_surface_info = (vulkan_surface_creation_info*)surface_creation_info;
|
||||
auto window_ptr = get_window(window);
|
||||
|
||||
auto vk_surface = new vulkan_surface();
|
||||
SDL_Vulkan_CreateSurface(window_ptr, vulkan_surface_info->instance, &vk_surface->surface);
|
||||
|
||||
return vk_surface;
|
||||
}
|
Reference in a new issue