SBgl 0.1.0
A graphics framework in C99
Loading...
Searching...
No Matches
sbgl_backend_vulkan.c File Reference
#include "core/sbgl_platform.h"
#include "core/sbl_arena.h"
#include "core/sbgl_internal_log.h"
#include "sbgl_graphics_hal.h"
#include <volk.h>
#include <stdio.h>
#include <string.h>
Include dependency graph for sbgl_backend_vulkan.c:

Go to the source code of this file.

Data Structures

struct  SBGL_VulkanBuffer
 
struct  SBGL_VulkanShader
 
struct  SBGL_VulkanPipeline
 
struct  SBGL_VulkanComputePipeline
 
struct  sbgl_GfxMemoryRange
 
struct  sbgl_GfxStaticHeap
 
struct  sbgl_GfxDynamicHeap
 
struct  sbgl_GfxManagedHeap
 
struct  sbgl_GfxContext
 

Macros

#define VK_NO_PROTOTYPES
 
#define SBGL_MAX_FRAMES_IN_FLIGHT   2
 
#define SBGL_MAX_SWAPCHAIN_IMAGES   8
 
#define SBGL_TRANSIENT_BUFFER_SIZE   (16 * 1024 * 1024)
 
#define SBGL_STATIC_HEAP_SIZE   (128 * 1024 * 1024)
 
#define SBGL_DYNAMIC_HEAP_SIZE   (128 * 1024 * 1024)
 
#define SBGL_MANAGED_HEAP_SIZE   (512 * 1024 * 1024)
 
#define SBGL_VK_PUSH_CONSTANT_SIZE   128
 

Enumerations

enum  SBGL_HeapType { SBGL_HEAP_TYPE_STATIC , SBGL_HEAP_TYPE_DYNAMIC , SBGL_HEAP_TYPE_MANAGED }
 

Functions

static void cleanup_swapchain (sbgl_GfxContext *ctx)
 
static bool create_swapchain (sbgl_GfxContext *ctx, sbgl_Window *window)
 
static void recreate_swapchain (sbgl_GfxContext *ctx)
 
static VKAPI_ATTR VkBool32 VKAPI_CALL sbgl_vk_debug_callback (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData)
 
static void sbgl_setup_debug_utils (sbgl_GfxContext *ctx)
 
static VkFormat sbgl_to_vk_format (sbgl_Format format)
 
static uint32_t find_memory_type (sbgl_GfxContext *ctx, uint32_t typeFilter, VkMemoryPropertyFlags properties)
 
static uint32_t static_heap_alloc (sbgl_GfxContext *ctx, size_t size)
 
static uint32_t dynamic_heap_alloc (sbgl_GfxContext *ctx, size_t size)
 
static uint32_t managed_heap_alloc (sbgl_GfxContext *ctx, size_t size)
 
static void managed_heap_free (sbgl_GfxContext *ctx, uint32_t offset)
 
static bool create_heaps (sbgl_GfxContext *ctx)
 
static bool create_instance (sbgl_GfxContext *ctx, bool enableValidation)
 
static bool create_surface (sbgl_GfxContext *ctx, sbgl_Window *window)
 
static bool select_physical_device (sbgl_GfxContext *ctx)
 
static bool create_logical_device (sbgl_GfxContext *ctx)
 
static bool find_depth_format (sbgl_GfxContext *ctx)
 
static bool create_depth_resources (sbgl_GfxContext *ctx)
 
static bool create_sync_and_command (sbgl_GfxContext *ctx)
 
static bool create_telemetry_resources (sbgl_GfxContext *ctx)
 
static bool create_transient_resources (sbgl_GfxContext *ctx)
 
sbgl_GfxContextsbgl_gfx_Init (sbgl_Window *window, struct SblArena *arena, const sbgl_ResourceLimits *limits, bool enableValidation)
 Initializes the graphics backend with configurable resource limits.
 
void sbgl_gfx_Shutdown (sbgl_GfxContext *ctx)
 
bool sbgl_gfx_BeginFrame (sbgl_GfxContext *ctx)
 Starts a new frame, acquiring an image and starting the command buffer.
 
void sbgl_gfx_BeginRenderPass (sbgl_GfxContext *ctx, float r, float g, float b, float a)
 Starts a graphics rendering pass.
 
void sbgl_gfx_EndRenderPass (sbgl_GfxContext *ctx)
 Ends the current graphics rendering pass.
 
void sbgl_gfx_EndFrame (sbgl_GfxContext *ctx)
 Submits the current frame's commands and presents the image.
 
void sbgl_gfx_DeviceWaitIdle (sbgl_GfxContext *ctx)
 
sbgl_Buffer sbgl_gfx_CreateBuffer (sbgl_GfxContext *ctx, sbgl_BufferUsage usage, size_t size, const void *data)
 
void sbgl_gfx_DestroyBuffer (sbgl_GfxContext *ctx, sbgl_Buffer handle)
 
void sbgl_gfx_FillBuffer (sbgl_GfxContext *ctx, sbgl_Buffer handle, size_t offset, size_t size, uint32_t value)
 Performs a hardware-accelerated buffer fill.
 
uint32_t sbgl_gfx_GetFrameIndex (sbgl_GfxContext *ctx)
 Retrieves the current backend frame index.
 
void * sbgl_gfx_MapBuffer (sbgl_GfxContext *ctx, sbgl_Buffer handle)
 
void sbgl_gfx_UnmapBuffer (sbgl_GfxContext *ctx, sbgl_Buffer handle)
 
void sbgl_gfx_DestroyBufferDeferred (sbgl_GfxContext *ctx, sbgl_Buffer handle)
 Marks a buffer for destruction after current frames complete.
 
uint64_t sbgl_gfx_GetBufferDeviceAddress (sbgl_GfxContext *ctx, sbgl_Buffer handle)
 Retrieves the 64-bit GPU virtual address for a buffer.
 
sbgl_Shader sbgl_gfx_LoadShader (sbgl_GfxContext *ctx, sbgl_ShaderStage stage, const uint32_t *bytecode, size_t size)
 
void sbgl_gfx_DestroyShader (sbgl_GfxContext *ctx, sbgl_Shader handle)
 
sbgl_Pipeline sbgl_gfx_CreatePipeline (sbgl_GfxContext *ctx, const sbgl_PipelineConfig *config)
 
void sbgl_gfx_DestroyPipeline (sbgl_GfxContext *ctx, sbgl_Pipeline handle)
 
sbgl_ComputePipeline sbgl_gfx_CreateComputePipeline (sbgl_GfxContext *ctx, sbgl_Shader handle)
 
void sbgl_gfx_DestroyComputePipeline (sbgl_GfxContext *ctx, sbgl_ComputePipeline handle)
 
void sbgl_gfx_BindComputePipeline (sbgl_GfxContext *ctx, sbgl_ComputePipeline handle)
 
void sbgl_gfx_DispatchCompute (sbgl_GfxContext *ctx, uint32_t x, uint32_t y, uint32_t z)
 
void sbgl_gfx_MemoryBarrier (sbgl_GfxContext *ctx, sbgl_BarrierType type)
 
void sbgl_gfx_BindPipeline (sbgl_GfxContext *ctx, sbgl_Pipeline handle)
 
void sbgl_gfx_BindBuffer (sbgl_GfxContext *ctx, sbgl_Buffer handle, sbgl_BufferUsage usage)
 
void sbgl_gfx_Draw (sbgl_GfxContext *ctx, uint32_t vertexCount, uint32_t firstVertex, uint32_t instanceCount)
 
void sbgl_gfx_DrawIndexed (sbgl_GfxContext *ctx, uint32_t indexCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t instanceCount)
 
void sbgl_gfx_DrawIndirect (sbgl_GfxContext *ctx, sbgl_Buffer handle, size_t offset, uint32_t drawCount)
 Submits a batch of draw calls stored in a GPU buffer.
 
sbgl_GfxTransientAllocation sbgl_gfx_AllocateTransient (sbgl_GfxContext *ctx, size_t size, uint32_t alignment)
 Allocates a slice of GPU-visible memory for transient per-frame data.
 
void sbgl_gfx_PushConstants (sbgl_GfxContext *ctx, size_t size, const void *data)
 
float sbgl_gfx_GetGpuTime (sbgl_GfxContext *ctx)
 Retrieves the elapsed GPU time for the previous frame in milliseconds.
 
int32_t sbgl_gfx_GetLastVkResult (sbgl_GfxContext *ctx)
 Retrieves the last VkResult from the backend for error inspection.
 

Variables

static const sbgl_ResourceLimits sbgl_DefaultResourceLimits
 

Macro Definition Documentation

◆ SBGL_DYNAMIC_HEAP_SIZE

#define SBGL_DYNAMIC_HEAP_SIZE   (128 * 1024 * 1024)

Definition at line 22 of file sbgl_backend_vulkan.c.

◆ SBGL_MANAGED_HEAP_SIZE

#define SBGL_MANAGED_HEAP_SIZE   (512 * 1024 * 1024)

Definition at line 23 of file sbgl_backend_vulkan.c.

◆ SBGL_MAX_FRAMES_IN_FLIGHT

#define SBGL_MAX_FRAMES_IN_FLIGHT   2

Definition at line 18 of file sbgl_backend_vulkan.c.

◆ SBGL_MAX_SWAPCHAIN_IMAGES

#define SBGL_MAX_SWAPCHAIN_IMAGES   8

Definition at line 19 of file sbgl_backend_vulkan.c.

◆ SBGL_STATIC_HEAP_SIZE

#define SBGL_STATIC_HEAP_SIZE   (128 * 1024 * 1024)

Definition at line 21 of file sbgl_backend_vulkan.c.

◆ SBGL_TRANSIENT_BUFFER_SIZE

#define SBGL_TRANSIENT_BUFFER_SIZE   (16 * 1024 * 1024)

Definition at line 20 of file sbgl_backend_vulkan.c.

◆ SBGL_VK_PUSH_CONSTANT_SIZE

#define SBGL_VK_PUSH_CONSTANT_SIZE   128

Definition at line 197 of file sbgl_backend_vulkan.c.

◆ VK_NO_PROTOTYPES

#define VK_NO_PROTOTYPES

Definition at line 6 of file sbgl_backend_vulkan.c.

Enumeration Type Documentation

◆ SBGL_HeapType

Enumerator
SBGL_HEAP_TYPE_STATIC 
SBGL_HEAP_TYPE_DYNAMIC 
SBGL_HEAP_TYPE_MANAGED 

Definition at line 32 of file sbgl_backend_vulkan.c.

Function Documentation

◆ cleanup_swapchain()

static void cleanup_swapchain ( sbgl_GfxContext * ctx)
static

Definition at line 169 of file sbgl_backend_vulkan.c.

169 {
170 ctx->vk.vkDestroyImageView(ctx->device, ctx->depthImageView, NULL);
171 ctx->vk.vkDestroyImage(ctx->device, ctx->depthImage, NULL);
172 ctx->vk.vkFreeMemory(ctx->device, ctx->depthMemory, NULL);
173
174 for (uint32_t i = 0; i < ctx->imageCount; i++) {
175 ctx->vk.vkDestroyImageView(ctx->device, ctx->imageViews[i], NULL);
176 }
177 ctx->vk.vkDestroySwapchainKHR(ctx->device, ctx->swapchain, NULL);
179}
SBL_ARENA_DEF void sbl_arena_rewind(SblArena *arena, SblArenaMark mark)
SblArenaMark swapchainMark
VkSwapchainKHR swapchain
struct VolkDeviceTable vk
VkImageView * imageViews
VkDeviceMemory depthMemory

◆ create_depth_resources()

static bool create_depth_resources ( sbgl_GfxContext * ctx)
static

Definition at line 687 of file sbgl_backend_vulkan.c.

687 {
688 if (!find_depth_format(ctx))
689 return false;
690
691 VkImageCreateInfo imageInfo = {
692 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
693 .imageType = VK_IMAGE_TYPE_2D,
694 .extent = { .width = ctx->swapchainExtent.width,
695 .height = ctx->swapchainExtent.height,
696 .depth = 1 },
697 .mipLevels = 1,
698 .arrayLayers = 1,
699 .format = ctx->depthFormat,
700 .tiling = VK_IMAGE_TILING_OPTIMAL,
701 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
702 .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
703 .samples = VK_SAMPLE_COUNT_1_BIT,
704 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
705 };
706
707 if (ctx->vk.vkCreateImage(ctx->device, &imageInfo, NULL, &ctx->depthImage) != VK_SUCCESS)
708 return false;
709
710 VkMemoryRequirements memRequirements;
711 ctx->vk.vkGetImageMemoryRequirements(ctx->device, ctx->depthImage, &memRequirements);
712
713 VkMemoryAllocateInfo allocInfo = {
714 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
715 .allocationSize = memRequirements.size,
716 .memoryTypeIndex = find_memory_type(
717 ctx,
718 memRequirements.memoryTypeBits,
719 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
720 ),
721 };
722
723 if (ctx->vk.vkAllocateMemory(ctx->device, &allocInfo, NULL, &ctx->depthMemory) != VK_SUCCESS)
724 return false;
725
726 ctx->vk.vkBindImageMemory(ctx->device, ctx->depthImage, ctx->depthMemory, 0);
727
728 VkImageViewCreateInfo viewInfo = {
729 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
730 .image = ctx->depthImage,
731 .viewType = VK_IMAGE_VIEW_TYPE_2D,
732 .format = ctx->depthFormat,
733 .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
734 .levelCount = 1,
735 .layerCount = 1 },
736 };
737
738 if (ctx->vk.vkCreateImageView(ctx->device, &viewInfo, NULL, &ctx->depthImageView) != VK_SUCCESS)
739 return false;
740
741 return true;
742}
static bool find_depth_format(sbgl_GfxContext *ctx)
static uint32_t find_memory_type(sbgl_GfxContext *ctx, uint32_t typeFilter, VkMemoryPropertyFlags properties)

◆ create_heaps()

static bool create_heaps ( sbgl_GfxContext * ctx)
static

Definition at line 401 of file sbgl_backend_vulkan.c.

401 {
402 /* The system initializes three distinct memory heaps to support diverse resource
403 allocation patterns: Static for long-lived assets, Dynamic for per-frame data,
404 and Managed for flexible sub-allocations. All heaps are host-visible and
405 persistently mapped for zero-copy access. */
406
407 uint32_t memoryTypeIndex = find_memory_type(
408 ctx,
409 0xFFFFFFFF,
410 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
411 );
412
413 if (memoryTypeIndex == SBGL_INVALID_OFFSET) {
414 return false;
415 }
416
417 VkMemoryAllocateFlagsInfo flagsInfo = {
418 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
419 .flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
420 };
421
422 VkMemoryAllocateInfo allocInfo = {
423 .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
424 .pNext = &flagsInfo,
425 .memoryTypeIndex = memoryTypeIndex,
426 };
427
428 // Static Heap
430 allocInfo.allocationSize = ctx->staticHeap.size;
431 if (ctx->vk.vkAllocateMemory(ctx->device, &allocInfo, NULL, &ctx->staticHeap.memory) != VK_SUCCESS) {
432 return false;
433 }
434 ctx->vk.vkMapMemory(ctx->device, ctx->staticHeap.memory, 0, ctx->staticHeap.size, 0, &ctx->staticHeap.mapped);
435 ctx->staticHeap.offset = 0;
436
437 // Dynamic Heap (split into two 64MB buffers for double buffering)
439 allocInfo.allocationSize = ctx->dynamicHeap.size;
440 if (ctx->vk.vkAllocateMemory(ctx->device, &allocInfo, NULL, &ctx->dynamicHeap.memory) != VK_SUCCESS) {
441 return false;
442 }
443 void* dynamicBase;
444 ctx->vk.vkMapMemory(ctx->device, ctx->dynamicHeap.memory, 0, ctx->dynamicHeap.size, 0, &dynamicBase);
445 ctx->dynamicHeap.mapped[0] = dynamicBase;
446 ctx->dynamicHeap.mapped[1] = (char*)dynamicBase + (SBGL_DYNAMIC_HEAP_SIZE / 2);
447 ctx->dynamicHeap.offset[0] = 0;
448 ctx->dynamicHeap.offset[1] = 0;
449
450 // Managed Heap
452 allocInfo.allocationSize = ctx->managedHeap.size;
453 if (ctx->vk.vkAllocateMemory(ctx->device, &allocInfo, NULL, &ctx->managedHeap.memory) != VK_SUCCESS) {
454 return false;
455 }
456 ctx->vk.vkMapMemory(ctx->device, ctx->managedHeap.memory, 0, ctx->managedHeap.size, 0, &ctx->managedHeap.mapped);
457 ctx->managedHeap.rangeCount = 1;
458 ctx->managedHeap.ranges[0] = (sbgl_GfxMemoryRange){ .offset = 0, .size = SBGL_MANAGED_HEAP_SIZE, .handle_index = 0 };
459
460 return true;
461}
#define SBGL_STATIC_HEAP_SIZE
#define SBGL_DYNAMIC_HEAP_SIZE
#define SBGL_MANAGED_HEAP_SIZE
#define SBGL_INVALID_OFFSET
Definition sbgl_types.h:9
sbgl_GfxDynamicHeap dynamicHeap
sbgl_GfxManagedHeap managedHeap
sbgl_GfxStaticHeap staticHeap
sbgl_GfxMemoryRange ranges[1024]

◆ create_instance()

static bool create_instance ( sbgl_GfxContext * ctx,
bool enableValidation )
static

Definition at line 463 of file sbgl_backend_vulkan.c.

463 {
464 VkApplicationInfo appInfo = {
465 .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
466 .pApplicationName = "SBgl Application",
467 .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
468 .pEngineName = "SBgl",
469 .engineVersion = VK_MAKE_VERSION(1, 0, 0),
470 .apiVersion = VK_API_VERSION_1_3,
471 };
472
473 const char* extensions[] = {
474 VK_KHR_SURFACE_EXTENSION_NAME,
475#ifdef SBGL_PLATFORM_WAYLAND
476 VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
477#elif defined(SBGL_PLATFORM_X11)
478 VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
479#elif defined(_WIN32)
480 VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
481#endif
482 };
483
484 VkInstanceCreateInfo createInfo = {
485 .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
486 .pApplicationInfo = &appInfo,
487 .enabledExtensionCount = sizeof(extensions) / sizeof(extensions[0]),
488 .ppEnabledExtensionNames = extensions,
489 };
490
491 const char* layers[] = { "VK_LAYER_KHRONOS_validation" };
492
493 if (enableValidation) {
494 createInfo.enabledLayerCount = 1;
495 createInfo.ppEnabledLayerNames = layers;
496 }
497
498 VkResult result = vkCreateInstance(&createInfo, NULL, &ctx->instance);
499 ctx->backendResult = result;
500
501 if (result != VK_SUCCESS) {
502 return false;
503 }
504
505 volkLoadInstance(ctx->instance);
506
507 if (enableValidation) {
509 }
510
511 return true;
512}
static void sbgl_setup_debug_utils(sbgl_GfxContext *ctx)

◆ create_logical_device()

static bool create_logical_device ( sbgl_GfxContext * ctx)
static

Definition at line 586 of file sbgl_backend_vulkan.c.

586 {
587 uint32_t queueFamilyCount = 0;
588 vkGetPhysicalDeviceQueueFamilyProperties(ctx->physicalDevice, &queueFamilyCount, NULL);
589
590 SblArenaMark mark = sbl_arena_mark(ctx->arena);
591 VkQueueFamilyProperties* queueFamilies =
592 SBL_ARENA_PUSH_ARRAY(ctx->arena, VkQueueFamilyProperties, queueFamilyCount);
593 if (!queueFamilies)
594 return false;
595 vkGetPhysicalDeviceQueueFamilyProperties(ctx->physicalDevice, &queueFamilyCount, queueFamilies);
596
597 int graphicsFamily = -1;
598 for (uint32_t i = 0; i < queueFamilyCount; i++) {
599 VkBool32 presentSupport = false;
600 vkGetPhysicalDeviceSurfaceSupportKHR(ctx->physicalDevice, i, ctx->surface, &presentSupport);
601 if ((queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && presentSupport) {
602 graphicsFamily = i;
603 break;
604 }
605 }
606 sbl_arena_rewind(ctx->arena, mark);
607
608 if (graphicsFamily == -1) {
609 fprintf(stderr, "[Vulkan] No suitable queue family found\n");
610 return false;
611 }
612 ctx->graphicsQueueFamily = (uint32_t)graphicsFamily;
613
614 float queuePriority = 1.0f;
615 VkDeviceQueueCreateInfo queueCreateInfo = {
616 .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
617 .queueFamilyIndex = ctx->graphicsQueueFamily,
618 .queueCount = 1,
619 .pQueuePriorities = &queuePriority,
620 };
621
622 VkPhysicalDeviceFeatures deviceFeatures = {
623 .multiDrawIndirect = VK_TRUE,
624 .shaderInt64 = VK_TRUE,
625 };
626
627 const char* deviceExtensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME,
628 VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
629 VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME };
630
631 VkPhysicalDeviceVulkan11Features features11 = {
632 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
633 .shaderDrawParameters = VK_TRUE,
634 };
635
636 VkPhysicalDeviceVulkan12Features features12 = {
637 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
638 .pNext = &features11,
639 .bufferDeviceAddress = VK_TRUE,
640 .hostQueryReset = VK_TRUE,
641 };
642
643 VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeatures = {
644 .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
645 .pNext = &features12,
646 .dynamicRendering = VK_TRUE,
647 };
648
649 VkDeviceCreateInfo createInfo = {
650 .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
651 .pNext = &dynamicRenderingFeatures,
652 .queueCreateInfoCount = 1,
653 .pQueueCreateInfos = &queueCreateInfo,
654 .pEnabledFeatures = &deviceFeatures,
655 .enabledExtensionCount = 3,
656 .ppEnabledExtensionNames = deviceExtensions,
657 };
658
659 if (vkCreateDevice(ctx->physicalDevice, &createInfo, NULL, &ctx->device) != VK_SUCCESS) {
660 fprintf(stderr, "[Vulkan] Failed to create logical device\n");
661 return false;
662 }
663
664 volkLoadDeviceTable(&ctx->vk, ctx->device);
665
666 ctx->vk.vkGetDeviceQueue(ctx->device, ctx->graphicsQueueFamily, 0, &ctx->graphicsQueue);
667
668 printf("[Vulkan] Logical Device created (Dynamic Rendering enabled)\n");
669 return true;
670}
SBL_ARENA_DEF SblArenaMark sbl_arena_mark(SblArena *arena)
#define SBL_ARENA_PUSH_ARRAY(arena, type, count)
Definition sbl_arena.h:21
Bookmark for arena state.
Definition sbl_arena.h:57
VkPhysicalDevice physicalDevice

◆ create_surface()

static bool create_surface ( sbgl_GfxContext * ctx,
sbgl_Window * window )
static

Definition at line 514 of file sbgl_backend_vulkan.c.

514 {
515#ifdef SBGL_PLATFORM_WAYLAND
516 VkWaylandSurfaceCreateInfoKHR createInfo = {
517 .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
518 .display = (struct wl_display*)sbgl_os_GetNativeDisplayHandle(window),
519 .surface = (struct wl_surface*)sbgl_os_GetNativeWindowHandle(window),
520 };
521 if (vkCreateWaylandSurfaceKHR(ctx->instance, &createInfo, NULL, &ctx->surface) != VK_SUCCESS) {
522 fprintf(stderr, "[Vulkan] Failed to create Wayland surface\n");
523 return false;
524 }
525#elif defined(SBGL_PLATFORM_X11)
526 VkXlibSurfaceCreateInfoKHR createInfo = {
527 .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
528 .dpy = (Display*)sbgl_os_GetNativeDisplayHandle(window),
529 .window = (Window)(uintptr_t)sbgl_os_GetNativeWindowHandle(window),
530 };
531 if (vkCreateXlibSurfaceKHR(ctx->instance, &createInfo, NULL, &ctx->surface) != VK_SUCCESS) {
532 fprintf(stderr, "[Vulkan] Failed to create Xlib surface\n");
533 return false;
534 }
535#elif defined(_WIN32)
536 VkWin32SurfaceCreateInfoKHR createInfo = {
537 .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
538 .hinstance = (HINSTANCE)sbgl_os_GetNativeInstanceHandle(window),
539 .hwnd = (HWND)sbgl_os_GetNativeWindowHandle(window),
540 };
541 if (vkCreateWin32SurfaceKHR(ctx->instance, &createInfo, NULL, &ctx->surface) != VK_SUCCESS) {
542 fprintf(stderr, "[Vulkan] Failed to create Win32 surface\n");
543 return false;
544 }
545#endif
546
547 printf("[Vulkan] Surface created successfully\n");
548 return true;
549}
void * sbgl_os_GetNativeWindowHandle(sbgl_Window *window)
Retrieves the raw window handle for Vulkan surface creation.
void * sbgl_os_GetNativeDisplayHandle(sbgl_Window *window)
Retrieves the native display handle (Linux specific).
void * sbgl_os_GetNativeInstanceHandle(sbgl_Window *window)
Retrieves the native instance handle (Win32 specific).
Definition window_x11.c:152

◆ create_swapchain()

static bool create_swapchain ( sbgl_GfxContext * ctx,
sbgl_Window * window )
static

Definition at line 744 of file sbgl_backend_vulkan.c.

744 {
745 int w, h;
746 sbgl_os_GetWindowSize(window, &w, &h);
747
748 VkSurfaceCapabilitiesKHR capabilities;
749 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(ctx->physicalDevice, ctx->surface, &capabilities);
750
751 VkExtent2D extent = { (uint32_t)w, (uint32_t)h };
752 if (capabilities.currentExtent.width != 0xFFFFFFFF) {
753 extent = capabilities.currentExtent;
754 }
755
756 if (extent.width == 0 || extent.height == 0) {
757 return false;
758 }
759
760 uint32_t formatCount;
761 vkGetPhysicalDeviceSurfaceFormatsKHR(ctx->physicalDevice, ctx->surface, &formatCount, NULL);
762 if (formatCount == 0) {
763 fprintf(stderr, "[Vulkan] No supported surface formats found\n");
764 return false;
765 }
766 VkSurfaceFormatKHR formats[64];
767 if (formatCount > 64)
768 formatCount = 64;
769 vkGetPhysicalDeviceSurfaceFormatsKHR(ctx->physicalDevice, ctx->surface, &formatCount, formats);
770
771 VkSurfaceFormatKHR selectedFormat = formats[0];
772 for (uint32_t i = 0; i < formatCount; i++) {
773 if ((formats[i].format == VK_FORMAT_B8G8R8A8_SRGB ||
774 formats[i].format == VK_FORMAT_R8G8B8A8_SRGB) &&
775 formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
776 selectedFormat = formats[i];
777 break;
778 }
779 }
780
781 uint32_t imageCount = capabilities.minImageCount + 1;
782 if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) {
783 imageCount = capabilities.maxImageCount;
784 }
785
786 VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
787 uint32_t presentModeCount;
788 vkGetPhysicalDeviceSurfacePresentModesKHR(
789 ctx->physicalDevice,
790 ctx->surface,
791 &presentModeCount,
792 NULL
793 );
794 if (presentModeCount > 0) {
795 VkPresentModeKHR presentModes[16];
796 if (presentModeCount > 16)
797 presentModeCount = 16;
798 vkGetPhysicalDeviceSurfacePresentModesKHR(
799 ctx->physicalDevice,
800 ctx->surface,
801 &presentModeCount,
802 presentModes
803 );
804
805 /* The system prioritizes present modes that minimize latency and maximize throughput.
806 IMMEDIATE is preferred for raw benchmarks, while MAILBOX provides high-performance
807 triple-buffering without tearing. */
808 bool mailboxSupported = false;
809 bool immediateSupported = false;
810 for (uint32_t i = 0; i < presentModeCount; i++) {
811 if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
812 immediateSupported = true;
813 if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
814 mailboxSupported = true;
815 }
816
817 if (immediateSupported) {
818 presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
819 } else if (mailboxSupported) {
820 presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
821 }
822 }
823
824 VkSwapchainCreateInfoKHR createInfo = {
825 .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
826 .surface = ctx->surface,
827 .minImageCount = imageCount,
828 .imageFormat = selectedFormat.format,
829 .imageColorSpace = selectedFormat.colorSpace,
830 .imageExtent = extent,
831 .imageArrayLayers = 1,
832 .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
833 .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
834 .preTransform = capabilities.currentTransform,
835 .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
836 .presentMode = presentMode,
837 .clipped = VK_TRUE,
838 };
839
840 if (ctx->vk.vkCreateSwapchainKHR(ctx->device, &createInfo, NULL, &ctx->swapchain) !=
841 VK_SUCCESS) {
842 fprintf(stderr, "[Vulkan] Failed to create swapchain\n");
843 return false;
844 }
845
846 ctx->swapchainExtent = createInfo.imageExtent;
847 ctx->swapchainFormat = createInfo.imageFormat;
848
849 ctx->vk.vkGetSwapchainImagesKHR(ctx->device, ctx->swapchain, &ctx->imageCount, NULL);
850
852 ctx->images = SBL_ARENA_PUSH_ARRAY(ctx->arena, VkImage, ctx->imageCount);
853 if (!ctx->images)
854 return false;
855 ctx->vk.vkGetSwapchainImagesKHR(ctx->device, ctx->swapchain, &ctx->imageCount, ctx->images);
856
857 ctx->imageViews = SBL_ARENA_PUSH_ARRAY(ctx->arena, VkImageView, ctx->imageCount);
858 if (!ctx->imageViews)
859 return false;
860 for (uint32_t i = 0; i < ctx->imageCount; i++) {
861 VkImageViewCreateInfo viewInfo = {
862 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
863 .image = ctx->images[i],
864 .viewType = VK_IMAGE_VIEW_TYPE_2D,
865 .format = ctx->swapchainFormat,
866 .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
867 .levelCount = 1,
868 .layerCount = 1 },
869 };
870 ctx->vk.vkCreateImageView(ctx->device, &viewInfo, NULL, &ctx->imageViews[i]);
871 }
872
873 printf(
874 "[Vulkan] Swapchain created (%dx%d, %u images, format: %d)\n",
875 ctx->swapchainExtent.width,
876 ctx->swapchainExtent.height,
877 ctx->imageCount,
878 ctx->swapchainFormat
879 );
880
881 if (!create_depth_resources(ctx))
882 return false;
883
884 return true;
885}
static bool create_depth_resources(sbgl_GfxContext *ctx)
void sbgl_os_GetWindowSize(sbgl_Window *window, int *w, int *h)
Retrieves the current client area size.

◆ create_sync_and_command()

static bool create_sync_and_command ( sbgl_GfxContext * ctx)
static

Definition at line 887 of file sbgl_backend_vulkan.c.

887 {
888 VkCommandPoolCreateInfo poolInfo = {
889 .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
890 .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
891 .queueFamilyIndex = ctx->graphicsQueueFamily,
892 };
893 if (ctx->vk.vkCreateCommandPool(ctx->device, &poolInfo, NULL, &ctx->commandPool) != VK_SUCCESS)
894 return false;
895
896 VkCommandBufferAllocateInfo allocInfo = {
897 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
898 .commandPool = ctx->commandPool,
899 .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
900 .commandBufferCount = SBGL_MAX_FRAMES_IN_FLIGHT,
901 };
902 if (ctx->vk.vkAllocateCommandBuffers(ctx->device, &allocInfo, ctx->commandBuffers) !=
903 VK_SUCCESS)
904 return false;
905
906 VkSemaphoreCreateInfo semInfo = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
907 VkFenceCreateInfo fenceInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
908 .flags = VK_FENCE_CREATE_SIGNALED_BIT };
909
910 for (uint32_t i = 0; i < SBGL_MAX_FRAMES_IN_FLIGHT; i++) {
911 if (ctx->vk.vkCreateFence(ctx->device, &fenceInfo, NULL, &ctx->inFlightFences[i]) !=
912 VK_SUCCESS)
913 return false;
914 }
915
916 for (uint32_t i = 0; i < SBGL_MAX_SWAPCHAIN_IMAGES; i++) {
917 if (ctx->vk.vkCreateSemaphore(
918 ctx->device,
919 &semInfo,
920 NULL,
922 ) != VK_SUCCESS ||
923 ctx->vk.vkCreateSemaphore(
924 ctx->device,
925 &semInfo,
926 NULL,
928 ) != VK_SUCCESS)
929 return false;
930 }
931
932 return true;
933}
#define SBGL_MAX_FRAMES_IN_FLIGHT
#define SBGL_MAX_SWAPCHAIN_IMAGES
VkCommandBuffer commandBuffers[SBGL_MAX_FRAMES_IN_FLIGHT]
VkSemaphore imageAvailableSemaphores[SBGL_MAX_SWAPCHAIN_IMAGES]
VkSemaphore renderFinishedSemaphores[SBGL_MAX_SWAPCHAIN_IMAGES]
VkFence inFlightFences[SBGL_MAX_FRAMES_IN_FLIGHT]
VkCommandPool commandPool

◆ create_telemetry_resources()

static bool create_telemetry_resources ( sbgl_GfxContext * ctx)
static

Definition at line 935 of file sbgl_backend_vulkan.c.

935 {
936 /* The system initializes a query pool with two timestamp slots for each frame in flight
937 to track GPU execution time, enabling performance telemetry across the full pipeline. */
938 VkQueryPoolCreateInfo queryPoolInfo = {
939 .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
940 .queryType = VK_QUERY_TYPE_TIMESTAMP,
941 .queryCount = SBGL_MAX_FRAMES_IN_FLIGHT * 2,
942 };
943
944 if (ctx->vk.vkCreateQueryPool(ctx->device, &queryPoolInfo, NULL, &ctx->queryPool) !=
945 VK_SUCCESS) {
946 return false;
947 }
948
949 /* The timestamp period is retrieved from physical device properties to convert
950 GPU clock cycles into nanoseconds. */
951 VkPhysicalDeviceProperties props;
952 vkGetPhysicalDeviceProperties(ctx->physicalDevice, &props);
953 ctx->timestampPeriod = props.limits.timestampPeriod;
954
955 return true;
956}

◆ create_transient_resources()

static bool create_transient_resources ( sbgl_GfxContext * ctx)
static

Definition at line 958 of file sbgl_backend_vulkan.c.

958 {
959 /* The system allocates persistent, large-scale buffers for each frame in flight,
960 enabling high-frequency data updates without the overhead of per-frame allocations. */
961 for (uint32_t i = 0; i < SBGL_MAX_FRAMES_IN_FLIGHT; i++) {
963 ctx,
966 NULL
967 );
968 if (ctx->transientBuffers[i] == SBGL_INVALID_HANDLE) {
969 return false;
970 }
971
972 uint32_t idx = (uint32_t)ctx->transientBuffers[i] - 1;
973 SBGL_VulkanBuffer* buffer = &ctx->buffers[idx];
974
975 /* The system utilizes the persistent mapping established during buffer creation
976 to provide zero-copy access to the transient memory pools. */
977 ctx->transientMapped[i] = buffer->mapped;
978 if (!ctx->transientMapped[i]) {
979 return false;
980 }
981 ctx->transientOffsets[i] = 0;
982 }
983 return true;
984}
sbgl_Buffer sbgl_gfx_CreateBuffer(sbgl_GfxContext *ctx, sbgl_BufferUsage usage, size_t size, const void *data)
#define SBGL_TRANSIENT_BUFFER_SIZE
@ SBGL_BUFFER_USAGE_INDIRECT
Definition sbgl_types.h:126
@ SBGL_BUFFER_USAGE_STORAGE
Definition sbgl_types.h:125
#define SBGL_INVALID_HANDLE
Definition sbgl_types.h:8
void * transientMapped[SBGL_MAX_FRAMES_IN_FLIGHT]
uint32_t transientOffsets[SBGL_MAX_FRAMES_IN_FLIGHT]
sbgl_Buffer transientBuffers[SBGL_MAX_FRAMES_IN_FLIGHT]
SBGL_VulkanBuffer * buffers

◆ dynamic_heap_alloc()

static uint32_t dynamic_heap_alloc ( sbgl_GfxContext * ctx,
size_t size )
static

Definition at line 298 of file sbgl_backend_vulkan.c.

298 {
299 /* Dynamic allocations are performed within the heap slice corresponding to the
300 currently active frame, utilizing a 256-byte alignment for hardware compatibility. */
301 uint32_t alignedSize = (uint32_t)((size + 255) & ~255);
302 uint32_t frame = ctx->currentFrame;
303 uint32_t halfSize = ctx->dynamicHeap.size / 2;
304
305 if (ctx->dynamicHeap.offset[frame] + alignedSize > halfSize) {
306 /* If the dynamic allocation exceeds the current frame's capacity, the context
307 is marked with an out-of-memory error to prevent invalid GPU access. */
309 return SBGL_INVALID_OFFSET;
310 }
311
312 uint32_t offset = ctx->dynamicHeap.offset[frame];
313 ctx->dynamicHeap.offset[frame] += alignedSize;
314 return offset;
315}
@ SBGL_ERROR_OUT_OF_MEMORY
Definition sbgl_types.h:221

◆ find_depth_format()

static bool find_depth_format ( sbgl_GfxContext * ctx)
static

Definition at line 672 of file sbgl_backend_vulkan.c.

672 {
673 VkFormat candidates[] = { VK_FORMAT_D32_SFLOAT,
674 VK_FORMAT_D32_SFLOAT_S8_UINT,
675 VK_FORMAT_D24_UNORM_S8_UINT };
676 for (uint32_t i = 0; i < 3; i++) {
677 VkFormatProperties props;
678 vkGetPhysicalDeviceFormatProperties(ctx->physicalDevice, candidates[i], &props);
679 if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
680 ctx->depthFormat = candidates[i];
681 return true;
682 }
683 }
684 return false;
685}

◆ find_memory_type()

static uint32_t find_memory_type ( sbgl_GfxContext * ctx,
uint32_t typeFilter,
VkMemoryPropertyFlags properties )
static

Definition at line 268 of file sbgl_backend_vulkan.c.

268 {
269 VkPhysicalDeviceMemoryProperties memProperties;
270 vkGetPhysicalDeviceMemoryProperties(ctx->physicalDevice, &memProperties);
271
272 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
273 if ((typeFilter & (1 << i)) &&
274 (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
275 return i;
276 }
277 }
278 return SBGL_INVALID_OFFSET;
279}

◆ managed_heap_alloc()

static uint32_t managed_heap_alloc ( sbgl_GfxContext * ctx,
size_t size )
static

Definition at line 317 of file sbgl_backend_vulkan.c.

317 {
318 /* The sub-allocator utilizes a first-fit strategy across an array of memory
319 ranges, ensuring each allocation is aligned to a 256-byte boundary. */
320 uint32_t alignedSize = (uint32_t)((size + 255) & ~255);
321
322 for (uint32_t i = 0; i < ctx->managedHeap.rangeCount; i++) {
323 sbgl_GfxMemoryRange* range = &ctx->managedHeap.ranges[i];
324
325 /* A range is considered a candidate if it is currently unassigned (handle_index 0)
326 and possesses sufficient capacity to accommodate the requested aligned size. */
327 if (range->handle_index == 0 && range->size >= alignedSize) {
328 uint32_t offset = range->offset;
329
330 if (range->size > alignedSize) {
331 /* If the selected range is larger than the requested size, it is split into
332 two segments: the allocated block and a new unassigned trailing range. */
333 if (ctx->managedHeap.rangeCount >= 1024) {
334 /* The allocation fails if the maximum number of range descriptors is exceeded. */
336 return SBGL_INVALID_OFFSET;
337 }
338
339 /* Shift subsequent ranges to maintain the sequential order of the range array. */
340 for (uint32_t j = ctx->managedHeap.rangeCount; j > i + 1; j--) {
341 ctx->managedHeap.ranges[j] = ctx->managedHeap.ranges[j - 1];
342 }
343
344 /* Initialize the new free range representing the remaining space in the block. */
345 ctx->managedHeap.ranges[i + 1] = (sbgl_GfxMemoryRange){
346 .offset = offset + alignedSize,
347 .size = range->size - alignedSize,
348 .handle_index = 0
349 };
350
351 ctx->managedHeap.rangeCount++;
352 range->size = alignedSize;
353 }
354
355 /* The range is marked as active by assigning a non-zero handle index (1 is used
356 as a generic active marker in this implementation stage). */
357 range->handle_index = 1;
358 return offset;
359 }
360 }
361
362 /* If no suitable range is identified, the context state is updated to reflect
363 the allocation failure. */
365 return SBGL_INVALID_OFFSET;
366}

◆ managed_heap_free()

static void managed_heap_free ( sbgl_GfxContext * ctx,
uint32_t offset )
static

Definition at line 368 of file sbgl_backend_vulkan.c.

368 {
369 /* The freeing process identifies the range associated with the given offset
370 and attempts to merge it with adjacent free blocks to mitigate fragmentation. */
371 for (uint32_t i = 0; i < ctx->managedHeap.rangeCount; i++) {
372 if (ctx->managedHeap.ranges[i].offset == offset) {
373 /* The block is transitioned back to an unassigned state. */
374 ctx->managedHeap.ranges[i].handle_index = 0;
375
376 /* Coalescing with the subsequent block occurs if it is also unassigned. */
377 if (i + 1 < ctx->managedHeap.rangeCount && ctx->managedHeap.ranges[i + 1].handle_index == 0) {
378 ctx->managedHeap.ranges[i].size += ctx->managedHeap.ranges[i + 1].size;
379 for (uint32_t j = i + 1; j < ctx->managedHeap.rangeCount - 1; j++) {
380 ctx->managedHeap.ranges[j] = ctx->managedHeap.ranges[j + 1];
381 }
382 ctx->managedHeap.rangeCount--;
383 }
384
385 /* Coalescing with the preceding block occurs if it is also unassigned. */
386 if (i > 0 && ctx->managedHeap.ranges[i - 1].handle_index == 0) {
387 ctx->managedHeap.ranges[i - 1].size += ctx->managedHeap.ranges[i].size;
388 for (uint32_t j = i; j < ctx->managedHeap.rangeCount - 1; j++) {
389 ctx->managedHeap.ranges[j] = ctx->managedHeap.ranges[j + 1];
390 }
391 ctx->managedHeap.rangeCount--;
392 }
393
394 return;
395 }
396 }
397
398 fprintf(stderr, "[Vulkan] managed_heap_free: offset %u not found in tracked ranges (possible double-free or corruption)\n", offset);
399}

◆ recreate_swapchain()

static void recreate_swapchain ( sbgl_GfxContext * ctx)
static

Definition at line 183 of file sbgl_backend_vulkan.c.

183 {
184 int w = 0, h = 0;
185 sbgl_os_GetWindowSize(ctx->window, &w, &h);
186 while (w == 0 || h == 0) {
187 sbgl_os_GetWindowSize(ctx->window, &w, &h);
189 }
190
191 ctx->vk.vkDeviceWaitIdle(ctx->device);
192
194 create_swapchain(ctx, ctx->window);
195}
static bool create_swapchain(sbgl_GfxContext *ctx, sbgl_Window *window)
static void cleanup_swapchain(sbgl_GfxContext *ctx)
void sbgl_os_PollEvents(sbgl_Window *window)
Dispatches OS events (messages/protocol requests).

◆ sbgl_gfx_AllocateTransient()

sbgl_GfxTransientAllocation sbgl_gfx_AllocateTransient ( sbgl_GfxContext * ctx,
size_t size,
uint32_t alignment )

Allocates a slice of GPU-visible memory for transient per-frame data.

This memory is managed by the backend's internal per-frame ring buffers and does not require manual destruction.

Parameters
ctxThe graphics context.
sizeThe number of bytes to allocate.
alignmentThe required byte alignment for the allocation.
Returns
A structure containing the allocation metadata and mapped pointer.

Definition at line 2040 of file sbgl_backend_vulkan.c.

2040 {
2041 /* The system sub-allocates from the current frame's persistent buffer, respecting
2042 the requested alignment to ensure compatibility with Vulkan requirements. */
2043 uint32_t frame = ctx->currentFrame;
2044 uint32_t offset = ctx->transientOffsets[frame];
2045
2046 if (alignment > 0) {
2047 offset = (offset + alignment - 1) & ~(alignment - 1);
2048 }
2049
2050 if (offset + size > SBGL_TRANSIENT_BUFFER_SIZE) {
2051 fprintf(stderr, "[Vulkan] Transient buffer overflow for frame %u!\n", frame);
2052 return (sbgl_GfxTransientAllocation){ 0 };
2053 }
2054
2056 .buffer = ctx->transientBuffers[frame],
2057 .offset = offset,
2058 .size = (uint32_t)size,
2059 .mapped = (char*)ctx->transientMapped[frame] + offset,
2060 .deviceAddress = sbgl_gfx_GetBufferDeviceAddress(ctx, ctx->transientBuffers[frame]) + offset
2061 };
2062
2063 ctx->transientOffsets[frame] = offset + (uint32_t)size;
2064 return alloc;
2065}
uint64_t sbgl_gfx_GetBufferDeviceAddress(sbgl_GfxContext *ctx, sbgl_Buffer handle)
Retrieves the 64-bit GPU virtual address for a buffer.
Represents a slice of a persistent GPU buffer used for transient data.

◆ sbgl_gfx_BeginFrame()

bool sbgl_gfx_BeginFrame ( sbgl_GfxContext * ctx)

Starts a new frame, acquiring an image and starting the command buffer.

This must be called before any GPU commands (Compute or Graphics) are recorded.

Definition at line 1106 of file sbgl_backend_vulkan.c.

1106 {
1107 ctx->vk.vkWaitForFences(
1108 ctx->device,
1109 1,
1110 &ctx->inFlightFences[ctx->currentFrame],
1111 VK_TRUE,
1112 UINT64_MAX
1113 );
1114
1115 /* The system processes the deferred destruction queue for the current frame slot,
1116 releasing GPU resources that are no longer in flight. */
1117 for (uint32_t i = 0; i < ctx->deferredCount[ctx->currentFrame]; i++) {
1119 }
1120 ctx->deferredCount[ctx->currentFrame] = 0;
1121
1122 /* The transient allocation offset is reset for the current frame, effectively
1123 recycling the GPU memory for new data while ensuring it does not overlap with
1124 memory currently in use by other frames in flight. */
1125 ctx->transientOffsets[ctx->currentFrame] = 0;
1126 ctx->dynamicHeap.offset[ctx->currentFrame] = 0;
1127
1128 if (sbgl_os_WasWindowResized(ctx->window)) {
1129 recreate_swapchain(ctx);
1130 }
1131
1132 VkResult result = ctx->vk.vkAcquireNextImageKHR(
1133 ctx->device,
1134 ctx->swapchain,
1135 UINT64_MAX,
1137 VK_NULL_HANDLE,
1138 &ctx->currentImageIndex
1139 );
1140
1141 ctx->backendResult = result;
1142
1143 if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1144 recreate_swapchain(ctx);
1145 return false;
1146 } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
1147 return false;
1148 }
1149
1150 ctx->vk.vkResetFences(ctx->device, 1, &ctx->inFlightFences[ctx->currentFrame]);
1151 ctx->vk.vkResetCommandBuffer(ctx->commandBuffers[ctx->currentFrame], 0);
1152 VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
1153 ctx->vk.vkBeginCommandBuffer(ctx->commandBuffers[ctx->currentFrame], &beginInfo);
1154
1155 /* The system resets the query pool for the current frame to prepare for new
1156 timestamp recordings. */
1157 ctx->vk.vkCmdResetQueryPool(
1158 ctx->commandBuffers[ctx->currentFrame],
1159 ctx->queryPool,
1160 ctx->currentFrame * 2,
1161 2
1162 );
1163
1164 return true;
1165}
static void recreate_swapchain(sbgl_GfxContext *ctx)
void sbgl_gfx_DestroyBuffer(sbgl_GfxContext *ctx, sbgl_Buffer handle)
bool sbgl_os_WasWindowResized(sbgl_Window *window)
Checks if the window has been resized since the last check.
sbgl_Buffer deferredBuffers[SBGL_MAX_FRAMES_IN_FLIGHT][64]
uint32_t deferredCount[SBGL_MAX_FRAMES_IN_FLIGHT]

◆ sbgl_gfx_BeginRenderPass()

void sbgl_gfx_BeginRenderPass ( sbgl_GfxContext * ctx,
float r,
float g,
float b,
float a )

Starts a graphics rendering pass.

This must be called before any draw commands are recorded. It handles clearing the attachments if requested.

Definition at line 1167 of file sbgl_backend_vulkan.c.

1167 {
1168 /* The system records the starting timestamp at the beginning of the graphics pass. */
1169 ctx->vk.vkCmdWriteTimestamp(
1170 ctx->commandBuffers[ctx->currentFrame],
1171 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1172 ctx->queryPool,
1173 ctx->currentFrame * 2
1174 );
1175
1176 VkImageMemoryBarrier barriers[2] = { 0 };
1177 barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1178 barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1179 barriers[0].newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
1180 barriers[0].image = ctx->images[ctx->currentImageIndex];
1181 barriers[0].subresourceRange =
1182 (VkImageSubresourceRange){ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
1183 .levelCount = 1,
1184 .layerCount = 1 };
1185 barriers[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
1186
1187 barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1188 barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1189 barriers[1].newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
1190 barriers[1].image = ctx->depthImage;
1191 barriers[1].subresourceRange =
1192 (VkImageSubresourceRange){ .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
1193 .levelCount = 1,
1194 .layerCount = 1 };
1195 barriers[1].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
1196
1197 ctx->vk.vkCmdPipelineBarrier(
1198 ctx->commandBuffers[ctx->currentFrame],
1199 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
1200 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
1201 0,
1202 0,
1203 NULL,
1204 0,
1205 NULL,
1206 2,
1207 barriers
1208 );
1209
1210 VkRenderingAttachmentInfo colorAttachment = {
1211 .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
1212 .imageView = ctx->imageViews[ctx->currentImageIndex],
1213 .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
1214 .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
1215 .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
1216 .clearValue = { { { r, g, b, a } } },
1217 };
1218
1219 VkRenderingAttachmentInfo depthAttachment = {
1220 .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
1221 .imageView = ctx->depthImageView,
1222 .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
1223 .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
1224 .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
1225 .clearValue = { .depthStencil = { 1.0f, 0 } },
1226 };
1227
1228 VkRenderingInfo renderingInfo = {
1229 .sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
1230 .renderArea = { .extent = ctx->swapchainExtent },
1231 .layerCount = 1,
1232 .colorAttachmentCount = 1,
1233 .pColorAttachments = &colorAttachment,
1234 .pDepthAttachment = &depthAttachment,
1235 };
1236
1237 ctx->vk.vkCmdBeginRendering(ctx->commandBuffers[ctx->currentFrame], &renderingInfo);
1238}

◆ sbgl_gfx_BindBuffer()

void sbgl_gfx_BindBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer handle,
sbgl_BufferUsage usage )

Definition at line 1971 of file sbgl_backend_vulkan.c.

1971 {
1972 if (handle == SBGL_INVALID_HANDLE)
1973 return;
1974 uint32_t index = (uint32_t)handle - 1;
1975 if (index >= ctx->limits.maxBuffers || !ctx->bufferActive[index])
1976 return;
1977
1978 if (usage == SBGL_BUFFER_USAGE_VERTEX) {
1979 VkDeviceSize offsets[] = { 0 };
1980 ctx->vk.vkCmdBindVertexBuffers(
1981 ctx->commandBuffers[ctx->currentFrame],
1982 0,
1983 1,
1984 &ctx->buffers[index].handle,
1985 offsets
1986 );
1987 } else if (usage == SBGL_BUFFER_USAGE_INDEX) {
1988 ctx->vk.vkCmdBindIndexBuffer(
1989 ctx->commandBuffers[ctx->currentFrame],
1990 ctx->buffers[index].handle,
1991 0,
1992 VK_INDEX_TYPE_UINT32
1993 );
1994 }
1995}
@ SBGL_BUFFER_USAGE_INDEX
Definition sbgl_types.h:124
@ SBGL_BUFFER_USAGE_VERTEX
Definition sbgl_types.h:123
sbgl_ResourceLimits limits

◆ sbgl_gfx_BindComputePipeline()

void sbgl_gfx_BindComputePipeline ( sbgl_GfxContext * ctx,
sbgl_ComputePipeline handle )

Definition at line 1851 of file sbgl_backend_vulkan.c.

1851 {
1852 /* The currently active command buffer is updated to utilize the specified compute
1853 pipeline for all subsequent dispatch operations. */
1854 if (handle == SBGL_INVALID_HANDLE) {
1856 return;
1857 }
1858 uint32_t index = (uint32_t)handle - 1;
1859 if (index >= ctx->limits.maxPipelines || !ctx->computePipelines[index].active)
1860 return;
1861
1862 ctx->vk.vkCmdBindPipeline(
1863 ctx->commandBuffers[ctx->currentFrame],
1864 VK_PIPELINE_BIND_POINT_COMPUTE,
1865 ctx->computePipelines[index].handle
1866 );
1867 ctx->boundComputePipeline = handle;
1868}
SBGL_VulkanComputePipeline * computePipelines
sbgl_ComputePipeline boundComputePipeline

◆ sbgl_gfx_BindPipeline()

void sbgl_gfx_BindPipeline ( sbgl_GfxContext * ctx,
sbgl_Pipeline handle )

Definition at line 1943 of file sbgl_backend_vulkan.c.

1943 {
1944 if (handle == SBGL_INVALID_HANDLE)
1945 return;
1946 uint32_t index = (uint32_t)handle - 1;
1947 if (index >= ctx->limits.maxPipelines || !ctx->pipelines[index].active)
1948 return;
1949
1950 ctx->vk.vkCmdBindPipeline(
1951 ctx->commandBuffers[ctx->currentFrame],
1952 VK_PIPELINE_BIND_POINT_GRAPHICS,
1953 ctx->pipelines[index].handle
1954 );
1955 ctx->boundPipeline = handle;
1956
1957 VkViewport viewport = {
1958 .x = 0.0f,
1959 .y = (float)ctx->swapchainExtent.height,
1960 .width = (float)ctx->swapchainExtent.width,
1961 .height = -(float)ctx->swapchainExtent.height,
1962 .minDepth = 0.0f,
1963 .maxDepth = 1.0f,
1964 };
1965 ctx->vk.vkCmdSetViewport(ctx->commandBuffers[ctx->currentFrame], 0, 1, &viewport);
1966
1967 VkRect2D scissor = { .offset = { 0, 0 }, .extent = ctx->swapchainExtent };
1968 ctx->vk.vkCmdSetScissor(ctx->commandBuffers[ctx->currentFrame], 0, 1, &scissor);
1969}
SBGL_VulkanPipeline * pipelines
sbgl_Pipeline boundPipeline

◆ sbgl_gfx_CreateBuffer()

sbgl_Buffer sbgl_gfx_CreateBuffer ( sbgl_GfxContext * ctx,
sbgl_BufferUsage usage,
size_t size,
const void * data )

Definition at line 1324 of file sbgl_backend_vulkan.c.

1324 {
1325 /* Search for an available buffer slot in the internal tracking arrays. */
1326 uint32_t index = 0;
1327 for (; index < ctx->limits.maxBuffers; index++) {
1328 if (!ctx->bufferActive[index])
1329 break;
1330 }
1331 if (index == ctx->limits.maxBuffers)
1332 return SBGL_INVALID_HANDLE;
1333
1334 /* Identify the target memory heap based on the buffer's intended usage.
1335 Vertex and index buffers are assigned to the static heap, while storage
1336 buffers utilize the managed heap for persistence. */
1339 heapType = SBGL_HEAP_TYPE_STATIC;
1340 } else if (usage & SBGL_BUFFER_USAGE_STORAGE) {
1341 heapType = SBGL_HEAP_TYPE_MANAGED;
1342 }
1343
1344 VkBufferCreateInfo bufferInfo = {
1345 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
1346 .size = size,
1347 .usage = (usage & SBGL_BUFFER_USAGE_VERTEX ? VK_BUFFER_USAGE_VERTEX_BUFFER_BIT : 0) |
1348 (usage & SBGL_BUFFER_USAGE_INDEX ? VK_BUFFER_USAGE_INDEX_BUFFER_BIT : 0) |
1349 (usage & SBGL_BUFFER_USAGE_STORAGE ? VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : 0) |
1350 (usage & SBGL_BUFFER_USAGE_INDIRECT ? VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT : 0) |
1351 (usage & SBGL_BUFFER_USAGE_TRANSFER_DST ? VK_BUFFER_USAGE_TRANSFER_DST_BIT : 0) |
1352 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
1353 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
1354 };
1355
1356 SBGL_VulkanBuffer* buffer = &ctx->buffers[index];
1357 if (ctx->vk.vkCreateBuffer(ctx->device, &bufferInfo, NULL, &buffer->handle) != VK_SUCCESS) {
1358 return SBGL_INVALID_HANDLE;
1359 }
1360
1361 VkMemoryRequirements memRequirements;
1362 ctx->vk.vkGetBufferMemoryRequirements(ctx->device, buffer->handle, &memRequirements);
1363
1364 /* Sub-allocate the required memory range from the selected hybrid heap. */
1365 uint32_t offset = SBGL_INVALID_OFFSET;
1366 VkDeviceMemory heapMemory = VK_NULL_HANDLE;
1367 void* heapMappedBase = NULL;
1368
1369 switch (heapType) {
1371 offset = static_heap_alloc(ctx, memRequirements.size);
1372 heapMemory = ctx->staticHeap.memory;
1373 heapMappedBase = ctx->staticHeap.mapped;
1374 break;
1376 offset = dynamic_heap_alloc(ctx, memRequirements.size);
1377 heapMemory = ctx->dynamicHeap.memory;
1378 heapMappedBase = ctx->dynamicHeap.mapped[ctx->currentFrame];
1379 break;
1381 offset = managed_heap_alloc(ctx, memRequirements.size);
1382 heapMemory = ctx->managedHeap.memory;
1383 heapMappedBase = ctx->managedHeap.mapped;
1384 break;
1385 }
1386
1387 if (offset == SBGL_INVALID_OFFSET) {
1388 ctx->vk.vkDestroyBuffer(ctx->device, buffer->handle, NULL);
1389 return SBGL_INVALID_HANDLE;
1390 }
1391
1392 /* Bind the buffer handle to the sub-allocated memory region within the heap. */
1393 ctx->vk.vkBindBufferMemory(ctx->device, buffer->handle, heapMemory, offset);
1394
1395 buffer->size = size;
1396 buffer->offset = offset;
1397 buffer->heapType = heapType;
1398 buffer->mapped = (char*)heapMappedBase + offset;
1399 ctx->bufferActive[index] = true;
1400
1401 /* If initial data is provided, perform an immediate memory copy to the
1402 persistently mapped buffer address. */
1403 if (data && buffer->mapped) {
1404 memcpy(buffer->mapped, data, size);
1405 }
1406
1407 return (sbgl_Buffer)(index + 1);
1408}
static uint32_t static_heap_alloc(sbgl_GfxContext *ctx, size_t size)
static uint32_t dynamic_heap_alloc(sbgl_GfxContext *ctx, size_t size)
static uint32_t managed_heap_alloc(sbgl_GfxContext *ctx, size_t size)
@ SBGL_BUFFER_USAGE_TRANSFER_DST
Definition sbgl_types.h:127
uint32_t sbgl_Buffer
Handle for a GPU-side buffer.
Definition sbgl_types.h:37

◆ sbgl_gfx_CreateComputePipeline()

sbgl_ComputePipeline sbgl_gfx_CreateComputePipeline ( sbgl_GfxContext * ctx,
sbgl_Shader handle )

Definition at line 1764 of file sbgl_backend_vulkan.c.

1764 {
1765 /* The system scans the internal pipeline storage for an available slot to allocate
1766 the new compute pipeline state. */
1767 uint32_t index = 0;
1768 for (; index < ctx->limits.maxPipelines; index++) {
1769 if (!ctx->computePipelines[index].active)
1770 break;
1771 }
1772 if (index == ctx->limits.maxPipelines)
1773 return SBGL_INVALID_HANDLE;
1774
1775 if (handle == SBGL_INVALID_HANDLE || handle > ctx->limits.maxShaders) {
1776 fprintf(stderr, "[Vulkan] Invalid compute shader handle\n");
1777 return SBGL_INVALID_HANDLE;
1778 }
1779 uint32_t shaderIndex = handle - 1;
1780 if (!ctx->shaders[shaderIndex].active || ctx->shaders[shaderIndex].stage != SBGL_SHADER_STAGE_COMPUTE) {
1781 fprintf(stderr, "[Vulkan] Invalid compute shader stage or inactive shader\n");
1782 return SBGL_INVALID_HANDLE;
1783 }
1784
1785 VkPipelineShaderStageCreateInfo stageInfo = {
1786 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
1787 .stage = VK_SHADER_STAGE_COMPUTE_BIT,
1788 .module = ctx->shaders[shaderIndex].module,
1789 .pName = "main",
1790 };
1791
1792 VkPipelineLayoutCreateInfo layoutInfo = {
1793 .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
1794 };
1795
1796 /* The system utilizes a standardized push constant block across both graphics
1797 and compute pipelines to maintain architectural consistency. */
1798 VkPushConstantRange pushConstantRange = {
1799 .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
1800 .offset = 0,
1802 };
1803 layoutInfo.pushConstantRangeCount = 1;
1804 layoutInfo.pPushConstantRanges = &pushConstantRange;
1805
1806 if (ctx->vk.vkCreatePipelineLayout(
1807 ctx->device,
1808 &layoutInfo,
1809 NULL,
1810 &ctx->computePipelines[index].layout
1811 ) != VK_SUCCESS) {
1812 return SBGL_INVALID_HANDLE;
1813 }
1814
1815 VkComputePipelineCreateInfo pipelineInfo = {
1816 .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
1817 .stage = stageInfo,
1818 .layout = ctx->computePipelines[index].layout,
1819 };
1820
1821 if (ctx->vk.vkCreateComputePipelines(
1822 ctx->device,
1823 VK_NULL_HANDLE,
1824 1,
1825 &pipelineInfo,
1826 NULL,
1827 &ctx->computePipelines[index].handle
1828 ) != VK_SUCCESS) {
1829 ctx->vk.vkDestroyPipelineLayout(ctx->device, ctx->computePipelines[index].layout, NULL);
1830 return SBGL_INVALID_HANDLE;
1831 }
1832
1833 ctx->computePipelines[index].active = true;
1834 return (sbgl_ComputePipeline)(index + 1);
1835}
#define SBGL_VK_PUSH_CONSTANT_SIZE
@ SBGL_SHADER_STAGE_COMPUTE
Definition sbgl_types.h:136
uint32_t sbgl_ComputePipeline
Handle for a compute pipeline.
Definition sbgl_types.h:52
sbgl_ShaderStage stage
SBGL_VulkanShader * shaders

◆ sbgl_gfx_CreatePipeline()

sbgl_Pipeline sbgl_gfx_CreatePipeline ( sbgl_GfxContext * ctx,
const sbgl_PipelineConfig * config )

Definition at line 1551 of file sbgl_backend_vulkan.c.

1551 {
1552 uint32_t index = 0;
1553 for (; index < ctx->limits.maxPipelines; index++) {
1554 if (!ctx->pipelines[index].active)
1555 break;
1556 }
1557 if (index == ctx->limits.maxPipelines)
1558 return SBGL_INVALID_HANDLE;
1559
1560 VkPipelineShaderStageCreateInfo shaderStages[2] = { 0 };
1561
1562 // Vertex Shader
1563 if (config->vertexShader == SBGL_INVALID_HANDLE || config->vertexShader > ctx->limits.maxShaders) {
1564 fprintf(stderr, "[Vulkan] Invalid vertex shader handle\n");
1565 return SBGL_INVALID_HANDLE;
1566 }
1567 uint32_t vsIndex = config->vertexShader - 1;
1568 if (!ctx->shaders[vsIndex].active || ctx->shaders[vsIndex].stage != SBGL_SHADER_STAGE_VERTEX) {
1569 fprintf(stderr, "[Vulkan] Invalid vertex shader stage or inactive shader\n");
1570 return SBGL_INVALID_HANDLE;
1571 }
1572 shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1573 shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
1574 shaderStages[0].module = ctx->shaders[vsIndex].module;
1575 shaderStages[0].pName = "main";
1576
1577 // Fragment Shader
1578 if (config->fragmentShader == SBGL_INVALID_HANDLE || config->fragmentShader > ctx->limits.maxShaders) {
1579 fprintf(stderr, "[Vulkan] Invalid fragment shader handle\n");
1580 return SBGL_INVALID_HANDLE;
1581 }
1582 uint32_t fsIndex = config->fragmentShader - 1;
1583 if (!ctx->shaders[fsIndex].active || ctx->shaders[fsIndex].stage != SBGL_SHADER_STAGE_FRAGMENT) {
1584 fprintf(stderr, "[Vulkan] Invalid fragment shader stage or inactive shader\n");
1585 return SBGL_INVALID_HANDLE;
1586 }
1587 shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1588 shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1589 shaderStages[1].module = ctx->shaders[fsIndex].module;
1590 shaderStages[1].pName = "main";
1591
1592 VkVertexInputBindingDescription bindingDescription = {
1593 .binding = 0,
1594 .stride = config->vertexLayout.stride,
1595 .inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
1596 };
1597
1598 SblArenaMark mark = sbl_arena_mark(ctx->arena);
1599 VkVertexInputAttributeDescription* attributeDescriptions = SBL_ARENA_PUSH_ARRAY(
1600 ctx->arena,
1601 VkVertexInputAttributeDescription,
1603 );
1604 if (!attributeDescriptions && config->vertexLayout.attributeCount > 0) {
1605 return SBGL_INVALID_HANDLE;
1606 }
1607 for (uint32_t i = 0; i < config->vertexLayout.attributeCount; i++) {
1608 attributeDescriptions[i].binding = 0;
1609 attributeDescriptions[i].location = config->vertexLayout.attributes[i].location;
1610 attributeDescriptions[i].format =
1612 attributeDescriptions[i].offset = config->vertexLayout.attributes[i].offset;
1613 }
1614
1615 VkPipelineVertexInputStateCreateInfo vertexInputInfo = {
1616 .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
1617 .vertexBindingDescriptionCount = 1,
1618 .pVertexBindingDescriptions = &bindingDescription,
1619 .vertexAttributeDescriptionCount = config->vertexLayout.attributeCount,
1620 .pVertexAttributeDescriptions = attributeDescriptions,
1621 };
1622
1623 VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
1624 .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
1625 .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
1626 .primitiveRestartEnable = VK_FALSE,
1627 };
1628
1629 VkPipelineViewportStateCreateInfo viewportState = {
1630 .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
1631 .viewportCount = 1,
1632 .scissorCount = 1,
1633 };
1634
1635 VkPipelineRasterizationStateCreateInfo rasterizer = {
1636 .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
1637 .depthClampEnable = VK_FALSE,
1638 .rasterizerDiscardEnable = VK_FALSE,
1639 .polygonMode = VK_POLYGON_MODE_FILL,
1640 .lineWidth = 1.0f,
1641 .cullMode = VK_CULL_MODE_BACK_BIT,
1642 .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
1643 .depthBiasEnable = VK_FALSE,
1644 };
1645
1646 VkPipelineMultisampleStateCreateInfo multisampling = {
1647 .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
1648 .sampleShadingEnable = VK_FALSE,
1649 .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
1650 };
1651
1652 VkPipelineDepthStencilStateCreateInfo depthStencil = {
1653 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
1654 .depthTestEnable = VK_TRUE,
1655 .depthWriteEnable = VK_TRUE,
1656 .depthCompareOp = VK_COMPARE_OP_LESS,
1657 .depthBoundsTestEnable = VK_FALSE,
1658 .stencilTestEnable = VK_FALSE,
1659 };
1660
1661 VkPipelineColorBlendAttachmentState colorBlendAttachment = {
1662 .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1663 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
1664 .blendEnable = (config->blendMode != SBGL_BLEND_MODE_NONE) ? VK_TRUE : VK_FALSE,
1665 .srcColorBlendFactor = (config->blendMode == SBGL_BLEND_MODE_ADDITIVE) ? VK_BLEND_FACTOR_ONE : VK_BLEND_FACTOR_SRC_ALPHA,
1666 .dstColorBlendFactor = (config->blendMode == SBGL_BLEND_MODE_ADDITIVE) ? VK_BLEND_FACTOR_ONE : VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
1667 .colorBlendOp = VK_BLEND_OP_ADD,
1668 .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
1669 .dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
1670 .alphaBlendOp = VK_BLEND_OP_ADD,
1671 };
1672
1673 VkPipelineColorBlendStateCreateInfo colorBlending = {
1674 .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
1675 .logicOpEnable = VK_FALSE,
1676 .attachmentCount = 1,
1677 .pAttachments = &colorBlendAttachment,
1678 };
1679
1680 VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
1681 VkPipelineDynamicStateCreateInfo dynamicState = {
1682 .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
1683 .dynamicStateCount = 2,
1684 .pDynamicStates = dynamicStates,
1685 };
1686
1687 VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
1688 .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
1689 };
1690
1691 VkPushConstantRange pushConstantRange = {
1692 .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
1693 .offset = 0,
1695 };
1696 pipelineLayoutInfo.pushConstantRangeCount = 1;
1697 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
1698
1699 if (ctx->vk.vkCreatePipelineLayout(
1700 ctx->device,
1701 &pipelineLayoutInfo,
1702 NULL,
1703 &ctx->pipelines[index].layout
1704 ) != VK_SUCCESS) {
1705 sbl_arena_rewind(ctx->arena, mark);
1706 return SBGL_INVALID_HANDLE;
1707 }
1708
1709 VkPipelineRenderingCreateInfo renderingCreateInfo = {
1710 .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
1711 .colorAttachmentCount = 1,
1712 .pColorAttachmentFormats = &ctx->swapchainFormat,
1713 .depthAttachmentFormat = ctx->depthFormat,
1714 };
1715
1716 VkGraphicsPipelineCreateInfo pipelineInfo = {
1717 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
1718 .pNext = &renderingCreateInfo,
1719 .stageCount = 2,
1720 .pStages = shaderStages,
1721 .pVertexInputState = &vertexInputInfo,
1722 .pInputAssemblyState = &inputAssembly,
1723 .pViewportState = &viewportState,
1724 .pRasterizationState = &rasterizer,
1725 .pMultisampleState = &multisampling,
1726 .pDepthStencilState = &depthStencil,
1727 .pColorBlendState = &colorBlending,
1728 .pDynamicState = &dynamicState,
1729 .layout = ctx->pipelines[index].layout,
1730 .renderPass = VK_NULL_HANDLE,
1731 .subpass = 0,
1732 };
1733
1734 if (ctx->vk.vkCreateGraphicsPipelines(
1735 ctx->device,
1736 VK_NULL_HANDLE,
1737 1,
1738 &pipelineInfo,
1739 NULL,
1740 &ctx->pipelines[index].handle
1741 ) != VK_SUCCESS) {
1742 ctx->vk.vkDestroyPipelineLayout(ctx->device, ctx->pipelines[index].layout, NULL);
1743 sbl_arena_rewind(ctx->arena, mark);
1744 return SBGL_INVALID_HANDLE;
1745 }
1746
1747 sbl_arena_rewind(ctx->arena, mark);
1748 ctx->pipelines[index].active = true;
1749 return (sbgl_Pipeline)(index + 1);
1750}
static VkFormat sbgl_to_vk_format(sbgl_Format format)
@ SBGL_BLEND_MODE_ADDITIVE
Definition sbgl_types.h:198
@ SBGL_BLEND_MODE_NONE
Definition sbgl_types.h:196
@ SBGL_SHADER_STAGE_FRAGMENT
Definition sbgl_types.h:135
@ SBGL_SHADER_STAGE_VERTEX
Definition sbgl_types.h:134
uint32_t sbgl_Pipeline
Handle for a graphics pipeline.
Definition sbgl_types.h:47
VkPipelineLayout layout
sbgl_Shader fragmentShader
Definition sbgl_types.h:206
sbgl_Shader vertexShader
Definition sbgl_types.h:205
sbgl_BlendMode blendMode
Definition sbgl_types.h:208
sbgl_VertexLayout vertexLayout
Definition sbgl_types.h:207
const sbgl_VertexAttribute * attributes
Definition sbgl_types.h:189
uint32_t attributeCount
Definition sbgl_types.h:188

◆ sbgl_gfx_DestroyBuffer()

void sbgl_gfx_DestroyBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer handle )

Definition at line 1410 of file sbgl_backend_vulkan.c.

1410 {
1411 /* The system releases the GPU-side buffer handle and, if the memory was
1412 allocated from the managed heap, returns the range to the sub-allocator
1413 to mitigate memory fragmentation. Static and Dynamic allocations are
1414 reclaimed automatically or persist until shutdown. */
1415 if (handle == SBGL_INVALID_HANDLE)
1416 return;
1417 uint32_t index = (uint32_t)handle - 1;
1418 if (index >= ctx->limits.maxBuffers || !ctx->bufferActive[index])
1419 return;
1420
1421 SBGL_VulkanBuffer* buffer = &ctx->buffers[index];
1422 ctx->vk.vkDestroyBuffer(ctx->device, buffer->handle, NULL);
1423
1424 if (buffer->heapType == SBGL_HEAP_TYPE_MANAGED) {
1425 managed_heap_free(ctx, buffer->offset);
1426 }
1427
1428 ctx->bufferActive[index] = false;
1429}
static void managed_heap_free(sbgl_GfxContext *ctx, uint32_t offset)

◆ sbgl_gfx_DestroyBufferDeferred()

void sbgl_gfx_DestroyBufferDeferred ( sbgl_GfxContext * ctx,
sbgl_Buffer buffer )

Marks a buffer for destruction after current frames complete.

This function should be used for temporary buffers that are submitted for GPU execution in the current frame and must not be destroyed until the GPU has finished using them.

Parameters
ctxThe graphics context.
bufferHandle to the buffer to destroy.

Definition at line 1480 of file sbgl_backend_vulkan.c.

1480 {
1481 /* The system queues the buffer for destruction after the current frame's GPU work
1482 is guaranteed to be complete, preventing premature release of in-flight resources. */
1483 if (ctx->deferredCount[ctx->currentFrame] < 64) {
1484 ctx->deferredBuffers[ctx->currentFrame][ctx->deferredCount[ctx->currentFrame]++] = handle;
1485 } else {
1486 /* If the deferred queue is full, the system falls back to immediate destruction
1487 after a device idle wait to maintain safety at the cost of performance. */
1489 sbgl_gfx_DestroyBuffer(ctx, handle);
1490 }
1491}
void sbgl_gfx_DeviceWaitIdle(sbgl_GfxContext *ctx)

◆ sbgl_gfx_DestroyComputePipeline()

void sbgl_gfx_DestroyComputePipeline ( sbgl_GfxContext * ctx,
sbgl_ComputePipeline handle )

Definition at line 1837 of file sbgl_backend_vulkan.c.

1837 {
1838 /* The system releases the GPU-side pipeline and layout resources and marks
1839 the internal slot as inactive for future reuse. */
1840 if (handle == SBGL_INVALID_HANDLE)
1841 return;
1842 uint32_t index = (uint32_t)handle - 1;
1843 if (index >= ctx->limits.maxPipelines || !ctx->computePipelines[index].active)
1844 return;
1845
1846 ctx->vk.vkDestroyPipeline(ctx->device, ctx->computePipelines[index].handle, NULL);
1847 ctx->vk.vkDestroyPipelineLayout(ctx->device, ctx->computePipelines[index].layout, NULL);
1848 ctx->computePipelines[index].active = false;
1849}

◆ sbgl_gfx_DestroyPipeline()

void sbgl_gfx_DestroyPipeline ( sbgl_GfxContext * ctx,
sbgl_Pipeline handle )

Definition at line 1752 of file sbgl_backend_vulkan.c.

1752 {
1753 if (handle == SBGL_INVALID_HANDLE)
1754 return;
1755 uint32_t index = (uint32_t)handle - 1;
1756 if (index >= ctx->limits.maxPipelines || !ctx->pipelines[index].active)
1757 return;
1758
1759 ctx->vk.vkDestroyPipeline(ctx->device, ctx->pipelines[index].handle, NULL);
1760 ctx->vk.vkDestroyPipelineLayout(ctx->device, ctx->pipelines[index].layout, NULL);
1761 ctx->pipelines[index].active = false;
1762}

◆ sbgl_gfx_DestroyShader()

void sbgl_gfx_DestroyShader ( sbgl_GfxContext * ctx,
sbgl_Shader handle )

Definition at line 1540 of file sbgl_backend_vulkan.c.

1540 {
1541 if (handle == SBGL_INVALID_HANDLE)
1542 return;
1543 uint32_t index = (uint32_t)handle - 1;
1544 if (index >= ctx->limits.maxShaders || !ctx->shaders[index].active)
1545 return;
1546
1547 ctx->vk.vkDestroyShaderModule(ctx->device, ctx->shaders[index].module, NULL);
1548 ctx->shaders[index].active = false;
1549}

◆ sbgl_gfx_DeviceWaitIdle()

void sbgl_gfx_DeviceWaitIdle ( sbgl_GfxContext * ctx)

Definition at line 1317 of file sbgl_backend_vulkan.c.

1317 {
1318 if (ctx && ctx->device) {
1319 ctx->vk.vkDeviceWaitIdle(ctx->device);
1320 }
1321}

◆ sbgl_gfx_DispatchCompute()

void sbgl_gfx_DispatchCompute ( sbgl_GfxContext * ctx,
uint32_t x,
uint32_t y,
uint32_t z )

Definition at line 1870 of file sbgl_backend_vulkan.c.

1870 {
1871 /* A compute dispatch command is recorded into the current frame's command buffer,
1872 triggering parallel execution across the specified workgroup dimensions. */
1873 ctx->vk.vkCmdDispatch(ctx->commandBuffers[ctx->currentFrame], x, y, z);
1874}

◆ sbgl_gfx_Draw()

void sbgl_gfx_Draw ( sbgl_GfxContext * ctx,
uint32_t vertexCount,
uint32_t firstVertex,
uint32_t instanceCount )

Definition at line 1997 of file sbgl_backend_vulkan.c.

1997 {
1998 ctx->vk.vkCmdDraw(ctx->commandBuffers[ctx->currentFrame], vertexCount, instanceCount, firstVertex, 0);
1999}

◆ sbgl_gfx_DrawIndexed()

void sbgl_gfx_DrawIndexed ( sbgl_GfxContext * ctx,
uint32_t indexCount,
uint32_t firstIndex,
int32_t vertexOffset,
uint32_t instanceCount )

Definition at line 2001 of file sbgl_backend_vulkan.c.

2007 {
2008 ctx->vk.vkCmdDrawIndexed(
2009 ctx->commandBuffers[ctx->currentFrame],
2010 indexCount,
2011 instanceCount,
2012 firstIndex,
2013 vertexOffset,
2014 0
2015 );
2016}

◆ sbgl_gfx_DrawIndirect()

void sbgl_gfx_DrawIndirect ( sbgl_GfxContext * ctx,
sbgl_Buffer buffer,
size_t offset,
uint32_t drawCount )

Submits a batch of draw calls stored in a GPU buffer.

Parameters
ctxThe graphics context.
bufferHandle to the buffer containing an array of sbgl_IndirectCommand.
offsetThe byte offset into the buffer where the commands begin.
drawCountThe number of commands to execute from the buffer.

Definition at line 2018 of file sbgl_backend_vulkan.c.

2023 {
2024 if (handle == SBGL_INVALID_HANDLE)
2025 return;
2026 uint32_t index = (uint32_t)handle - 1;
2027 if (index >= ctx->limits.maxBuffers || !ctx->bufferActive[index])
2028 return;
2029
2030 ctx->vk.vkCmdDrawIndexedIndirect(
2031 ctx->commandBuffers[ctx->currentFrame],
2032 ctx->buffers[index].handle,
2033 (VkDeviceSize)offset,
2034 drawCount,
2035 sizeof(sbgl_IndirectCommand)
2036 );
2037}
Standard Vulkan Indirect Draw command layout.
Definition sbgl_types.h:111

◆ sbgl_gfx_EndFrame()

void sbgl_gfx_EndFrame ( sbgl_GfxContext * ctx)

Submits the current frame's commands and presents the image.

Definition at line 1277 of file sbgl_backend_vulkan.c.

1277 {
1278 ctx->vk.vkEndCommandBuffer(ctx->commandBuffers[ctx->currentFrame]);
1279
1280 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1281
1282 /* The system utilizes a semaphore indexed by the current image to signal completion
1283 to the presentation engine, preventing reuse conflicts during high-frequency updates. */
1284 VkSemaphore signalSemaphore = ctx->renderFinishedSemaphores[ctx->currentImageIndex];
1285
1286 VkSubmitInfo submitInfo = {
1287 .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
1288 .waitSemaphoreCount = 1,
1289 .pWaitSemaphores = &ctx->imageAvailableSemaphores[ctx->semaphoreIndex],
1290 .pWaitDstStageMask = waitStages,
1291 .commandBufferCount = 1,
1292 .pCommandBuffers = &ctx->commandBuffers[ctx->currentFrame],
1293 .signalSemaphoreCount = 1,
1294 .pSignalSemaphores = &signalSemaphore,
1295 };
1296 ctx->vk
1297 .vkQueueSubmit(ctx->graphicsQueue, 1, &submitInfo, ctx->inFlightFences[ctx->currentFrame]);
1298
1299 VkPresentInfoKHR presentInfo = {
1300 .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
1301 .waitSemaphoreCount = 1,
1302 .pWaitSemaphores = &signalSemaphore,
1303 .swapchainCount = 1,
1304 .pSwapchains = &ctx->swapchain,
1305 .pImageIndices = &ctx->currentImageIndex,
1306 };
1307 VkResult result = ctx->vk.vkQueuePresentKHR(ctx->graphicsQueue, &presentInfo);
1308
1309 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
1310 recreate_swapchain(ctx);
1311 }
1312
1315}

◆ sbgl_gfx_EndRenderPass()

void sbgl_gfx_EndRenderPass ( sbgl_GfxContext * ctx)

Ends the current graphics rendering pass.

Definition at line 1240 of file sbgl_backend_vulkan.c.

1240 {
1241 /* The system records the ending timestamp at the conclusion of the frame's rendering commands.
1242 */
1243 ctx->vk.vkCmdWriteTimestamp(
1244 ctx->commandBuffers[ctx->currentFrame],
1245 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
1246 ctx->queryPool,
1247 ctx->currentFrame * 2 + 1
1248 );
1249
1250 ctx->vk.vkCmdEndRendering(ctx->commandBuffers[ctx->currentFrame]);
1251
1252 VkImageMemoryBarrier barrier = {
1253 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
1254 .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
1255 .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
1256 .image = ctx->images[ctx->currentImageIndex],
1257 .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
1258 .levelCount = 1,
1259 .layerCount = 1 },
1260 .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
1261 .dstAccessMask = 0,
1262 };
1263 ctx->vk.vkCmdPipelineBarrier(
1264 ctx->commandBuffers[ctx->currentFrame],
1265 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
1266 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
1267 0,
1268 0,
1269 NULL,
1270 0,
1271 NULL,
1272 1,
1273 &barrier
1274 );
1275}

◆ sbgl_gfx_FillBuffer()

void sbgl_gfx_FillBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer handle,
size_t offset,
size_t size,
uint32_t value )

Performs a hardware-accelerated buffer fill.

Definition at line 1431 of file sbgl_backend_vulkan.c.

1437 {
1438 /* A hardware-accelerated fill operation is recorded into the current frame's
1439 command buffer, utilizing the GPU's DMA engine for maximum performance. */
1440 if (handle == SBGL_INVALID_HANDLE)
1441 return;
1442 uint32_t index = (uint32_t)handle - 1;
1443 if (index >= ctx->limits.maxBuffers || !ctx->bufferActive[index])
1444 return;
1445
1446 ctx->vk.vkCmdFillBuffer(
1447 ctx->commandBuffers[ctx->currentFrame],
1448 ctx->buffers[index].handle,
1449 (VkDeviceSize)offset,
1450 (VkDeviceSize)size,
1451 value
1452 );
1453}

◆ sbgl_gfx_GetBufferDeviceAddress()

uint64_t sbgl_gfx_GetBufferDeviceAddress ( sbgl_GfxContext * ctx,
sbgl_Buffer buffer )

Retrieves the 64-bit GPU virtual address for a buffer.

Used primarily for passing buffer pointers to shaders via push constants or storage buffers when using VK_KHR_buffer_device_address.

Parameters
ctxThe graphics context.
bufferThe buffer to query.
Returns
The 64-bit device address, or 0 if retrieval failed.

Definition at line 1493 of file sbgl_backend_vulkan.c.

1493 {
1494 /* The system retrieves the 64-bit GPU virtual address for the specified buffer,
1495 enabling direct memory access within shaders via Buffer Device Address. */
1496 if (handle == SBGL_INVALID_HANDLE)
1497 return 0;
1498 uint32_t index = (uint32_t)handle - 1;
1499 if (index >= ctx->limits.maxBuffers || !ctx->bufferActive[index])
1500 return 0;
1501
1502 VkBufferDeviceAddressInfo info = {
1503 .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
1504 .buffer = ctx->buffers[index].handle,
1505 };
1506
1507 return ctx->vk.vkGetBufferDeviceAddress(ctx->device, &info);
1508}

◆ sbgl_gfx_GetFrameIndex()

uint32_t sbgl_gfx_GetFrameIndex ( sbgl_GfxContext * ctx)

Retrieves the current backend frame index.

Definition at line 1455 of file sbgl_backend_vulkan.c.

1455 {
1456 /* Returns the current frame index, which is used by the core engine to
1457 manage multi-buffered resources. */
1458 return ctx->currentFrame;
1459}

◆ sbgl_gfx_GetGpuTime()

float sbgl_gfx_GetGpuTime ( sbgl_GfxContext * ctx)

Retrieves the elapsed GPU time for the previous frame in milliseconds.

Parameters
ctxThe graphics context.
Returns
The duration in milliseconds.

Definition at line 2100 of file sbgl_backend_vulkan.c.

2100 {
2101 /* The system retrieves the recorded timestamps from the GPU and calculates the elapsed time
2102 in milliseconds, providing a non-blocking performance measurement. */
2103 uint64_t results[2] = { 0 };
2104 VkResult res = ctx->vk.vkGetQueryPoolResults(
2105 ctx->device,
2106 ctx->queryPool,
2107 ctx->currentFrame * 2,
2108 2,
2109 sizeof(results),
2110 results,
2111 sizeof(uint64_t),
2112 VK_QUERY_RESULT_64_BIT
2113 );
2114
2115 if (res == VK_SUCCESS) {
2116 uint64_t start = results[0];
2117 uint64_t end = results[1];
2118 return (float)(end - start) * ctx->timestampPeriod / 1e6f;
2119 }
2120
2121 return 0.0f;
2122}

◆ sbgl_gfx_GetLastVkResult()

int32_t sbgl_gfx_GetLastVkResult ( sbgl_GfxContext * ctx)

Retrieves the last VkResult from the backend for error inspection.

Parameters
ctxThe graphics context.
Returns
The last VkResult code, or 0 if no error occurred.

Definition at line 2124 of file sbgl_backend_vulkan.c.

2124 {
2125 if (!ctx) return 0;
2126 return ctx->backendResult;
2127}

◆ sbgl_gfx_Init()

sbgl_GfxContext * sbgl_gfx_Init ( sbgl_Window * window,
struct SblArena * arena,
const sbgl_ResourceLimits * limits,
bool enableValidation )

Initializes the graphics backend with configurable resource limits.

Parameters
windowThe platform window handle.
arenaThe arena for persistent allocations.
limitsPointer to resource limits (must not be NULL).
enableValidationWhether to enable Vulkan validation layers.
Returns
A pointer to the graphics context, or NULL on failure.

Definition at line 986 of file sbgl_backend_vulkan.c.

986 {
987 if (volkInitialize() != VK_SUCCESS) {
988 fprintf(stderr, "[Vulkan] Failed to initialize volk\n");
989 return NULL;
990 }
991
993 if (!ctx)
994 return NULL;
995
996 ctx->window = window;
997 ctx->arena = arena;
998
999 // Apply resource limits (use defaults if not provided)
1000 if (limits) {
1001 ctx->limits = *limits;
1002 // Enforce minimums to prevent crashes
1003 if (ctx->limits.maxBuffers < 64) ctx->limits.maxBuffers = 64;
1004 if (ctx->limits.maxShaders < 16) ctx->limits.maxShaders = 16;
1005 if (ctx->limits.maxPipelines < 16) ctx->limits.maxPipelines = 16;
1006 } else {
1008 }
1009
1010 // Dynamically allocate resource arrays from the arena
1011 // Use raw byte allocation since SBGL_Vulkan* types are defined later in this file
1012 ctx->bufferActive = (bool*)sbl_arena_alloc_zero(arena, sizeof(bool) * ctx->limits.maxBuffers);
1017
1018 if (!ctx->bufferActive || !ctx->buffers || !ctx->shaders || !ctx->pipelines || !ctx->computePipelines) {
1019 fprintf(stderr, "[Vulkan] Failed to allocate resource arrays\n");
1020 sbgl_gfx_Shutdown(ctx);
1021 return NULL;
1022 }
1023
1024 if (!create_instance(ctx, enableValidation) || !create_surface(ctx, window) || !select_physical_device(ctx) ||
1025 !create_logical_device(ctx) || !create_heaps(ctx) || !create_swapchain(ctx, window) ||
1028 sbgl_gfx_Shutdown(ctx);
1029 return NULL;
1030 }
1031
1032 /* The query pool is reset on the host immediately after creation to ensure that all
1033 queries are in a valid state before the first attempt to retrieve results. */
1034 ctx->vk.vkResetQueryPool(ctx->device, ctx->queryPool, 0, SBGL_MAX_FRAMES_IN_FLIGHT * 2);
1035
1036 return ctx;
1037}
static bool create_sync_and_command(sbgl_GfxContext *ctx)
void sbgl_gfx_Shutdown(sbgl_GfxContext *ctx)
static bool create_instance(sbgl_GfxContext *ctx, bool enableValidation)
static bool create_heaps(sbgl_GfxContext *ctx)
static bool select_physical_device(sbgl_GfxContext *ctx)
static const sbgl_ResourceLimits sbgl_DefaultResourceLimits
static bool create_logical_device(sbgl_GfxContext *ctx)
static bool create_telemetry_resources(sbgl_GfxContext *ctx)
static bool create_transient_resources(sbgl_GfxContext *ctx)
static bool create_surface(sbgl_GfxContext *ctx, sbgl_Window *window)
SBL_ARENA_DEF void * sbl_arena_alloc_zero(SblArena *arena, uint64_t size)
#define SBL_ARENA_PUSH_STRUCT_ZERO(arena, type)
Definition sbl_arena.h:20

◆ sbgl_gfx_LoadShader()

sbgl_Shader sbgl_gfx_LoadShader ( sbgl_GfxContext * ctx,
sbgl_ShaderStage stage,
const uint32_t * bytecode,
size_t size )

Definition at line 1510 of file sbgl_backend_vulkan.c.

1515 {
1516 uint32_t index = 0;
1517 for (; index < ctx->limits.maxShaders; index++) {
1518 if (!ctx->shaders[index].active)
1519 break;
1520 }
1521 if (index == ctx->limits.maxShaders)
1522 return SBGL_INVALID_HANDLE;
1523
1524 VkShaderModuleCreateInfo createInfo = {
1525 .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
1526 .codeSize = size,
1527 .pCode = bytecode,
1528 };
1529
1530 if (ctx->vk.vkCreateShaderModule(ctx->device, &createInfo, NULL, &ctx->shaders[index].module) !=
1531 VK_SUCCESS) {
1532 return SBGL_INVALID_HANDLE;
1533 }
1534
1535 ctx->shaders[index].stage = stage;
1536 ctx->shaders[index].active = true;
1537 return (sbgl_Shader)(index + 1);
1538}
uint32_t sbgl_Shader
Handle for a shader module.
Definition sbgl_types.h:42

◆ sbgl_gfx_MapBuffer()

void * sbgl_gfx_MapBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer handle )

Definition at line 1461 of file sbgl_backend_vulkan.c.

1461 {
1462 /* The system returns the persistently mapped pointer for the specified buffer,
1463 enabling high-performance data updates without the overhead of repeated mapping. */
1464 if (handle == SBGL_INVALID_HANDLE)
1465 return NULL;
1466 uint32_t index = (uint32_t)handle - 1;
1467 if (index >= ctx->limits.maxBuffers || !ctx->bufferActive[index])
1468 return NULL;
1469
1470 return ctx->buffers[index].mapped;
1471}

◆ sbgl_gfx_MemoryBarrier()

void sbgl_gfx_MemoryBarrier ( sbgl_GfxContext * ctx,
sbgl_BarrierType type )

Definition at line 1876 of file sbgl_backend_vulkan.c.

1876 {
1877 /* The system injects a pipeline barrier into the command stream to synchronize
1878 memory access between different execution stages, preventing race conditions. */
1879 VkMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER };
1880 VkPipelineStageFlags srcStage = 0;
1881 VkPipelineStageFlags dstStage = 0;
1882
1883 switch (type) {
1885 /* Synchronizes compute and transfer (fill) writes to be visible to
1886 subsequent compute operations. */
1887 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
1888 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
1889 srcStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
1890 dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
1891 break;
1893 /* Synchronizes compute writes to SSBOs for use in indirect draw command buffers. */
1894 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
1895 barrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
1896 srcStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
1897 dstStage = VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
1898 break;
1900 /* Synchronizes compute writes to be visible to vertex input and shader stages. */
1901 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
1902 barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
1903 srcStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
1904 dstStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
1905 break;
1907 /* Synchronizes graphics writes to be visible to subsequent compute operations. */
1908 barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
1909 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
1910 srcStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1911 dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
1912 break;
1914 /* Synchronizes host writes to be visible to subsequent compute operations. */
1915 barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
1916 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
1917 srcStage = VK_PIPELINE_STAGE_HOST_BIT;
1918 dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
1919 break;
1921 /* Synchronizes host writes to be visible to subsequent graphics (vertex) operations. */
1922 barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
1923 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1924 srcStage = VK_PIPELINE_STAGE_HOST_BIT;
1925 dstStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
1926 break;
1927 }
1928
1929 ctx->vk.vkCmdPipelineBarrier(
1930 ctx->commandBuffers[ctx->currentFrame],
1931 srcStage,
1932 dstStage,
1933 0,
1934 1,
1935 &barrier,
1936 0,
1937 NULL,
1938 0,
1939 NULL
1940 );
1941}
@ SBGL_BARRIER_COMPUTE_TO_INDIRECT
Definition sbgl_types.h:144
@ SBGL_BARRIER_GRAPHICS_TO_COMPUTE
Definition sbgl_types.h:146
@ SBGL_BARRIER_COMPUTE_TO_COMPUTE
Definition sbgl_types.h:143
@ SBGL_BARRIER_HOST_TO_GRAPHICS
Definition sbgl_types.h:148
@ SBGL_BARRIER_HOST_TO_COMPUTE
Definition sbgl_types.h:147
@ SBGL_BARRIER_COMPUTE_TO_GRAPHICS
Definition sbgl_types.h:145

◆ sbgl_gfx_PushConstants()

void sbgl_gfx_PushConstants ( sbgl_GfxContext * ctx,
size_t size,
const void * data )

Definition at line 2067 of file sbgl_backend_vulkan.c.

2067 {
2068 /* Push constants are submitted to both the currently bound graphics and compute
2069 pipelines to ensure that metadata is available across all execution stages. */
2070 if (size > SBGL_VK_PUSH_CONSTANT_SIZE) {
2071 fprintf(stderr, "[Vulkan] Push constant size (%zu) exceeds maximum (%d)\n", size, SBGL_VK_PUSH_CONSTANT_SIZE);
2072 return;
2073 }
2074
2075 if (ctx->boundPipeline != SBGL_INVALID_HANDLE) {
2076 uint32_t index = (uint32_t)ctx->boundPipeline - 1;
2077 ctx->vk.vkCmdPushConstants(
2078 ctx->commandBuffers[ctx->currentFrame],
2079 ctx->pipelines[index].layout,
2080 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
2081 0,
2082 (uint32_t)size,
2083 data
2084 );
2085 }
2086
2088 uint32_t index = (uint32_t)ctx->boundComputePipeline - 1;
2089 ctx->vk.vkCmdPushConstants(
2090 ctx->commandBuffers[ctx->currentFrame],
2091 ctx->computePipelines[index].layout,
2092 VK_SHADER_STAGE_COMPUTE_BIT,
2093 0,
2094 (uint32_t)size,
2095 data
2096 );
2097 }
2098}

◆ sbgl_gfx_Shutdown()

void sbgl_gfx_Shutdown ( sbgl_GfxContext * ctx)

Definition at line 1039 of file sbgl_backend_vulkan.c.

1039 {
1040 if (!ctx)
1041 return;
1042
1043 if (ctx->device) {
1044 ctx->vk.vkDeviceWaitIdle(ctx->device);
1045
1046 // Clean up all active buffers
1047 for (uint32_t i = 0; i < ctx->limits.maxBuffers; i++) {
1048 if (ctx->bufferActive[i]) {
1049 sbgl_gfx_DestroyBuffer(ctx, (sbgl_Buffer)(i + 1));
1050 }
1051 }
1052
1053 // Clean up all active shaders
1054 for (uint32_t i = 0; i < ctx->limits.maxShaders; i++) {
1055 if (ctx->shaders[i].active) {
1056 sbgl_gfx_DestroyShader(ctx, (sbgl_Shader)(i + 1));
1057 }
1058 }
1059
1060 // Clean up all active pipelines
1061 for (uint32_t i = 0; i < ctx->limits.maxPipelines; i++) {
1062 if (ctx->pipelines[i].active) {
1064 }
1065 if (ctx->computePipelines[i].active) {
1067 }
1068 }
1069
1070 // Process any remaining deferred buffers
1071 for (uint32_t f = 0; f < SBGL_MAX_FRAMES_IN_FLIGHT; f++) {
1072 for (uint32_t i = 0; i < ctx->deferredCount[f]; i++) {
1073 sbgl_gfx_DestroyBuffer(ctx, ctx->deferredBuffers[f][i]);
1074 }
1075 ctx->deferredCount[f] = 0;
1076 }
1077
1078 for (uint32_t i = 0; i < SBGL_MAX_FRAMES_IN_FLIGHT; i++) {
1079 ctx->vk.vkDestroyFence(ctx->device, ctx->inFlightFences[i], NULL);
1080 }
1081
1082 for (uint32_t i = 0; i < SBGL_MAX_SWAPCHAIN_IMAGES; i++) {
1083 if (ctx->imageAvailableSemaphores[i] != VK_NULL_HANDLE) {
1084 ctx->vk.vkDestroySemaphore(ctx->device, ctx->imageAvailableSemaphores[i], NULL);
1085 }
1086 if (ctx->renderFinishedSemaphores[i] != VK_NULL_HANDLE) {
1087 ctx->vk.vkDestroySemaphore(ctx->device, ctx->renderFinishedSemaphores[i], NULL);
1088 }
1089 }
1090 ctx->vk.vkDestroyQueryPool(ctx->device, ctx->queryPool, NULL);
1091 ctx->vk.vkDestroyCommandPool(ctx->device, ctx->commandPool, NULL);
1092
1093 ctx->vk.vkFreeMemory(ctx->device, ctx->staticHeap.memory, NULL);
1094 ctx->vk.vkFreeMemory(ctx->device, ctx->dynamicHeap.memory, NULL);
1095 ctx->vk.vkFreeMemory(ctx->device, ctx->managedHeap.memory, NULL);
1096
1097 cleanup_swapchain(ctx);
1098 ctx->vk.vkDestroyDevice(ctx->device, NULL);
1099 }
1100 if (ctx->instance) {
1101 vkDestroySurfaceKHR(ctx->instance, ctx->surface, NULL);
1102 vkDestroyInstance(ctx->instance, NULL);
1103 }
1104}
void sbgl_gfx_DestroyShader(sbgl_GfxContext *ctx, sbgl_Shader handle)
void sbgl_gfx_DestroyPipeline(sbgl_GfxContext *ctx, sbgl_Pipeline handle)
void sbgl_gfx_DestroyComputePipeline(sbgl_GfxContext *ctx, sbgl_ComputePipeline handle)

◆ sbgl_gfx_UnmapBuffer()

void sbgl_gfx_UnmapBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer handle )

Definition at line 1473 of file sbgl_backend_vulkan.c.

1473 {
1474 /* Persistent mapping remains active for the buffer's lifecycle, so unmapping
1475 is a no-op to maintain API compatibility while maximizing performance. */
1476 (void)ctx;
1477 (void)handle;
1478}

◆ sbgl_setup_debug_utils()

static void sbgl_setup_debug_utils ( sbgl_GfxContext * ctx)
static

Definition at line 221 of file sbgl_backend_vulkan.c.

221 {
222 VkDebugUtilsMessengerCreateInfoEXT createInfo = {
223 .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
224 .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
225 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT,
226 .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
227 VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
228 .pfnUserCallback = sbgl_vk_debug_callback,
229 .pUserData = NULL,
230 };
231
232 PFN_vkCreateDebugUtilsMessengerEXT func =
233 (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
234 ctx->instance, "vkCreateDebugUtilsMessengerEXT");
235
236 if (!func) {
237 return;
238 }
239
240 VkDebugUtilsMessengerEXT messenger;
241 if (func(ctx->instance, &createInfo, NULL, &messenger) != VK_SUCCESS) {
242 return;
243 }
244
245 (void)messenger;
246}
static VKAPI_ATTR VkBool32 VKAPI_CALL sbgl_vk_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData)

◆ sbgl_to_vk_format()

static VkFormat sbgl_to_vk_format ( sbgl_Format format)
static

Definition at line 248 of file sbgl_backend_vulkan.c.

248 {
249 switch (format) {
251 return VK_FORMAT_R32_SFLOAT;
253 return VK_FORMAT_R32G32_SFLOAT;
255 return VK_FORMAT_R32G32B32_SFLOAT;
257 return VK_FORMAT_R32G32B32A32_SFLOAT;
259 return VK_FORMAT_R16G16B16A16_SNORM;
261 return VK_FORMAT_R8G8B8A8_UNORM;
262 default:
263 return VK_FORMAT_UNDEFINED;
264 }
265}
@ SBGL_FORMAT_R8G8B8A8_UNORM
Definition sbgl_types.h:160
@ SBGL_FORMAT_R32G32_SFLOAT
Definition sbgl_types.h:156
@ SBGL_FORMAT_R16G16B16A16_SNORM
Definition sbgl_types.h:159
@ SBGL_FORMAT_R32G32B32_SFLOAT
Definition sbgl_types.h:157
@ SBGL_FORMAT_R32G32B32A32_SFLOAT
Definition sbgl_types.h:158
@ SBGL_FORMAT_R32_SFLOAT
Definition sbgl_types.h:155

◆ sbgl_vk_debug_callback()

static VKAPI_ATTR VkBool32 VKAPI_CALL sbgl_vk_debug_callback ( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT * pCallbackData,
void * pUserData )
static

Definition at line 200 of file sbgl_backend_vulkan.c.

205 {
206 (void)pUserData;
207 (void)messageType;
208
209 sbgl_LogLevel level =
210 (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
213
215 __FILE__, __LINE__, __func__,
216 pCallbackData->pMessage);
217
218 return VK_FALSE;
219}
@ SBGL_LOG_CAT_GFX
sbgl_LogLevel
Logging severity levels.
@ SBGL_LOG_WARN
@ SBGL_LOG_ERROR
void sbgl_internal_log_impl(sbgl_LogLevel level, sbgl_LogCategory category, const char *file, int line, const char *function, const char *message)
Definition sbgl_log.c:116

◆ select_physical_device()

static bool select_physical_device ( sbgl_GfxContext * ctx)
static

Definition at line 551 of file sbgl_backend_vulkan.c.

551 {
552 uint32_t deviceCount = 0;
553 vkEnumeratePhysicalDevices(ctx->instance, &deviceCount, NULL);
554 if (deviceCount == 0) {
555 fprintf(stderr, "[Vulkan] No physical devices found\n");
556 return false;
557 }
558
559 SblArenaMark mark = sbl_arena_mark(ctx->arena);
560 VkPhysicalDevice* devices = SBL_ARENA_PUSH_ARRAY(ctx->arena, VkPhysicalDevice, deviceCount);
561 if (!devices)
562 return false;
563 vkEnumeratePhysicalDevices(ctx->instance, &deviceCount, devices);
564
565 for (uint32_t i = 0; i < deviceCount; i++) {
566 VkPhysicalDeviceProperties props;
567 vkGetPhysicalDeviceProperties(devices[i], &props);
568 if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
569 ctx->physicalDevice = devices[i];
570 printf("[Vulkan] Selected Discrete GPU: %s\n", props.deviceName);
571 break;
572 }
573 }
574
575 if (!ctx->physicalDevice) {
576 ctx->physicalDevice = devices[0];
577 VkPhysicalDeviceProperties props;
578 vkGetPhysicalDeviceProperties(ctx->physicalDevice, &props);
579 printf("[Vulkan] Selected GPU: %s\n", props.deviceName);
580 }
581
582 sbl_arena_rewind(ctx->arena, mark);
583 return true;
584}

◆ static_heap_alloc()

static uint32_t static_heap_alloc ( sbgl_GfxContext * ctx,
size_t size )
static

Definition at line 281 of file sbgl_backend_vulkan.c.

281 {
282 /* The size is rounded up to a 256-byte boundary to satisfy the most stringent
283 Vulkan hardware alignment requirements (minUniformBufferOffsetAlignment). */
284 uint32_t alignedSize = (uint32_t)((size + 255) & ~255);
285
286 if (ctx->staticHeap.offset + alignedSize > ctx->staticHeap.size) {
287 /* If the requested allocation exceeds the remaining capacity of the static heap,
288 the context's result state is updated to reflect an out-of-memory error. */
290 return SBGL_INVALID_OFFSET;
291 }
292
293 uint32_t offset = ctx->staticHeap.offset;
294 ctx->staticHeap.offset += alignedSize;
295 return offset;
296}

Variable Documentation

◆ sbgl_DefaultResourceLimits

const sbgl_ResourceLimits sbgl_DefaultResourceLimits
static
Initial value:
= {
.maxBuffers = 1024,
.maxShaders = 256,
.maxPipelines = 256
}

Definition at line 26 of file sbgl_backend_vulkan.c.

26 {
27 .maxBuffers = 1024,
28 .maxShaders = 256,
29 .maxPipelines = 256
30};