feat: created render context
parent
40be131fdc
commit
963f83b782
115
src/main.c
115
src/main.c
|
@ -1,14 +1,10 @@
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_log.h>
|
|
||||||
#include <SDL2/SDL_video.h>
|
|
||||||
#include <SDL2/SDL_vulkan.h>
|
#include <SDL2/SDL_vulkan.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
#include <vulkan/vulkan_core.h>
|
|
||||||
|
|
||||||
#include "list.h"
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "render_context.h"
|
||||||
|
|
||||||
#define APPNAME "VulkanApp"
|
#define APPNAME "VulkanApp"
|
||||||
const char* appName = APPNAME;
|
const char* appName = APPNAME;
|
||||||
|
@ -16,126 +12,33 @@ const VkApplicationInfo appInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||||
.pApplicationName = APPNAME,
|
.pApplicationName = APPNAME,
|
||||||
.applicationVersion = VK_MAKE_VERSION(0, 0, 1),
|
.applicationVersion = VK_MAKE_VERSION(0, 0, 1),
|
||||||
.pEngineName = "No Engine",
|
.pEngineName = APPNAME" Engine",
|
||||||
.engineVersion = VK_MAKE_VERSION(0, 0, 1),
|
.engineVersion = VK_MAKE_VERSION(0, 0, 1),
|
||||||
.apiVersion = VK_API_VERSION_1_0,
|
.apiVersion = VK_API_VERSION_1_0,
|
||||||
|
|
||||||
.pNext = NULL
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct VkContext {
|
|
||||||
SDL_Window* window;
|
|
||||||
VkInstance instance;
|
|
||||||
VkPhysicalDevice device;
|
|
||||||
uint32_t queueFamilyIndexes;
|
|
||||||
uint32_t queueFamilyCount;
|
|
||||||
} AppContext;
|
|
||||||
|
|
||||||
void quit(AppContext* context) {
|
void Quit(RenderContext *context) {
|
||||||
vkDestroyInstance(context->instance, NULL);
|
RenderContextDestroy(context);
|
||||||
SDL_DestroyWindow(context->window);
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(AppContext* context) {
|
void Loop(RenderContext *context) {
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while(1) {
|
while(1) {
|
||||||
SDL_PollEvent(&event);
|
SDL_PollEvent(&event);
|
||||||
if(event.type == SDL_QUIT)
|
if(event.type == SDL_QUIT)
|
||||||
quit(context);
|
Quit(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_extensions(AppContext* context, List* out) {
|
|
||||||
unsigned extCount = 10;
|
|
||||||
const char* vkExtensions[10];
|
|
||||||
if(!SDL_Vulkan_GetInstanceExtensions(context->window, &extCount, vkExtensions)) {
|
|
||||||
LOG_WARNING("Failed to fetch vulkan instance extensions '%s'", SDL_GetError());
|
|
||||||
}
|
|
||||||
LOG_INFO("Fetched %u vulkan extensions", extCount);
|
|
||||||
for(unsigned i = 0; i < extCount; ++i)
|
|
||||||
list_add(out, vkExtensions + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_instance(AppContext* context) {
|
|
||||||
List extensions = list_from_type(char*);
|
|
||||||
get_extensions(context, &extensions);
|
|
||||||
VkInstanceCreateInfo createInfo = {
|
|
||||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
|
||||||
.pNext = NULL,
|
|
||||||
.flags = 0,
|
|
||||||
.pApplicationInfo = &appInfo,
|
|
||||||
.enabledLayerCount = 0,
|
|
||||||
.ppEnabledLayerNames = NULL,
|
|
||||||
.enabledExtensionCount = extensions.len,
|
|
||||||
.ppEnabledExtensionNames = extensions.data,
|
|
||||||
};
|
|
||||||
if(vkCreateInstance(&createInfo, NULL, &context->instance) != VK_SUCCESS) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
list_empty(&extensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_window(AppContext* context) {
|
|
||||||
context->window = SDL_CreateWindow(
|
|
||||||
appName,
|
|
||||||
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
|
|
||||||
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
|
|
||||||
1920, 1080,
|
|
||||||
SDL_WINDOW_VULKAN | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_SHOWN
|
|
||||||
);
|
|
||||||
if(!context->window) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int gpu_usable(VkPhysicalDevice device) {
|
|
||||||
VkPhysicalDeviceProperties devProps;
|
|
||||||
vkGetPhysicalDeviceProperties(device, &devProps);
|
|
||||||
VkPhysicalDeviceFeatures devFeats;
|
|
||||||
vkGetPhysicalDeviceFeatures(device, &devFeats);
|
|
||||||
return (devProps.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
|
|
||||||
&& (devFeats.geometryShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
void select_physical_device(AppContext* context) {
|
|
||||||
uint32_t numDevices = 0;
|
|
||||||
vkEnumeratePhysicalDevices(context->instance, &numDevices, NULL);
|
|
||||||
ASSERT_RETURN(numDevices != 0,, "Could not find any vulkan graphics devices");
|
|
||||||
List devices = list_from_type(numDevices);
|
|
||||||
list_reserve(&devices, numDevices);
|
|
||||||
devices.len = numDevices;
|
|
||||||
vkEnumeratePhysicalDevices(context->instance, &numDevices, devices.data);
|
|
||||||
list_foreach(VkPhysicalDevice*, device, &devices) {
|
|
||||||
if(gpu_usable(*device)) {
|
|
||||||
context->device = *device;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list_empty(&devices);
|
|
||||||
ASSERT_RETURN(context->device != VK_NULL_HANDLE,, "Could not find suitable graphics device");
|
|
||||||
}
|
|
||||||
|
|
||||||
void find_queue_families(AppContext* context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if(SDL_Init(SDL_INIT_VIDEO) != 0) {
|
if(SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError());
|
LOG_ERROR("%s", SDL_GetError());
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
AppContext context = {
|
RenderContext *context = RenderContextCreate(&appInfo);
|
||||||
.window = NULL,
|
Loop(context);
|
||||||
.instance = VK_NULL_HANDLE,
|
|
||||||
.device = VK_NULL_HANDLE,
|
|
||||||
};
|
|
||||||
create_window(&context);
|
|
||||||
create_instance(&context);
|
|
||||||
select_physical_device(&context);
|
|
||||||
find_queue_families(&context);
|
|
||||||
loop(&context);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
#include "render_context.h"
|
||||||
|
#include <SDL2/SDL_vulkan.h>
|
||||||
|
#include <SDL2/SDL_video.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#include "debug.h"
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
// get the list of supported vulkan protocol extensions
|
||||||
|
static List RenderContextGetExtensions(const RenderContext *self) {
|
||||||
|
// get the number of available extensions
|
||||||
|
unsigned extCount = 0;
|
||||||
|
SDL_Vulkan_GetInstanceExtensions(self->window, &extCount, 0);
|
||||||
|
List extensions = list_from_type_with_len(char*, extCount);
|
||||||
|
if(!SDL_Vulkan_GetInstanceExtensions(self->window, &extCount, extensions.data)) {
|
||||||
|
LOG_WARNING("Failed to fetch vulkan instance extensions '%s'", SDL_GetError());
|
||||||
|
}
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns 1 if the GPU reference by device is usable for this application
|
||||||
|
// 0 otherwise
|
||||||
|
static int GpuIsUsable(VkPhysicalDevice device) {
|
||||||
|
// get properties and features
|
||||||
|
VkPhysicalDeviceProperties properties;
|
||||||
|
VkPhysicalDeviceFeatures features;
|
||||||
|
vkGetPhysicalDeviceProperties(device, &properties);
|
||||||
|
vkGetPhysicalDeviceFeatures(device, &features);
|
||||||
|
// integrated or discrete works
|
||||||
|
return (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
|
||||||
|
// needs geometry shader support
|
||||||
|
&& (features.geometryShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the SDL window for this application
|
||||||
|
// initializes self->window
|
||||||
|
static void RenderContextInitSdlWindow(RenderContext* self, const VkApplicationInfo *appinfo) {
|
||||||
|
self->window = SDL_CreateWindow(appinfo->pApplicationName,
|
||||||
|
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
|
||||||
|
SDL_WINDOWPOS_CENTERED_DISPLAY(0),
|
||||||
|
1920, 1080,
|
||||||
|
SDL_WINDOW_VULKAN | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_SHOWN);
|
||||||
|
if(!self->window) {
|
||||||
|
LOG_ERROR("Failed to create window: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a vulkan instance
|
||||||
|
// initializes self->vulkanInstance
|
||||||
|
static void RenderContextInitVulkanInstance(RenderContext *self, const VkApplicationInfo *appinfo) {
|
||||||
|
List extensions = RenderContextGetExtensions(self);
|
||||||
|
VkInstanceCreateInfo createInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||||
|
.pApplicationInfo = appinfo,
|
||||||
|
.enabledExtensionCount = extensions.len,
|
||||||
|
.ppEnabledExtensionNames = extensions.data,
|
||||||
|
};
|
||||||
|
if(vkCreateInstance(&createInfo, NULL, &self->vulkanInstance) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("Failed to initialize vulkan instance");
|
||||||
|
}
|
||||||
|
list_empty(&extensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// select a suitable physical vulkan device for rendering
|
||||||
|
static void RenderContextSelectPhysicalDevice(RenderContext *self) {
|
||||||
|
uint32_t deviceCount = 0; // number of devices available
|
||||||
|
vkEnumeratePhysicalDevices(self->vulkanInstance, &deviceCount, NULL);
|
||||||
|
ASSERT_RETURN(deviceCount != 0,, "Could not find any vulkan graphics device");
|
||||||
|
// available devices
|
||||||
|
List devices = list_with_len(sizeof(VkPhysicalDevice), deviceCount);
|
||||||
|
vkEnumeratePhysicalDevices(self->vulkanInstance, &deviceCount, devices.data);
|
||||||
|
// search through the available devices for one suitable to our usecase
|
||||||
|
list_foreach(VkPhysicalDevice*, device, &devices) {
|
||||||
|
if(GpuIsUsable(*device)) {
|
||||||
|
self->physicalDevice = *device;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list_empty(&devices);
|
||||||
|
ASSERT_RETURN(self->physicalDevice != VK_NULL_HANDLE,, "Could not find a suitable graphics device");
|
||||||
|
}
|
||||||
|
|
||||||
|
static QueueFamilyIndexes RenderContextFindQueueFamilies(RenderContext *self) {
|
||||||
|
uint32_t familyCount = 0;
|
||||||
|
QueueFamilyIndexes out = {0};
|
||||||
|
// fetch number of available queue families
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(self->physicalDevice, &familyCount, NULL);
|
||||||
|
// set graphics and present indexes to the number of queues
|
||||||
|
out.graphicsQueue =
|
||||||
|
out.presentQueue = familyCount;
|
||||||
|
List queueFamilies = list_with_len(sizeof(VkQueueFamilyProperties), familyCount);
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(self->physicalDevice, &familyCount, queueFamilies.data);
|
||||||
|
// check through all available queues to find ones that are relevant to this application
|
||||||
|
for(uint32_t i = 0; i < queueFamilies.len; ++i) {
|
||||||
|
VkQueueFamilyProperties *family = list_at(&queueFamilies, i);
|
||||||
|
VkBool32 supportsSurface = VK_FALSE;
|
||||||
|
vkGetPhysicalDeviceSurfaceSupportKHR(self->physicalDevice, i, self->windowSurface, &supportsSurface);
|
||||||
|
// we need a present queue
|
||||||
|
if(supportsSurface)
|
||||||
|
out.presentQueue = i;
|
||||||
|
// we need a graphics queue
|
||||||
|
if((family->queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
|
||||||
|
out.graphicsQueue = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert correctly fetched device queues
|
||||||
|
ASSERT_RETURN(out.presentQueue != familyCount, out, "Could not find present queue");
|
||||||
|
ASSERT_RETURN(out.graphicsQueue != familyCount, out, "Could not find graphics queue");
|
||||||
|
// if assertions pass, that means we have out fully initialized
|
||||||
|
out.init = 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a list of create info structs for creation of vulkan queues from QueueFamilyIndexes struct
|
||||||
|
// return list may be shorter than indexes as identical queues are filtered
|
||||||
|
// DOES NOT assign pQueuePriorities, the caller is responsible for initializing it.
|
||||||
|
static List QueueCreateInfoListFrom(QueueFamilyIndexes indexes) {
|
||||||
|
// return empty list if indexes is not initialized
|
||||||
|
if(!indexes.init)
|
||||||
|
return list_from_type(VkDeviceQueueCreateInfo);
|
||||||
|
// filter duplicates from indexes
|
||||||
|
List uniqueFamilies = list_from_type(uint32_t);
|
||||||
|
for(size_t i = 0; i < QueueFamilyIndexes_Count; ++i) {
|
||||||
|
if(!list_contains(&uniqueFamilies, indexes.as_array + i)) {
|
||||||
|
list_add(&uniqueFamilies, indexes.as_array + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// compose list of create infos without priories assigned
|
||||||
|
List createInfo_ = list_from_type_with_len(VkDeviceQueueCreateInfo, uniqueFamilies.len);
|
||||||
|
// for simplicities sake get the direct pointers to underlying arrays
|
||||||
|
VkDeviceQueueCreateInfo* createInfo = createInfo_.data;
|
||||||
|
uint32_t* families = uniqueFamilies.data;
|
||||||
|
for(size_t i = 0; i < uniqueFamilies.len; ++i) {
|
||||||
|
createInfo[i] = (VkDeviceQueueCreateInfo) {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||||
|
.queueFamilyIndex = families[0],
|
||||||
|
.queueCount = 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return createInfo_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderContextCreateLogicalDevice(RenderContext *self) {
|
||||||
|
// fetch relevant queue families
|
||||||
|
QueueFamilyIndexes queueFamilies = RenderContextFindQueueFamilies(self);
|
||||||
|
// convert queue families to queue creation infos
|
||||||
|
List queueCreateInfo = QueueCreateInfoListFrom(queueFamilies);
|
||||||
|
// assign priorities
|
||||||
|
const float priorities = 1.f;
|
||||||
|
list_foreach(VkDeviceQueueCreateInfo*, info, &queueCreateInfo)
|
||||||
|
info->pQueuePriorities = &priorities;
|
||||||
|
VkDeviceCreateInfo createInfo = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||||
|
.pQueueCreateInfos = queueCreateInfo.data,
|
||||||
|
.queueCreateInfoCount = queueCreateInfo.len,
|
||||||
|
};
|
||||||
|
VkPhysicalDeviceFeatures features = {0};
|
||||||
|
if(vkCreateDevice(self->physicalDevice, &createInfo, NULL, &self->device) != VK_SUCCESS) {
|
||||||
|
LOG_ERROR("Failed to create vulkan logical device");
|
||||||
|
}
|
||||||
|
list_empty(&queueCreateInfo);
|
||||||
|
vkGetDeviceQueue(self->device, queueFamilies.graphicsQueue, 0, &self->graphicsQueue);
|
||||||
|
vkGetDeviceQueue(self->device, queueFamilies.presentQueue, 0, &self->presentQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderContextCreateSurface(RenderContext *self) {
|
||||||
|
if(SDL_Vulkan_CreateSurface(self->window, self->vulkanInstance, &self->windowSurface) == SDL_FALSE) {
|
||||||
|
LOG_ERROR("Failed to create vulkan surface");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderContext *RenderContextCreate(const VkApplicationInfo *appinfo) {
|
||||||
|
RenderContext* self = malloc(sizeof(RenderContext));
|
||||||
|
memset(self, 0x0, sizeof(RenderContext));
|
||||||
|
RenderContextInitSdlWindow(self, appinfo);
|
||||||
|
RenderContextInitVulkanInstance(self, appinfo);
|
||||||
|
RenderContextSelectPhysicalDevice(self);
|
||||||
|
RenderContextCreateSurface(self);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderContextDestroy(RenderContext *self) {
|
||||||
|
vkDestroySurfaceKHR(self->vulkanInstance, self->windowSurface, NULL);
|
||||||
|
vkDestroyDevice(self->device, NULL);
|
||||||
|
vkDestroyInstance(self->vulkanInstance, NULL);
|
||||||
|
SDL_DestroyWindow(self->window);
|
||||||
|
free(self);
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef RENDER_CONTEXT_H
|
||||||
|
#define RENDER_CONTEXT_H
|
||||||
|
|
||||||
|
#include <SDL2/SDL_vulkan.h>
|
||||||
|
#include <SDL2/SDL_video.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
|
||||||
|
typedef struct RenderContext {
|
||||||
|
SDL_Window *window;
|
||||||
|
VkInstance vulkanInstance;
|
||||||
|
VkPhysicalDevice physicalDevice;
|
||||||
|
VkDevice device;
|
||||||
|
VkSurfaceKHR windowSurface;
|
||||||
|
|
||||||
|
VkQueue graphicsQueue;
|
||||||
|
VkQueue presentQueue;
|
||||||
|
} RenderContext;
|
||||||
|
|
||||||
|
// number of queue family indexes, used for QueueFamilyIndexes.as_array
|
||||||
|
#define QueueFamilyIndexes_Count 2
|
||||||
|
typedef struct QueueFamilyIndexes {
|
||||||
|
// should be 0 if any of the queue indexes are not initialized/invalid
|
||||||
|
unsigned init;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint32_t graphicsQueue;
|
||||||
|
uint32_t presentQueue;
|
||||||
|
};
|
||||||
|
uint32_t as_array[QueueFamilyIndexes_Count];
|
||||||
|
};
|
||||||
|
} QueueFamilyIndexes;
|
||||||
|
|
||||||
|
RenderContext *RenderContextCreate(const VkApplicationInfo *appinfo);
|
||||||
|
void RenderContextDestroy(RenderContext *self);
|
||||||
|
|
||||||
|
#endif // !RENDER_CONTEXT_H
|
Loading…
Reference in New Issue