Archived
1
Fork 0

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:
Joshua Goins 2022-02-21 11:03:34 -05:00
parent f0a72530b7
commit a3704eb7a9
10 changed files with 238 additions and 64 deletions

View file

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

View file

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

View file

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

View file

@ -0,0 +1,9 @@
#pragma once
enum class GFXContext {
None,
Metal,
OpenGL,
DirectX,
Vulkan
};

View file

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

View file

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

View file

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

View file

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

View file

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

View 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;
}