Location
src/backend/vulkan/
vulkan_backend.c — RHI function table, device lifecycle, frame recording
vulkan_pipeline.c — Pipeline creation, render pass, descriptor set layout
vulkan_memory.c — Buffer and image allocation helpers
vulkan_internal.h — Shared internal types and constants
vulkan_shaders.h — Embedded SPIR-V bytecode
shaders/ — GLSL source for SPIR-V compilation
Overview
Fully implemented Vulkan 1.0 headless backend. Renders offscreen with no surface or swapchain — the application reads back RGBA8 pixels via mop_viewport_read_color and blits to its own window (e.g. SDL3 texture). Enable with make MOP_ENABLE_VULKAN=1.
Instance and Device
Headless Vulkan — no surface or swapchain extensions required. The backend:
- Creates a
VkInstance(with validation layers if available) - Selects the first discrete GPU, falling back to integrated
- Creates a single-queue
VkDeviceon the graphics queue family - Allocates a command pool, descriptor pool, fence, and staging buffer
Render Pass
Three attachments:
| Attachment | Format | Usage |
|---|---|---|
| Color | VK_FORMAT_R8G8B8A8_SRGB | sRGB color output |
| Depth | VK_FORMAT_D32_SFLOAT | Depth testing |
| Object ID | VK_FORMAT_R32_UINT | Picking buffer |
Pipeline
- Vertex input: position (vec3), normal (vec3), color (vec4), texcoord (vec2) — matching
MopVertexlayout - Flexible vertex stride: respects per-mesh
MopVertexFormatstride - Push constants: MVP matrix (mat4), model matrix (mat4), object ID (uint), render flags
- UBO: multi-light data (up to
MOP_MAX_LIGHTS), camera position, ambient, shading mode - Polygon mode:
VK_POLYGON_MODE_FILLorVK_POLYGON_MODE_LINE - Depth test: configurable per draw call (disabled for gizmo/light indicator overlays)
- Backface culling: configurable per draw call (disabled for overlays and indicators)
- Pipeline caching: pipelines are created on demand and cached by configuration hash
Command Recording
Per-frame:
vkResetCommandBuffer+vkBeginCommandBuffervkCmdBeginRenderPass(clear all attachments)- Set viewport and scissor
- For each draw call: bind pipeline, push constants, bind vertex/index buffer,
vkCmdDrawIndexed vkCmdEndRenderPass- Pipeline barrier for color image transfer
vkCmdCopyImageToBuffer— color, depth, and object ID to host-visible stagingvkEndCommandBuffervkQueueSubmit+ fence wait
Readback
Host-visible staging buffers are persistently mapped. After fence completion, the application reads RGBA8 color, float depth, and uint32 object ID directly from the mapped pointers. Zero-copy on the CPU side.
RHI Handle Mapping
| RHI Handle | Vulkan Concrete Type |
|---|---|
MopRhiDevice | VkInstance, VkDevice, VkQueue, VkCommandPool, pipelines, UBO |
MopRhiBuffer | VkBuffer + VkDeviceMemory (device-local) |
MopRhiFramebuffer | VkFramebuffer + VkImage x 3 + VkImageView x 3 + staging buffers |
MopRhiTexture | VkImage + VkImageView + VkDeviceMemory |
Buffer Updates
Vertex and index data are uploaded through a staging buffer (MOP_VK_STAGING_SIZE). The staging buffer is host-visible and persistently mapped. Data is copied to the staging buffer, then vkCmdCopyBuffer transfers it to device-local memory with a pipeline barrier for vertex/index access.
Runtime Selection
MopViewport *vp = mop_viewport_create(&(MopViewportDesc){
.width = 800, .height = 600,
.backend = MOP_BACKEND_VULKAN
});
Or via environment variable in the showcase:
MOP_BACKEND=vulkan ./build/mop_showcase