From 963f83b7828b76b253b64415a9e6023ee359c2ad Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 26 Jan 2024 19:54:05 +0100 Subject: [PATCH] feat: created render context --- src/main.c | 115 +++----------------------- src/render_context.c | 187 +++++++++++++++++++++++++++++++++++++++++++ src/render_context.h | 38 +++++++++ 3 files changed, 234 insertions(+), 106 deletions(-) create mode 100644 src/render_context.c create mode 100644 src/render_context.h diff --git a/src/main.c b/src/main.c index aa6ce99..799261e 100644 --- a/src/main.c +++ b/src/main.c @@ -1,14 +1,10 @@ #include -#include -#include #include -#include #include #include -#include -#include "list.h" #include "debug.h" +#include "render_context.h" #define APPNAME "VulkanApp" const char* appName = APPNAME; @@ -16,126 +12,33 @@ const VkApplicationInfo appInfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = APPNAME, .applicationVersion = VK_MAKE_VERSION(0, 0, 1), - .pEngineName = "No Engine", + .pEngineName = APPNAME" Engine", .engineVersion = VK_MAKE_VERSION(0, 0, 1), .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) { - vkDestroyInstance(context->instance, NULL); - SDL_DestroyWindow(context->window); +void Quit(RenderContext *context) { + RenderContextDestroy(context); SDL_Quit(); exit(0); } -void loop(AppContext* context) { +void Loop(RenderContext *context) { SDL_Event event; while(1) { SDL_PollEvent(&event); 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[]) { if(SDL_Init(SDL_INIT_VIDEO) != 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", SDL_GetError()); + LOG_ERROR("%s", SDL_GetError()); abort(); } - AppContext context = { - .window = NULL, - .instance = VK_NULL_HANDLE, - .device = VK_NULL_HANDLE, - }; - create_window(&context); - create_instance(&context); - select_physical_device(&context); - find_queue_families(&context); - loop(&context); + RenderContext *context = RenderContextCreate(&appInfo); + Loop(context); return -1; } diff --git a/src/render_context.c b/src/render_context.c new file mode 100644 index 0000000..1080ff4 --- /dev/null +++ b/src/render_context.c @@ -0,0 +1,187 @@ +#include "render_context.h" +#include +#include +#include +#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); +} diff --git a/src/render_context.h b/src/render_context.h new file mode 100644 index 0000000..407c0e7 --- /dev/null +++ b/src/render_context.h @@ -0,0 +1,38 @@ +#ifndef RENDER_CONTEXT_H +#define RENDER_CONTEXT_H + +#include +#include +#include +#include +#include + +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