diff options
| author | Clément Sibille <clements@lisible.xyz> | 2025-03-16 19:13:09 +0100 |
|---|---|---|
| committer | Clément Sibille <clements@lisible.xyz> | 2025-03-18 21:25:10 +0100 |
| commit | cb74ffacedb3c1bc4a5398d665d1f4dc12f69a4a (patch) | |
| tree | 23c91e34fd0a5dafb47beac7e7e0c6f6bd26457f /src | |
| parent | 038c0c44d26a45554e3cc6257bc67d3fecb291cd (diff) | |
Add the renderer's foundations
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.c | 10 | ||||
| -rw-r--r-- | src/platform.h | 26 | ||||
| -rw-r--r-- | src/platform_sdl.c | 17 | ||||
| -rw-r--r-- | src/renderer.c | 934 | ||||
| -rw-r--r-- | src/renderer.h | 27 |
5 files changed, 1014 insertions, 0 deletions
@@ -1,5 +1,6 @@ #include "log.h" #include "platform.h" +#include "renderer.h" int main(void) { struct vgltf_platform platform = {}; @@ -8,6 +9,12 @@ int main(void) { goto err; } + struct vgltf_renderer renderer = {}; + if (!vgltf_renderer_init(&renderer, &platform)) { + VGLTF_LOG_ERR("Couldn't initialize the renderer"); + goto deinit_platform; + } + while (true) { struct vgltf_event event; while (vgltf_platform_poll_event(&platform, &event)) { @@ -20,8 +27,11 @@ int main(void) { } out_main_loop: + vgltf_renderer_deinit(&renderer); vgltf_platform_deinit(&platform); return 0; +deinit_platform: + vgltf_platform_deinit(&platform); err: return 1; } diff --git a/src/platform.h b/src/platform.h index baf0165..2f8bc19 100644 --- a/src/platform.h +++ b/src/platform.h @@ -1,6 +1,16 @@ #ifndef VGLTF_PLATFORM_H #define VGLTF_PLATFORM_H +#include "log.h" +#include <stdint.h> +#include <stdlib.h> + +#define VGLTF_PANIC(...) \ + do { \ + VGLTF_LOG_ERR("panic: " __VA_ARGS__); \ + exit(1); \ + } while (0) + enum vgltf_event_type { VGLTF_EVENT_QUIT, VGLTF_EVENT_KEY_DOWN, @@ -49,11 +59,27 @@ struct vgltf_event { }; }; +struct vgltf_window_size { + int width; + int height; +}; + struct vgltf_platform; bool vgltf_platform_init(struct vgltf_platform *platform); void vgltf_platform_deinit(struct vgltf_platform *platform); bool vgltf_platform_poll_event(struct vgltf_platform *platform, struct vgltf_event *event); +bool vgltf_platform_get_window_size(struct vgltf_platform *platform, + struct vgltf_window_size *window_size); + +// Vulkan specifics +#include "vulkan/vulkan_core.h" +char const *const * +vgltf_platform_get_vulkan_instance_extensions(struct vgltf_platform *platform, + uint32_t *count); +bool vgltf_platform_create_vulkan_surface(struct vgltf_platform *platform, + VkInstance instance, + VkSurfaceKHR *surface); #include "platform_sdl.h" diff --git a/src/platform_sdl.c b/src/platform_sdl.c index 27ed4c3..281ccb5 100644 --- a/src/platform_sdl.c +++ b/src/platform_sdl.c @@ -1,6 +1,7 @@ #include "log.h" #include "platform.h" #include "platform_sdl.h" +#include <SDL3/SDL_vulkan.h> bool vgltf_platform_init(struct vgltf_platform *platform) { if (!SDL_Init(SDL_INIT_VIDEO)) { @@ -111,3 +112,19 @@ bool vgltf_platform_poll_event(struct vgltf_platform *platform, } return pending_events; } +bool vgltf_platform_get_window_size(struct vgltf_platform *platform, + struct vgltf_window_size *window_size) { + return SDL_GetWindowSize(platform->window, &window_size->width, + &window_size->height); +} +char const *const * +vgltf_platform_get_vulkan_instance_extensions(struct vgltf_platform *platform, + uint32_t *count) { + (void)platform; + return SDL_Vulkan_GetInstanceExtensions(count); +} +bool vgltf_platform_create_vulkan_surface(struct vgltf_platform *platform, + VkInstance instance, + VkSurfaceKHR *surface) { + return SDL_Vulkan_CreateSurface(platform->window, instance, nullptr, surface); +} diff --git a/src/renderer.c b/src/renderer.c new file mode 100644 index 0000000..cacd3bd --- /dev/null +++ b/src/renderer.c @@ -0,0 +1,934 @@ +#include "log.h" +#include "renderer.h" +#include "src/platform.h" +#include "vulkan/vulkan_core.h" +#include <assert.h> + +static const char *VALIDATION_LAYERS[] = {"VK_LAYER_KHRONOS_validation"}; +static constexpr int VALIDATION_LAYER_COUNT = + sizeof(VALIDATION_LAYERS) / sizeof(VALIDATION_LAYERS[0]); + +#ifdef VGLTF_DEBUG +static constexpr bool enable_validation_layers = true; +#else +static constexpr bool enable_validation_layers = false; +#endif + +static VKAPI_ATTR VkBool32 VKAPI_CALL +debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagBitsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT *callback_data, + void *user_data) { + (void)message_severity; + (void)message_type; + (void)user_data; + VGLTF_LOG_DBG("validation layer: %s", callback_data->pMessage); + return VK_FALSE; +} + +static constexpr int REQUIRED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY = 10; +struct required_instance_extensions { + const char *extensions[REQUIRED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY]; + uint32_t count; +}; +void required_instance_extensions_push( + struct required_instance_extensions *required_instance_extensions, + const char *required_instance_extension) { + if (required_instance_extensions->count == + REQUIRED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY) { + VGLTF_PANIC("required instance extensions array is full"); + } + required_instance_extensions + ->extensions[required_instance_extensions->count++] = + required_instance_extension; +} + +static constexpr int SUPPORTED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY = 128; +struct supported_instance_extensions { + VkExtensionProperties + properties[SUPPORTED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY]; + uint32_t count; +}; +bool supported_instance_extensions_init( + struct supported_instance_extensions *supported_instance_extensions) { + if (vkEnumerateInstanceExtensionProperties( + nullptr, &supported_instance_extensions->count, nullptr) != + VK_SUCCESS) { + goto err; + } + + if (supported_instance_extensions->count > + SUPPORTED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY) { + VGLTF_LOG_ERR("supported instance extensions array cannot fit all the " + "VkExtensionProperties"); + goto err; + } + + if (vkEnumerateInstanceExtensionProperties( + nullptr, &supported_instance_extensions->count, + supported_instance_extensions->properties) != VK_SUCCESS) { + goto err; + } + return true; +err: + return false; +} +void supported_instance_extensions_debug_print( + const struct supported_instance_extensions *supported_instance_extensions) { + VGLTF_LOG_DBG("Supported instance extensions:"); + for (uint32_t i = 0; i < supported_instance_extensions->count; i++) { + VGLTF_LOG_DBG("\t- %s", + supported_instance_extensions->properties[i].extensionName); + } +} +bool supported_instance_extensions_includes( + const struct supported_instance_extensions *supported_instance_extensions, + const char *extension_name) { + for (uint32_t supported_instance_extension_index = 0; + supported_instance_extension_index < + supported_instance_extensions->count; + supported_instance_extension_index++) { + const VkExtensionProperties *extension_properties = + &supported_instance_extensions + ->properties[supported_instance_extension_index]; + if (strcmp(extension_properties->extensionName, extension_name) == 0) { + return true; + } + } + + return false; +} + +static constexpr uint32_t SUPPORTED_VALIDATION_LAYERS_ARRAY_CAPACITY = 64; +struct supported_validation_layers { + VkLayerProperties properties[SUPPORTED_VALIDATION_LAYERS_ARRAY_CAPACITY]; + uint32_t count; +}; +bool supported_validation_layers_init( + struct supported_validation_layers *supported_validation_layers) { + if (vkEnumerateInstanceLayerProperties(&supported_validation_layers->count, + nullptr) != VK_SUCCESS) { + goto err; + } + + if (supported_validation_layers->count > + SUPPORTED_VALIDATION_LAYERS_ARRAY_CAPACITY) { + VGLTF_LOG_ERR("supported validation layers array cannot fit all the " + "VkLayerProperties"); + goto err; + } + + if (vkEnumerateInstanceLayerProperties( + &supported_validation_layers->count, + supported_validation_layers->properties) != VK_SUCCESS) { + goto err; + } + + return true; +err: + return false; +} + +static bool are_validation_layer_supported() { + struct supported_validation_layers supported_layers = {}; + if (!supported_validation_layers_init(&supported_layers)) { + goto err; + } + + for (int requested_layer_index = 0; + requested_layer_index < VALIDATION_LAYER_COUNT; + requested_layer_index++) { + const char *requested_layer_name = VALIDATION_LAYERS[requested_layer_index]; + bool requested_layer_found = false; + for (uint32_t supported_layer_index = 0; + supported_layer_index < supported_layers.count; + supported_layer_index++) { + VkLayerProperties *supported_layer = + &supported_layers.properties[supported_layer_index]; + if (strcmp(requested_layer_name, supported_layer->layerName) == 0) { + requested_layer_found = true; + break; + } + } + + if (!requested_layer_found) { + goto err; + } + } + + return true; +err: + return false; +} + +static bool fetch_required_instance_extensions( + struct required_instance_extensions *required_extensions, + struct vgltf_platform *platform) { + struct supported_instance_extensions supported_extensions = {}; + if (!supported_instance_extensions_init(&supported_extensions)) { + VGLTF_LOG_ERR( + "Couldn't fetch supported instance extensions details (OOM?)"); + goto err; + } + supported_instance_extensions_debug_print(&supported_extensions); + + uint32_t platform_required_extension_count = 0; + const char *const *platform_required_extensions = + vgltf_platform_get_vulkan_instance_extensions( + platform, &platform_required_extension_count); + for (uint32_t platform_required_extension_index = 0; + platform_required_extension_index < platform_required_extension_count; + platform_required_extension_index++) { + required_instance_extensions_push( + required_extensions, + platform_required_extensions[platform_required_extension_index]); + } + required_instance_extensions_push( + required_extensions, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + + if (enable_validation_layers) { + required_instance_extensions_push(required_extensions, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + bool all_extensions_supported = true; + for (uint32_t required_extension_index = 0; + required_extension_index < required_extensions->count; + required_extension_index++) { + const char *required_extension_name = + required_extensions->extensions[required_extension_index]; + if (!supported_instance_extensions_includes(&supported_extensions, + required_extension_name)) { + VGLTF_LOG_ERR("Unsupported instance extension: %s", + required_extension_name); + all_extensions_supported = false; + } + } + + if (!all_extensions_supported) { + VGLTF_LOG_ERR("Some required extensions are unsupported."); + goto err; + } + + return true; +err: + return false; +} + +static void populate_debug_messenger_create_info( + VkDebugUtilsMessengerCreateInfoEXT *create_info) { + *create_info = (VkDebugUtilsMessengerCreateInfoEXT){}; + create_info->sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + create_info->messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + create_info->messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + create_info->pfnUserCallback = debug_callback; +} + +static bool vgltf_renderer_create_instance(struct vgltf_renderer *renderer, + struct vgltf_platform *platform) { + VGLTF_LOG_INFO("Creating vulkan instance..."); + if (enable_validation_layers && !are_validation_layer_supported()) { + VGLTF_LOG_ERR("Requested validation layers aren't supported"); + goto err; + } + + VkApplicationInfo application_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Visible GLTF", + .applicationVersion = VK_MAKE_VERSION(0, 1, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_2}; + + struct required_instance_extensions required_extensions = {}; + fetch_required_instance_extensions(&required_extensions, platform); + + VkInstanceCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &application_info, + .enabledExtensionCount = required_extensions.count, + .ppEnabledExtensionNames = required_extensions.extensions, + .flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR}; + + VkDebugUtilsMessengerCreateInfoEXT debug_create_info; + if (enable_validation_layers) { + create_info.enabledLayerCount = VALIDATION_LAYER_COUNT; + create_info.ppEnabledLayerNames = VALIDATION_LAYERS; + populate_debug_messenger_create_info(&debug_create_info); + create_info.pNext = &debug_create_info; + } + + if (vkCreateInstance(&create_info, nullptr, &renderer->instance) != + VK_SUCCESS) { + VGLTF_LOG_ERR("Failed to create VkInstance"); + goto err; + } + + return true; +err: + return false; +} + +static VkResult create_debug_utils_messenger_ext( + VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *create_info, + const VkAllocationCallbacks *allocator, + VkDebugUtilsMessengerEXT *debug_messenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, create_info, allocator, debug_messenger); + } + + return VK_ERROR_EXTENSION_NOT_PRESENT; +} + +static void +destroy_debug_utils_messenger_ext(VkInstance instance, + VkDebugUtilsMessengerEXT debug_messenger, + const VkAllocationCallbacks *allocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debug_messenger, allocator); + } +} + +static void +vgltf_renderer_setup_debug_messenger(struct vgltf_renderer *renderer) { + if (!enable_validation_layers) + return; + VkDebugUtilsMessengerCreateInfoEXT create_info; + populate_debug_messenger_create_info(&create_info); + create_debug_utils_messenger_ext(renderer->instance, &create_info, nullptr, + &renderer->debug_messenger); +} + +static constexpr int AVAILABLE_PHYSICAL_DEVICE_ARRAY_CAPACITY = 128; +struct available_physical_devices { + VkPhysicalDevice devices[AVAILABLE_PHYSICAL_DEVICE_ARRAY_CAPACITY]; + uint32_t count; +}; +static bool +available_physical_devices_init(VkInstance instance, + struct available_physical_devices *devices) { + + if (vkEnumeratePhysicalDevices(instance, &devices->count, nullptr) != + VK_SUCCESS) { + VGLTF_LOG_ERR("Couldn't enumerate physical devices"); + goto err; + } + + if (devices->count == 0) { + VGLTF_LOG_ERR("Failed to find any GPU with Vulkan support"); + goto err; + } + + if (devices->count > AVAILABLE_PHYSICAL_DEVICE_ARRAY_CAPACITY) { + VGLTF_LOG_ERR("available physical devices array cannot fit all available " + "physical devices"); + goto err; + } + + if (vkEnumeratePhysicalDevices(instance, &devices->count, devices->devices) != + VK_SUCCESS) { + VGLTF_LOG_ERR("Couldn't enumerate physical devices"); + goto err; + } + + return true; +err: + return false; +} + +struct queue_family_indices { + uint32_t graphics_family; + uint32_t present_family; + bool has_graphics_family; + bool has_present_family; +}; +bool queue_family_indices_is_complete( + const struct queue_family_indices *indices) { + return indices->has_graphics_family && indices->has_present_family; +} +bool queue_family_indices_for_device(struct queue_family_indices *indices, + VkPhysicalDevice device, + VkSurfaceKHR surface) { + static constexpr uint32_t QUEUE_FAMILY_PROPERTIES_ARRAY_CAPACITY = 64; + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, + nullptr); + + if (queue_family_count > QUEUE_FAMILY_PROPERTIES_ARRAY_CAPACITY) { + VGLTF_LOG_ERR( + "Queue family properties array cannot fit all queue family properties"); + goto err; + } + + VkQueueFamilyProperties + queue_family_properties[QUEUE_FAMILY_PROPERTIES_ARRAY_CAPACITY] = {}; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, + queue_family_properties); + + for (uint32_t queue_family_index = 0; queue_family_index < queue_family_count; + queue_family_index++) { + VkQueueFamilyProperties *queue_family = + &queue_family_properties[queue_family_index]; + + VkBool32 present_support; + vkGetPhysicalDeviceSurfaceSupportKHR(device, queue_family_index, surface, + &present_support); + + if (queue_family->queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices->graphics_family = queue_family_index; + indices->has_graphics_family = true; + } + + if (present_support) { + indices->present_family = queue_family_index; + indices->has_present_family = true; + } + + if (queue_family_indices_is_complete(indices)) { + break; + } + } + + return true; +err: + return false; +} + +static bool is_in_array(uint32_t *array, int length, uint32_t value) { + for (int i = 0; i < length; i++) { + if (array[i] == value) { + return true; + } + } + + return false; +} + +static constexpr uint32_t SUPPORTED_EXTENSIONS_ARRAY_CAPACITY = 128; +struct supported_extensions { + VkExtensionProperties properties[SUPPORTED_EXTENSIONS_ARRAY_CAPACITY]; + uint32_t count; +}; +bool supported_extensions_init( + struct supported_extensions *supported_extensions, + VkPhysicalDevice device) { + if (vkEnumerateDeviceExtensionProperties(device, nullptr, + &supported_extensions->count, + nullptr) != VK_SUCCESS) { + goto err; + } + + if (supported_extensions->count > SUPPORTED_EXTENSIONS_ARRAY_CAPACITY) { + VGLTF_LOG_ERR( + "supported extensions aarray cannot fit all the VkExtensionProperties"); + goto err; + } + + if (vkEnumerateDeviceExtensionProperties( + device, nullptr, &supported_extensions->count, + supported_extensions->properties) != VK_SUCCESS) { + goto err; + } + + return true; +err: + return false; +} + +static bool supported_extensions_includes_extension( + struct supported_extensions *supported_extensions, + const char *extension_name) { + for (uint32_t supported_extension_index = 0; + supported_extension_index < supported_extensions->count; + supported_extension_index++) { + if (strcmp(supported_extensions->properties[supported_extension_index] + .extensionName, + extension_name) == 0) { + return true; + } + } + return false; +} + +static const char *DEVICE_EXTENSIONS[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset"}; +static constexpr int DEVICE_EXTENSION_COUNT = + sizeof(DEVICE_EXTENSIONS) / sizeof(DEVICE_EXTENSIONS[0]); +static bool are_device_extensions_supported(VkPhysicalDevice device) { + struct supported_extensions supported_extensions = {}; + if (!supported_extensions_init(&supported_extensions, device)) { + goto err; + } + + for (uint32_t required_extension_index = 0; + required_extension_index < DEVICE_EXTENSION_COUNT; + required_extension_index++) { + if (!supported_extensions_includes_extension( + &supported_extensions, + DEVICE_EXTENSIONS[required_extension_index])) { + VGLTF_LOG_DBG("Unsupported: %s", + DEVICE_EXTENSIONS[required_extension_index]); + goto err; + } + } + + return true; + +err: + return false; +} + +static constexpr int SWAPCHAIN_SUPPORT_DETAILS_MAX_SURFACE_FORMAT_COUNT = 256; +static constexpr int SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT = 256; +struct swapchain_support_details { + VkSurfaceCapabilitiesKHR capabilities; + VkSurfaceFormatKHR + formats[SWAPCHAIN_SUPPORT_DETAILS_MAX_SURFACE_FORMAT_COUNT]; + VkPresentModeKHR + present_modes[SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT]; + uint32_t format_count; + uint32_t present_mode_count; +}; +bool swapchain_support_details_query_from_device( + struct swapchain_support_details *swapchain_support_details, + VkPhysicalDevice device, VkSurfaceKHR surface) { + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + device, surface, &swapchain_support_details->capabilities) != + VK_SUCCESS) { + goto err; + } + + if (vkGetPhysicalDeviceSurfaceFormatsKHR( + device, surface, &swapchain_support_details->format_count, nullptr) != + VK_SUCCESS) { + goto err; + } + + if (swapchain_support_details->format_count != 0 && + swapchain_support_details->format_count < + SWAPCHAIN_SUPPORT_DETAILS_MAX_SURFACE_FORMAT_COUNT) { + if (vkGetPhysicalDeviceSurfaceFormatsKHR( + device, surface, &swapchain_support_details->format_count, + swapchain_support_details->formats) != VK_SUCCESS) { + goto err; + } + } + + if (vkGetPhysicalDeviceSurfacePresentModesKHR( + device, surface, &swapchain_support_details->present_mode_count, + nullptr) != VK_SUCCESS) { + goto err; + } + + if (swapchain_support_details->present_mode_count != 0 && + swapchain_support_details->present_mode_count < + SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT) { + if (vkGetPhysicalDeviceSurfacePresentModesKHR( + device, surface, &swapchain_support_details->present_mode_count, + swapchain_support_details->present_modes) != VK_SUCCESS) { + goto err; + } + } + + return true; +err: + return false; +} + +static bool is_physical_device_suitable(VkPhysicalDevice device, + VkSurfaceKHR surface) { + struct queue_family_indices indices = {}; + queue_family_indices_for_device(&indices, device, surface); + + VGLTF_LOG_DBG("Checking for physical device extension support"); + bool extensions_supported = are_device_extensions_supported(device); + VGLTF_LOG_DBG("Supported: %d", extensions_supported); + + bool swapchain_adequate = false; + if (extensions_supported) { + + VGLTF_LOG_DBG("Checking for swapchain support details"); + struct swapchain_support_details swapchain_support_details = {}; + if (!swapchain_support_details_query_from_device(&swapchain_support_details, + device, surface)) { + VGLTF_LOG_ERR("Couldn't query swapchain support details from device"); + goto err; + } + + swapchain_adequate = swapchain_support_details.format_count > 0 && + swapchain_support_details.present_mode_count > 0; + } + + return queue_family_indices_is_complete(&indices) && extensions_supported && + swapchain_adequate; +err: + return false; +} + +static bool +vgltf_renderer_pick_physical_device(struct vgltf_renderer *renderer) { + VkPhysicalDevice physical_device = VK_NULL_HANDLE; + + struct available_physical_devices available_physical_devices = {}; + if (!available_physical_devices_init(renderer->instance, + &available_physical_devices)) { + VGLTF_LOG_ERR("Couldn't fetch available physical devices"); + goto err; + } + + for (uint32_t available_physical_device_index = 0; + available_physical_device_index < available_physical_devices.count; + available_physical_device_index++) { + VkPhysicalDevice available_physical_device = + available_physical_devices.devices[available_physical_device_index]; + if (is_physical_device_suitable(available_physical_device, + renderer->surface)) { + physical_device = available_physical_device; + break; + } + } + + if (physical_device == VK_NULL_HANDLE) { + VGLTF_LOG_ERR("Failed to find a suitable GPU"); + goto err; + } + + renderer->physical_device = physical_device; + + return true; +err: + return false; +} + +static bool +vgltf_renderer_create_logical_device(struct vgltf_renderer *renderer) { + struct queue_family_indices queue_family_indices = {}; + queue_family_indices_for_device(&queue_family_indices, + renderer->physical_device, renderer->surface); + static constexpr int MAX_QUEUE_FAMILY_COUNT = 2; + + uint32_t unique_queue_families[MAX_QUEUE_FAMILY_COUNT] = {}; + int unique_queue_family_count = 0; + + if (!is_in_array(unique_queue_families, unique_queue_family_count, + queue_family_indices.graphics_family)) { + assert(unique_queue_family_count < MAX_QUEUE_FAMILY_COUNT); + unique_queue_families[unique_queue_family_count++] = + queue_family_indices.graphics_family; + } + if (!is_in_array(unique_queue_families, unique_queue_family_count, + queue_family_indices.present_family)) { + assert(unique_queue_family_count < MAX_QUEUE_FAMILY_COUNT); + unique_queue_families[unique_queue_family_count++] = + queue_family_indices.present_family; + } + + float queue_priority = 1.f; + VkDeviceQueueCreateInfo queue_create_infos[MAX_QUEUE_FAMILY_COUNT] = {}; + int queue_create_info_count = 0; + for (int unique_queue_family_index = 0; + unique_queue_family_index < unique_queue_family_count; + unique_queue_family_index++) { + queue_create_infos[queue_create_info_count++] = (VkDeviceQueueCreateInfo){ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = unique_queue_families[unique_queue_family_index], + .queueCount = 1, + .pQueuePriorities = &queue_priority}; + } + + VkPhysicalDeviceFeatures device_features = {}; + VkDeviceCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = queue_create_infos, + .queueCreateInfoCount = queue_create_info_count, + .pEnabledFeatures = &device_features, + .ppEnabledExtensionNames = DEVICE_EXTENSIONS, + .enabledExtensionCount = DEVICE_EXTENSION_COUNT}; + if (vkCreateDevice(renderer->physical_device, &create_info, nullptr, + &renderer->device) != VK_SUCCESS) { + VGLTF_LOG_ERR("Failed to create logical device"); + goto err; + } + + vkGetDeviceQueue(renderer->device, queue_family_indices.graphics_family, 0, + &renderer->graphics_queue); + vkGetDeviceQueue(renderer->device, queue_family_indices.present_family, 0, + &renderer->present_queue); + + return true; +err: + return false; +} + +static bool vgltf_renderer_create_surface(struct vgltf_renderer *renderer, + struct vgltf_platform *platform) { + if (!vgltf_platform_create_vulkan_surface(platform, renderer->instance, + &renderer->surface)) { + VGLTF_LOG_ERR("Couldn't create surface"); + goto err; + } + + return true; +err: + return false; +} + +static VkSurfaceFormatKHR +choose_swapchain_surface_format(VkSurfaceFormatKHR *available_formats, + uint32_t available_format_count) { + for (uint32_t available_format_index = 0; + available_format_index < available_format_count; + available_format_index++) { + VkSurfaceFormatKHR *available_format = + &available_formats[available_format_index]; + if (available_format->format == VK_FORMAT_B8G8R8A8_SRGB && + available_format->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return *available_format; + } + } + + return available_formats[0]; +} + +static VkPresentModeKHR +choose_swapchain_present_mode(VkPresentModeKHR *available_modes, + uint32_t available_mode_count) { + for (uint32_t available_mode_index = 0; + available_mode_index < available_mode_count; available_mode_index++) { + VkPresentModeKHR available_mode = available_modes[available_mode_index]; + if (available_mode == VK_PRESENT_MODE_MAILBOX_KHR) { + return available_mode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +static uint32_t clamp_uint32(uint32_t min, uint32_t max, uint32_t value) { + return value < min ? min : value > max ? max : value; +} + +static VkExtent2D +choose_swapchain_extent(const VkSurfaceCapabilitiesKHR *capabilities, int width, + int height) { + if (capabilities->currentExtent.width != UINT32_MAX) { + return capabilities->currentExtent; + } else { + VkExtent2D actual_extent = {width, height}; + actual_extent.width = + clamp_uint32(capabilities->minImageExtent.width, + capabilities->maxImageExtent.width, actual_extent.width); + actual_extent.height = + clamp_uint32(capabilities->minImageExtent.height, + capabilities->maxImageExtent.height, actual_extent.height); + return actual_extent; + } +} + +static bool vgltf_renderer_create_swapchain(struct vgltf_renderer *renderer, + int window_width_px, + int window_height_px) { + struct swapchain_support_details swapchain_support_details = {}; + swapchain_support_details_query_from_device( + &swapchain_support_details, renderer->physical_device, renderer->surface); + + VkSurfaceFormatKHR surface_format = + choose_swapchain_surface_format(swapchain_support_details.formats, + swapchain_support_details.format_count); + VkPresentModeKHR present_mode = choose_swapchain_present_mode( + swapchain_support_details.present_modes, + swapchain_support_details.present_mode_count); + + VkExtent2D extent = + choose_swapchain_extent(&swapchain_support_details.capabilities, + window_width_px, window_height_px); + uint32_t image_count = + swapchain_support_details.capabilities.minImageCount + 1; + if (swapchain_support_details.capabilities.maxImageCount > 0 && + image_count > swapchain_support_details.capabilities.maxImageCount) { + image_count = swapchain_support_details.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR create_info = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = renderer->surface, + .minImageCount = image_count, + .imageFormat = surface_format.format, + .imageColorSpace = surface_format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT}; + struct queue_family_indices indices = {}; + queue_family_indices_for_device(&indices, renderer->physical_device, + renderer->surface); + uint32_t queue_family_indices[] = {indices.graphics_family, + indices.present_family}; + if (indices.graphics_family != indices.present_family) { + create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + create_info.queueFamilyIndexCount = 2; + create_info.pQueueFamilyIndices = queue_family_indices; + } else { + create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + create_info.preTransform = + swapchain_support_details.capabilities.currentTransform; + create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + create_info.presentMode = present_mode; + create_info.clipped = VK_TRUE; + create_info.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(renderer->device, &create_info, nullptr, + &renderer->swapchain) != VK_SUCCESS) { + VGLTF_LOG_ERR("Swapchain creation failed!"); + goto err; + } + + if (vkGetSwapchainImagesKHR(renderer->device, renderer->swapchain, + &renderer->swapchain_image_count, + nullptr) != VK_SUCCESS) { + VGLTF_LOG_ERR("Couldn't get swapchain image count"); + goto destroy_swapchain; + } + + if (renderer->swapchain_image_count > + VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT) { + VGLTF_LOG_ERR("Swapchain image array cannot fit all %d swapchain images", + renderer->swapchain_image_count); + goto destroy_swapchain; + } + + if (vkGetSwapchainImagesKHR(renderer->device, renderer->swapchain, + &renderer->swapchain_image_count, + renderer->swapchain_images) != VK_SUCCESS) { + VGLTF_LOG_ERR("Couldn't get swapchain images"); + goto destroy_swapchain; + } + + renderer->swapchain_image_format = surface_format.format; + renderer->swapchain_extent = extent; + + return true; +destroy_swapchain: + vkDestroySwapchainKHR(renderer->device, renderer->swapchain, nullptr); +err: + return false; +} + +static bool vgltf_renderer_create_image_views(struct vgltf_renderer *renderer) { + uint32_t swapchain_image_index; + for (swapchain_image_index = 0; + swapchain_image_index < renderer->swapchain_image_count; + swapchain_image_index++) { + VkImage swapchain_image = renderer->swapchain_images[swapchain_image_index]; + + VkImageViewCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = swapchain_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = renderer->swapchain_image_format, + .components = {VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY}, + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1}}; + + if (vkCreateImageView( + renderer->device, &create_info, nullptr, + &renderer->swapchain_image_views[swapchain_image_index]) != + VK_SUCCESS) { + goto err; + } + } + return true; +err: + for (uint32_t to_remove_index = 0; to_remove_index < swapchain_image_index; + to_remove_index++) { + vkDestroyImageView(renderer->device, + renderer->swapchain_image_views[to_remove_index], + nullptr); + } + return false; +} + +bool vgltf_renderer_init(struct vgltf_renderer *renderer, + struct vgltf_platform *platform) { + if (!vgltf_renderer_create_instance(renderer, platform)) { + VGLTF_LOG_ERR("instance creation failed"); + goto err; + } + vgltf_renderer_setup_debug_messenger(renderer); + if (!vgltf_renderer_create_surface(renderer, platform)) { + goto destroy_instance; + } + + if (!vgltf_renderer_pick_physical_device(renderer)) { + VGLTF_LOG_ERR("Couldn't pick physical device"); + goto destroy_surface; + } + if (!vgltf_renderer_create_logical_device(renderer)) { + VGLTF_LOG_ERR("Couldn't create logical device"); + goto destroy_device; + } + + struct vgltf_window_size window_size = {800, 600}; + if (!vgltf_platform_get_window_size(platform, &window_size)) { + VGLTF_LOG_ERR("Couldn't get window size"); + goto destroy_device; + } + + if (!vgltf_renderer_create_swapchain(renderer, window_size.width, + window_size.height)) { + VGLTF_LOG_ERR("Couldn't create swapchain"); + goto destroy_device; + } + + if (!vgltf_renderer_create_image_views(renderer)) { + VGLTF_LOG_ERR("Couldn't create image views"); + goto destroy_swapchain; + } + + return true; +destroy_swapchain: + vkDestroySwapchainKHR(renderer->device, renderer->swapchain, nullptr); +destroy_device: + vkDestroyDevice(renderer->device, nullptr); +destroy_surface: + vkDestroySurfaceKHR(renderer->instance, renderer->surface, nullptr); +destroy_instance: + if (enable_validation_layers) { + destroy_debug_utils_messenger_ext(renderer->instance, + renderer->debug_messenger, nullptr); + } + vkDestroyInstance(renderer->instance, nullptr); +err: + return false; +} +void vgltf_renderer_deinit(struct vgltf_renderer *renderer) { + for (uint32_t swapchain_image_view_index = 0; + swapchain_image_view_index < renderer->swapchain_image_count; + swapchain_image_view_index++) { + vkDestroyImageView( + renderer->device, + renderer->swapchain_image_views[swapchain_image_view_index], nullptr); + } + vkDestroySwapchainKHR(renderer->device, renderer->swapchain, nullptr); + vkDestroyDevice(renderer->device, nullptr); + vkDestroySurfaceKHR(renderer->instance, renderer->surface, nullptr); + if (enable_validation_layers) { + destroy_debug_utils_messenger_ext(renderer->instance, + renderer->debug_messenger, nullptr); + } + vkDestroyInstance(renderer->instance, nullptr); +} diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..5e08762 --- /dev/null +++ b/src/renderer.h @@ -0,0 +1,27 @@ +#ifndef VGLTF_RENDERER_H +#define VGLTF_RENDERER_H + +#include "platform.h" +#include <vulkan/vulkan.h> + +constexpr int VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT = 32; +struct vgltf_renderer { + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + VkQueue graphics_queue; + VkQueue present_queue; + VkDebugUtilsMessengerEXT debug_messenger; + VkSurfaceKHR surface; + VkSwapchainKHR swapchain; + VkImage swapchain_images[VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT]; + VkImageView swapchain_image_views[VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT]; + VkFormat swapchain_image_format; + VkExtent2D swapchain_extent; + uint32_t swapchain_image_count; +}; +bool vgltf_renderer_init(struct vgltf_renderer *renderer, + struct vgltf_platform *platform); +void vgltf_renderer_deinit(struct vgltf_renderer *renderer); + +#endif // VGLTF_RENDERER_H |
