Properly seperated/cleaned up OpenGL and Vulkan implementations.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include <thread>
|
||||
#include <fmt/base.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <mijin/detect.hpp>
|
||||
#include <mijin/debug/assert.hpp>
|
||||
#include <mijin/platform/folders.hpp>
|
||||
@@ -62,6 +63,12 @@ int stbiEof(void* user)
|
||||
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
|
||||
return stream.isAtEnd();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr std::uint32_t vkMakeApiVersion(std::uint32_t variant, std::uint32_t major, std::uint32_t minor, std::uint32_t patch) noexcept
|
||||
{
|
||||
return ((variant << 29U) | (major << 22U) | (minor << 12U) | patch);
|
||||
}
|
||||
}
|
||||
|
||||
int Application::run(int argc, char** argv)
|
||||
@@ -114,15 +121,26 @@ int Application::run(int argc, char** argv)
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
case GraphicsAPI::OPENGL:
|
||||
gl.ClearColor(0.3f, 0.3f, 0.3f, 1.f);
|
||||
gl.Clear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
break;
|
||||
case GraphicsAPI::VULKAN:
|
||||
MIJIN_TRAP(); // TODO!
|
||||
break;
|
||||
}
|
||||
|
||||
if (imguiIO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||
{
|
||||
ImGui::UpdatePlatformWindows();
|
||||
ImGui::RenderPlatformWindowsDefault();
|
||||
SDL_GL_MakeCurrent(mWindow, mGLContext);
|
||||
|
||||
if (mConfig.graphicsApi == GraphicsAPI::OPENGL) {
|
||||
SDL_GL_MakeCurrent(mWindow, gl.context);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(mWindow);
|
||||
@@ -223,23 +241,60 @@ ImTextureID Application::getOrLoadTexture(fs::path path)
|
||||
}
|
||||
|
||||
// create texture
|
||||
GLuint texture = 0;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
ImTextureID texture = 0;
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
case GraphicsAPI::OPENGL:
|
||||
{
|
||||
GLuint glTexture = 0;
|
||||
gl.GenTextures(1, &glTexture);
|
||||
gl.BindTexture(GL_TEXTURE_2D, glTexture);
|
||||
|
||||
// setup texture
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
// setup texture
|
||||
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// upload image
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
|
||||
// upload image
|
||||
gl.PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
|
||||
|
||||
mTextures.emplace(std::move(path), texture);
|
||||
texture = glTexture;
|
||||
break;
|
||||
}
|
||||
case GraphicsAPI::VULKAN:
|
||||
MIJIN_TRAP(); // TODO!
|
||||
break;
|
||||
}
|
||||
|
||||
if (texture != 0) {
|
||||
mTextures.emplace(std::move(path), texture);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void Application::destroyTexture(ImTextureID texture)
|
||||
{
|
||||
auto it = std::ranges::find_if(mTextures, [texture](const auto& entry) {
|
||||
return entry.second == texture;
|
||||
});
|
||||
MIJIN_ASSERT(it != mTextures.end(), "Invalid texture id.");
|
||||
mTextures.erase(it);
|
||||
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
case GraphicsAPI::OPENGL:
|
||||
{
|
||||
const GLuint asUint = static_cast<GLuint>(texture);
|
||||
gl.DeleteTextures(1, &asUint);
|
||||
break;
|
||||
}
|
||||
case GraphicsAPI::VULKAN:
|
||||
MIJIN_TRAP(); // TODO
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::configureImgui()
|
||||
{
|
||||
ImGuiIO& imguiIO = ImGui::GetIO();
|
||||
@@ -379,9 +434,19 @@ bool Application::init()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!initGL())
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
return false;
|
||||
case GraphicsAPI::OPENGL:
|
||||
if (!initOpenGL())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GraphicsAPI::VULKAN:
|
||||
if (!initVulkan())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!initImGui())
|
||||
{
|
||||
@@ -407,14 +472,29 @@ void Application::cleanup()
|
||||
}
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
for (const auto& [path, texture] : mTextures)
|
||||
{
|
||||
const GLuint asUint = static_cast<GLuint>(texture);
|
||||
glDeleteTextures(1, &asUint);
|
||||
for (const auto& [_, texture] : mTextures) {
|
||||
destroyTexture(texture);
|
||||
}
|
||||
if (mGLContext != nullptr)
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
SDL_GL_DestroyContext(mGLContext);
|
||||
case GraphicsAPI::OPENGL:
|
||||
if (gl.context != nullptr)
|
||||
{
|
||||
SDL_GL_DestroyContext(gl.context);
|
||||
}
|
||||
break;
|
||||
case GraphicsAPI::VULKAN:
|
||||
if (vk.device != nullptr)
|
||||
{
|
||||
vk.DeviceWaitIdle(vk.device);
|
||||
vk.DestroyDevice(vk.device, nullptr);
|
||||
}
|
||||
if (vk.instance != nullptr)
|
||||
{
|
||||
vk.DestroyInstance(vk.instance, nullptr);
|
||||
}
|
||||
SDL_Vulkan_UnloadLibrary();
|
||||
break;
|
||||
}
|
||||
if (mWindow != nullptr)
|
||||
{
|
||||
@@ -438,48 +518,146 @@ bool Application::initSDL()
|
||||
|
||||
msgInfo("SDL video driver: {}", SDL_GetCurrentVideoDriver());
|
||||
|
||||
// GL attributes must be set before window creation
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_WindowFlags windowFlags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
case GraphicsAPI::OPENGL:
|
||||
// GL attributes must be set before window creation
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
// TODO: not sure if these really make sense, but they are in the example
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
// TODO: not sure if these really make sense, but they are in the example
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
windowFlags |= SDL_WINDOW_OPENGL;
|
||||
break;
|
||||
case GraphicsAPI::VULKAN:
|
||||
if (!SDL_Vulkan_LoadLibrary(nullptr))
|
||||
{
|
||||
msgError("Error loading Vulkan library: {}.", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
windowFlags |= SDL_WINDOW_VULKAN;
|
||||
break;
|
||||
}
|
||||
|
||||
const SDL_WindowFlags WINDOW_FLAGS = 0
|
||||
| SDL_WINDOW_OPENGL
|
||||
| SDL_WINDOW_RESIZABLE
|
||||
| SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
mWindow = SDL_CreateWindow(
|
||||
/* title = */ getWindowTitle().c_str(),
|
||||
/* w = */ 1280,
|
||||
/* h = */ 720,
|
||||
/* flags = */ WINDOW_FLAGS
|
||||
/* flags = */ windowFlags
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::initGL()
|
||||
bool Application::initOpenGL()
|
||||
{
|
||||
mGLContext = SDL_GL_CreateContext(mWindow);
|
||||
gl.context = SDL_GL_CreateContext(mWindow);
|
||||
if (mWindow == nullptr)
|
||||
{
|
||||
msgError("Error creating SDL window: {}.", SDL_GetError());
|
||||
msgError("Error creating OpenGL context: {}.", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_GL_MakeCurrent(mWindow, mGLContext);
|
||||
SDL_GL_MakeCurrent(mWindow, gl.context);
|
||||
SDL_GL_SetSwapInterval(1); // enable vsync, at least for now
|
||||
|
||||
glClear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
|
||||
glClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
|
||||
glGenTextures = reinterpret_cast<glGenTextures_fn_t>(SDL_GL_GetProcAddress("glGenTextures"));
|
||||
glBindTexture = reinterpret_cast<glBindTexture_fn_t>(SDL_GL_GetProcAddress("glBindTexture"));
|
||||
glTexParameteri = reinterpret_cast<glTexParameteri_fn_t>(SDL_GL_GetProcAddress("glTexParameteri"));
|
||||
glPixelStorei = reinterpret_cast<glPixelStorei_fn_t>(SDL_GL_GetProcAddress("glPixelStorei"));
|
||||
glTexImage2D = reinterpret_cast<glTexImage2D_fn_t>(SDL_GL_GetProcAddress("glTexImage2D"));
|
||||
glDeleteTextures = reinterpret_cast<glDeleteTextures_fn_t>(SDL_GL_GetProcAddress("glDeleteTextures"));
|
||||
gl.Clear = reinterpret_cast<glClear_fn_t>(SDL_GL_GetProcAddress("glClear"));
|
||||
gl.ClearColor = reinterpret_cast<glClearColor_fn_t>(SDL_GL_GetProcAddress("glClearColor"));
|
||||
gl.GenTextures = reinterpret_cast<glGenTextures_fn_t>(SDL_GL_GetProcAddress("glGenTextures"));
|
||||
gl.BindTexture = reinterpret_cast<glBindTexture_fn_t>(SDL_GL_GetProcAddress("glBindTexture"));
|
||||
gl.TexParameteri = reinterpret_cast<glTexParameteri_fn_t>(SDL_GL_GetProcAddress("glTexParameteri"));
|
||||
gl.PixelStorei = reinterpret_cast<glPixelStorei_fn_t>(SDL_GL_GetProcAddress("glPixelStorei"));
|
||||
gl.TexImage2D = reinterpret_cast<glTexImage2D_fn_t>(SDL_GL_GetProcAddress("glTexImage2D"));
|
||||
gl.DeleteTextures = reinterpret_cast<glDeleteTextures_fn_t>(SDL_GL_GetProcAddress("glDeleteTextures"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::initVulkan()
|
||||
{
|
||||
vk.GetInstanceProc = reinterpret_cast<vkGetInstanceProcAddr_fn_t>(SDL_Vulkan_GetVkGetInstanceProcAddr());
|
||||
vk.CreateInstance = reinterpret_cast<vkCreateInstance_fn_t>(vk.GetInstanceProc(nullptr, "vkCreateInstance"));
|
||||
|
||||
const VkApplicationInfo applicationInfo = {
|
||||
.apiVersion = vkMakeApiVersion(0, 1, 3, 0) // TODO: probably should let the user specify this?
|
||||
};
|
||||
const VkInstanceCreateInfo instanceCreateInfo = {
|
||||
.pApplicationInfo = &applicationInfo
|
||||
};
|
||||
|
||||
if (const VkResult result = vk.CreateInstance(&instanceCreateInfo, nullptr, &vk.instance); result != VK_SUCCESS)
|
||||
{
|
||||
msgError("Error creating Vulkan instance: {:x}.", static_cast<unsigned>(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
vk.DestroyInstance = reinterpret_cast<vkDestroyInstance_fn_t>(vk.GetInstanceProc(vk.instance, "vkDestroyInstance"));
|
||||
vk.EnumeratePhysicalDevices = reinterpret_cast<vkEnumeratePhysicalDevices_fn_t>(vk.GetInstanceProc(vk.instance, "vkEnumeratePhysicalDevices"));
|
||||
vk.GetPhysicalDeviceQueueFamilyProperties2 = reinterpret_cast<vkGetPhysicalDeviceQueueFamilyProperties2_fn_t>(vk.GetInstanceProc(vk.instance, "vkGetPhysicalDeviceQueueFamilyProperties2"));
|
||||
vk.CreateDevice = reinterpret_cast<vkCreateDevice_fn_t>(vk.GetInstanceProc(vk.instance, "vkCreateDevice"));
|
||||
vk.DestroyDevice = reinterpret_cast<vkDestroyDevice_fn_t>(vk.GetInstanceProc(vk.instance, "vkDestroyDevice"));
|
||||
vk.DeviceWaitIdle = reinterpret_cast<vkDeviceWaitIdle_fn_t>(vk.GetInstanceProc(vk.instance, "vkDeviceWaitIdle"));
|
||||
vk.GetDeviceQueue = reinterpret_cast<vkGetDeviceQueue_fn_t>(vk.GetInstanceProc(vk.instance, "vkGetDeviceQueue"));
|
||||
|
||||
// TODO: this is really cheap... (but should be sufficient in most cases)
|
||||
std::uint32_t physicalDeviceCount = 1;
|
||||
if (const VkResult result = vk.EnumeratePhysicalDevices(vk.instance, &physicalDeviceCount, &vk.physicalDevice); result != VK_SUCCESS)
|
||||
{
|
||||
msgError("Error enumerating Vulkan physical devices: {:x}.", static_cast<unsigned>(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t queueFamilyPropertyCount = 0;
|
||||
vk.GetPhysicalDeviceQueueFamilyProperties2(vk.physicalDevice, &queueFamilyPropertyCount, nullptr);
|
||||
|
||||
std::vector<VkQueueFamilyProperties2> queueFamilyProperties;
|
||||
queueFamilyProperties.resize(queueFamilyPropertyCount);
|
||||
vk.GetPhysicalDeviceQueueFamilyProperties2(vk.physicalDevice, &queueFamilyPropertyCount, queueFamilyProperties.data());
|
||||
|
||||
// TODO: this should also check for surface support (but who cares?)
|
||||
std::uint32_t queueFamilyIndex = 0;
|
||||
for (; queueFamilyIndex < queueFamilyPropertyCount; ++queueFamilyIndex)
|
||||
{
|
||||
if (queueFamilyProperties[queueFamilyIndex].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (queueFamilyIndex == queueFamilyPropertyCount)
|
||||
{
|
||||
msgError("No suitable Vulkan queue family found.");
|
||||
return false;
|
||||
}
|
||||
vk.queueFamilyIndex = queueFamilyIndex;
|
||||
|
||||
static const float queuePriority = 1.0f;
|
||||
const VkDeviceQueueCreateInfo queueCreateInfo = {
|
||||
.queueFamilyIndex = vk.queueFamilyIndex,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &queuePriority
|
||||
};
|
||||
static const std::array DEVICE_EXTENSIONS = {
|
||||
"VK_KHR_surface"
|
||||
};
|
||||
const VkDeviceCreateInfo deviceCreateInfo = {
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &queueCreateInfo,
|
||||
.enabledExtensionCount = static_cast<std::uint32_t>(DEVICE_EXTENSIONS.size()),
|
||||
.ppEnabledExtensionNames = DEVICE_EXTENSIONS.data()
|
||||
};
|
||||
if (const VkResult result = vk.CreateDevice(vk.physicalDevice, &deviceCreateInfo, nullptr, &vk.device); result != VK_SUCCESS)
|
||||
{
|
||||
msgError("Error creating Vulkan device: {:x}.", static_cast<unsigned>(result));
|
||||
return false;
|
||||
}
|
||||
vk.GetDeviceQueue(vk.device, /* queueFamilyIndex = */ 0, /* queueIndex = */ 0, &vk.queue);
|
||||
|
||||
if (!SDL_Vulkan_CreateSurface(mWindow, vk.instance, nullptr, &vk.surface))
|
||||
{
|
||||
msgError("Error creating SDL Vulkan surface: {}.", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -511,14 +689,22 @@ bool Application::initImGui()
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
// init the backends
|
||||
if (!ImGui_ImplSDL3_InitForOpenGL(mWindow, mGLContext))
|
||||
switch (mConfig.graphicsApi)
|
||||
{
|
||||
msgError("Error initializing ImGui SDL3 backend.");
|
||||
return false;
|
||||
}
|
||||
if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION))
|
||||
{
|
||||
msgError("Error initializing ImGui OpenGL3 backend.");
|
||||
case GraphicsAPI::OPENGL:
|
||||
if (!ImGui_ImplSDL3_InitForOpenGL(mWindow, gl.context))
|
||||
{
|
||||
msgError("Error initializing ImGui SDL3 backend.");
|
||||
return false;
|
||||
}
|
||||
if (!ImGui_ImplOpenGL3_Init(IMGUI_GLSL_VERSION))
|
||||
{
|
||||
msgError("Error initializing ImGui OpenGL3 backend.");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case GraphicsAPI::VULKAN:
|
||||
MIJIN_TRAP(); // TODO!
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user