SBgl 0.1.0
A graphics framework in C99
Loading...
Searching...
No Matches
sbgl_graphics_hal.h File Reference
#include <stdbool.h>
#include <stddef.h>
#include "sbgl_types.h"
Include dependency graph for sbgl_graphics_hal.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  sbgl_GfxTransientAllocation
 Represents a slice of a persistent GPU buffer used for transient data. More...
 

Typedefs

typedef struct sbgl_GfxContext sbgl_GfxContext
 Opaque handle for the graphics backend context.
 

Functions

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_EndFrame (sbgl_GfxContext *ctx)
 Submits the current frame's commands and presents the image.
 
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_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 buffer)
 
void sbgl_gfx_FillBuffer (sbgl_GfxContext *ctx, sbgl_Buffer buffer, 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 buffer)
 
void sbgl_gfx_UnmapBuffer (sbgl_GfxContext *ctx, sbgl_Buffer 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 shader)
 
sbgl_Pipeline sbgl_gfx_CreatePipeline (sbgl_GfxContext *ctx, const sbgl_PipelineConfig *config)
 
void sbgl_gfx_DestroyPipeline (sbgl_GfxContext *ctx, sbgl_Pipeline pipeline)
 
sbgl_ComputePipeline sbgl_gfx_CreateComputePipeline (sbgl_GfxContext *ctx, sbgl_Shader shader)
 
void sbgl_gfx_DestroyComputePipeline (sbgl_GfxContext *ctx, sbgl_ComputePipeline pipeline)
 
void sbgl_gfx_BindComputePipeline (sbgl_GfxContext *ctx, sbgl_ComputePipeline pipeline)
 
void sbgl_gfx_DispatchCompute (sbgl_GfxContext *ctx, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ)
 
void sbgl_gfx_MemoryBarrier (sbgl_GfxContext *ctx, sbgl_BarrierType type)
 
void sbgl_gfx_BindPipeline (sbgl_GfxContext *ctx, sbgl_Pipeline pipeline)
 
void sbgl_gfx_BindBuffer (sbgl_GfxContext *ctx, sbgl_Buffer buffer, 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 buffer, 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.
 
uint64_t sbgl_gfx_GetBufferDeviceAddress (sbgl_GfxContext *ctx, sbgl_Buffer buffer)
 Retrieves the 64-bit GPU virtual address for a buffer.
 
void sbgl_gfx_DestroyBufferDeferred (sbgl_GfxContext *ctx, sbgl_Buffer buffer)
 Marks a buffer for destruction after current frames complete.
 
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.
 

Typedef Documentation

◆ sbgl_GfxContext

typedef struct sbgl_GfxContext sbgl_GfxContext

Opaque handle for the graphics backend context.

Definition at line 21 of file sbgl_graphics_hal.h.

Function Documentation

◆ 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.
#define SBGL_TRANSIENT_BUFFER_SIZE
void * transientMapped[SBGL_MAX_FRAMES_IN_FLIGHT]
uint32_t transientOffsets[SBGL_MAX_FRAMES_IN_FLIGHT]
sbgl_Buffer transientBuffers[SBGL_MAX_FRAMES_IN_FLIGHT]
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]
sbgl_GfxDynamicHeap dynamicHeap
VkCommandBuffer commandBuffers[SBGL_MAX_FRAMES_IN_FLIGHT]
VkSemaphore imageAvailableSemaphores[SBGL_MAX_SWAPCHAIN_IMAGES]
VkSwapchainKHR swapchain
struct VolkDeviceTable vk
uint32_t deferredCount[SBGL_MAX_FRAMES_IN_FLIGHT]
VkFence inFlightFences[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}
VkImageView * imageViews

◆ sbgl_gfx_BindBuffer()

void sbgl_gfx_BindBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer buffer,
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
#define SBGL_INVALID_HANDLE
Definition sbgl_types.h:8
sbgl_ResourceLimits limits
SBGL_VulkanBuffer * buffers

◆ sbgl_gfx_BindComputePipeline()

void sbgl_gfx_BindComputePipeline ( sbgl_GfxContext * ctx,
sbgl_ComputePipeline pipeline )

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 pipeline )

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}
@ SBGL_HEAP_TYPE_STATIC
@ SBGL_HEAP_TYPE_MANAGED
@ SBGL_HEAP_TYPE_DYNAMIC
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)
#define SBGL_INVALID_OFFSET
Definition sbgl_types.h:9
@ SBGL_BUFFER_USAGE_INDIRECT
Definition sbgl_types.h:126
@ SBGL_BUFFER_USAGE_TRANSFER_DST
Definition sbgl_types.h:127
@ SBGL_BUFFER_USAGE_STORAGE
Definition sbgl_types.h:125
uint32_t sbgl_Buffer
Handle for a GPU-side buffer.
Definition sbgl_types.h:37
sbgl_GfxManagedHeap managedHeap
sbgl_GfxStaticHeap staticHeap

◆ sbgl_gfx_CreateComputePipeline()

sbgl_ComputePipeline sbgl_gfx_CreateComputePipeline ( sbgl_GfxContext * ctx,
sbgl_Shader shader )

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
SBL_ARENA_DEF SblArenaMark sbl_arena_mark(SblArena *arena)
#define SBL_ARENA_PUSH_ARRAY(arena, type, count)
Definition sbl_arena.h:21
SBL_ARENA_DEF void sbl_arena_rewind(SblArena *arena, SblArenaMark mark)
VkPipelineLayout layout
Bookmark for arena state.
Definition sbl_arena.h:57
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 buffer )

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 pipeline )

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 pipeline )

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 shader )

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 groupCountX,
uint32_t groupCountY,
uint32_t groupCountZ )

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}
#define SBGL_MAX_FRAMES_IN_FLIGHT
#define SBGL_MAX_SWAPCHAIN_IMAGES
VkSemaphore renderFinishedSemaphores[SBGL_MAX_SWAPCHAIN_IMAGES]

◆ 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 buffer,
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 create_swapchain(sbgl_GfxContext *ctx, sbgl_Window *window)
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 buffer )

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)
static void cleanup_swapchain(sbgl_GfxContext *ctx)
void sbgl_gfx_DestroyComputePipeline(sbgl_GfxContext *ctx, sbgl_ComputePipeline handle)
VkCommandPool commandPool

◆ sbgl_gfx_UnmapBuffer()

void sbgl_gfx_UnmapBuffer ( sbgl_GfxContext * ctx,
sbgl_Buffer buffer )

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}