The Vulkan Kitchen: A Visual Tour of the Graphics Pipeline
Vocabulary, a quick reference
Terms the article uses, defined once for context. Skip it and refer back when a word is unfamiliar.
- Attachment
- A slot the pipeline draws into: an image view used as a color or depth target for a render.
- BufferVkBuffer
- A described linear region of GPU data such as vertices, indices, or uniforms. It holds no memory until you bind it to device memory.
- Command bufferVkCommandBuffer
- A recorded list of GPU commands (bind pipeline, bind descriptors, draw). Nothing runs until it is submitted to a queue.
- Command poolVkCommandPool
- An allocator that hands out command buffers, tied to one queue family.
- Descriptor
- A small reference to one resource (a buffer, an image view, or a sampler) that a shader is allowed to access.
- Descriptor poolVkDescriptorPool
- The allocator that hands out descriptor sets.
- Descriptor setVkDescriptorSet
- A tray of descriptors bound to a shader, organized by set and binding number. It points at resources; it holds no memory itself.
- Device memoryVkDeviceMemory
- An actual GPU (or host-visible) memory allocation. Buffers and images bind to a region of it.
- Dynamic rendering
- The modern path, core in Vulkan 1.3, where you name attachments inline at draw time, with no render pass or framebuffer object.
- FenceVkFence
- A GPU-to-CPU signal. The CPU waits on it to know work finished, so it can safely reuse a command buffer or resource.
- FIFO
- First In First Out. A present mode: images are shown in order on the display refresh beat (vsync), so no tearing. Mailbox and immediate are the other common modes.
- FramebufferVkFramebuffer
- In the classic path, the object that binds concrete image views to a render pass's attachments.
- GLSL / HLSL
- High-level shader languages. You compile them ahead of time to SPIR-V for Vulkan.
- ImageVkImage
- Pixel storage with a format and size (1D, 2D, or 3D). The same image is transitioned between layouts for whatever job it is doing now.
- Image viewVkImageView
- The serving order for an image: which format to read it as, and which mip levels and array layers to expose.
- InstanceVkInstance
- Your application's connection to the Vulkan library. It lists the GPUs and enables global extensions and validation layers.
- Logical deviceVkDevice
- Your working connection to one physical device, created with the features, extensions, and queues you ask for.
- Mip levels
- Precomputed smaller copies of an image (half, quarter, and so on) used when it is drawn small or far away, which reduces aliasing and helps cache use.
- Physical deviceVkPhysicalDevice
- A GPU present in the machine, with fixed capabilities, memory types, and queue families you inspect before choosing one.
- PipelineVkPipeline
- A baked configuration of the whole graphics process (fixed-function state plus shader stages), compiled up front so the GPU runs flat out.
- Pool
- An allocator that hands out objects of one kind. Vulkan has command pools (for command buffers) and descriptor pools (for descriptor sets).
- QueueVkQueue
- A lane you submit recorded command buffers to. Families are typed by capability (graphics, compute, transfer); some can also present.
- Render passVkRenderPass
- The classic path: attachments and subpasses declared up front, with each pipeline built for a specific render pass.
- SemaphoreVkSemaphore
- A GPU-to-GPU ordering primitive. It makes one batch of work wait for another on the device. This article uses the classic binary kind; Vulkan 1.2 added timeline semaphores, a more general counting variant.
- Shader
- A small program for one programmable pipeline stage. The vertex shader places each corner; the fragment shader colors each pixel.
- Shader moduleVkShaderModule
- A wrapper around a chunk of compiled SPIR-V; a pipeline stage points at a named entry point inside it.
- SPIR-V
- A standard bytecode the driver loads directly. You compile GLSL or HLSL to it ahead of time; the driver still turns it into native GPU code at load.
- SurfaceVkSurfaceKHR
- A platform-neutral handle to the window you present into. It bridges Vulkan and the operating system windowing.
- SwapchainVkSwapchainKHR
- A ring of images you render into and hand to the display in turn. Double or triple buffering keeps one on screen while another is drawn.
- Uniform
- Constant data the CPU supplies to shaders that the shaders cannot change, such as the model, view, and projection matrices. It is read-only in the shader and usually updated once per frame; modern code stores it in a uniform buffer (a UBO) so several shaders can share it with fewer commands.
- Validation layers
- Optional development-only layers that check your API usage and report mistakes, since core Vulkan does almost no error checking.
What Vulkan is, and why it is everywhere
Vulkan is a modern, cross-platform way to talk to a GPU. It is an open standard from the Khronos Group, the same people behind OpenGL, and it grew out of AMD's Mantle, handed to Khronos to seed one low-level standard for the whole industry. Version 1.0 arrived in 2016.
Its defining trait is that it is explicit. Where older APIs quietly decide things for you, Vulkan hands you the controls: more work, in exchange for very low CPU overhead, honest use of many threads at once, and one model that runs the same way on desktop, mobile, and console. Section 1 unpacks that trade. Shaders are compiled ahead of time to a standard bytecode called SPIR-V, so the driver loads them directly.
You are probably already running it. Vulkan is the primary low-level graphics API on Android, where modern versions of Unity and Unreal default to it. It powers Linux and Steam Deck gaming through Proton and DXVK, which translate DirectX to Vulkan. It runs on the Nintendo Switch, and on Apple hardware through MoltenVK, which maps Vulkan onto Metal. The map below shows the spread.
One API, almost everywhere
runs on
1. Why Vulkan asks so much of you
Older graphics APIs like OpenGL work like ordering at a counter. You make one call, and the driver quietly decides almost everything else: which memory to use, when to synchronize, how to schedule the work. It is convenient, and for a long time it was enough.
Vulkan hands you the kitchen instead. You pick the device, allocate the memory, build the pipeline, record the commands, and manage the timing yourself. In return you get explicit, predictable, low-overhead control that works across platforms and across vendors, scales cleanly to many threads, and covers both graphics and compute. The price is verbosity: a lot of small, deliberate decisions.
Same triangle out, very different setup
One call goes in and a finished frame comes out. Behind it the driver decides the memory, the synchronization, the scheduling, the pipeline state, and the command recording, all hidden from you.
2. Meeting the kitchen: instance, devices, queues
Four objects, kitchen words first
registering your restaurant with the city
surveying the kitchens available
leasing and staffing one kitchen
the submission lanes to the cooks
Setup is four objects, met in order. The instance registers your application with the Vulkan loader, the way you register a restaurant with the city before cooking. From it you list the physical devices, the GPUs in the machine, and inspect what each one offers before choosing one. You then open a logical device, your working connection to that GPU, enabling only the features and queues you need. The logical device is where you ask for those queues, features, and extensions; the memory types and hardware limits themselves belong to the physical device, and you allocate memory through the logical device using them. Queues are the lanes you submit work to, and queue families are typed by capability, graphics, compute, or transfer, with some families also able to present to a surface. Once submitted, the GPU runs that work on its own schedule, and the panel below lets you step through the whole setup.
A typical machine has more than one option, say an integrated GPU and a discrete one. You enumerate them, read each one's queue families and memory heaps, and score them by suitability: does it support the extensions you need, does it have a queue family that can both render and present, how much device-local memory does it have. For rendering you usually pick the discrete GPU. At device creation you then ask for the specific queues you want, for example one queue from the graphics family (often family 0, which also supports present), and Vulkan hands back a VkQueue handle you submit work to later.
Register the restaurant
The instance is your application's handle to the Vulkan library. It switches on the validation layers and extensions you want, then announces your app to the loader.
Nothing has been drawn yet. This is the paperwork that lets you start cooking.
Survey the kitchens
With an instance in hand you enumerate the physical devices, the GPUs present in the machine. Each one advertises fixed capabilities, memory types, and queue families, and they are not interchangeable: an integrated GPU shares system memory and sips power, a discrete GPU has its own dedicated memory and far more throughput, and a virtual or software device is mainly for headless or CI work.
You read those off and pick by suitability. For rendering that is usually the discrete GPU, the best fit here for its dedicated memory and highest throughput.
Lease and staff one kitchen
You pick a physical device and create a logical device on top of it, enabling only the features and queues you actually need.
That chosen kitchen is now staffed and active. The candidates you passed over fade away.
Open the submission lanes
Queues are the lanes you hand recorded work to. Queue families are typed by capability (graphics, compute, transfer), and some families can also present to a surface.
Once you submit, the GPU runs that work on its own schedule, asynchronously.
3. Getting a plate to the customer: surface, swapchain, images
The surface, and why pictures come in sizes
The surface is the one bridge from the kitchen to the dining room full of customers. You present through it.
the one bridge from the kitchen to the window you present into
Mip levels are the same picture saved at half-sizes, so a small or distant object reads a small copy: less shimmer, kinder to the cache. The rotation of these images, one shown while the next is drawn, is the swapchain carousel just below.
the serving order for an image: which portion sizes (mip levels) a station may take
The surface is a platform-neutral handle to the window you draw into. It is the bridge between Vulkan and the operating system, the serving hatch between the kitchen and the dining room.
The swapchain is a small ring of images. While one image is on screen, you render into another, then hand it to the display in turn. The present mode sets the rhythm: FIFO is vsync-smooth (vsync is the display's fixed refresh beat), immediate can tear, and mailbox keeps latency low by always presenting the freshest image.
An image is the plate with the food on it: raw pixels. An image view is the serving order for that plate: which portion size to bring out (mip level), which copy from a stack of identical plates (array layer), and which kind of dish to read it as (format). Same plate, different orders.
The same image is also stored differently depending on its job right now, whether it is a render target, a sampled texture, or the thing being shown, and you transition it between those layouts explicitly. The kitchen re-plates the same dish for each step of service.
Swapchain, render then present
Steady and vsync paced. Finished plates wait their turn in order, so the picture stays smooth and never tears.
Same bytes underneath, shown on the left. The view on the right only changes how a stage reads them: the format, the channels, and which mip levels (sizes) it exposes. Here mip 0 is the full-size base level.
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, render target. Packed for fast writes as a render target. This is the layout you draw into during a render pass. A pipeline barrier. You call it, it is not free.
Plate arranged for COLOR_ATTACHMENT_OPTIMAL, render target. on the hot plate, being cooked onto.Same plate, different orders.
4. The recipe card: pipeline and dynamic rendering
First, what is the GPU even doing?
We have the kitchen, the counter we serve across, and clean plates waiting to be filled: the device, the surface, and the images from the last three sections. What we have not written down yet is the method for cooking a single frame. That method is the pipeline. The graphics pipeline is a baked recipe card. It fixes the sequence of steps your geometry passes through, in order, before a single frame runs. A few of those steps are programmable, the shaders you write, and the rest are fixed-function steps you only configure. The visualizer below walks the stages in turn.
Vulkan bakes the whole thing into a pipeline object up front, so the GPU can run it at full speed. The trade-off is rigidity: most recipe changes mean baking a new pipeline, though a few knobs stay adjustable at draw time, like viewport and scissor as dynamic state, and small values passed through push constants or uniforms.
An attachment is just a slot you draw into: an image view used as a color or depth target for the pipeline's output. The two approaches differ only in how you describe those slots. The classic approach bakes a render pass and a framebuffer ahead of time, and every pipeline is built for a specific render pass (formally, any compatible one), so changing the attachments can mean baking a new pipeline. Its subpasses can chain on-chip, which saves bandwidth on tile-based mobile GPUs. Dynamic rendering, core in Vulkan 1.3, drops both objects: you name the attachments inline when you begin rendering, and a pipeline only needs to know the attachment formats, not a specific pass. Far less to set up, and the recommended path for new code. It traded away subpasses, which mattered for mobile bandwidth, until a later extension (core in Vulkan 1.4) brought those on-chip reads back.
That is the recipe card in theory. The conveyor below maps the five stages in order, then lets you flip between the two ways of naming your attachments, the classic render pass and dynamic rendering.
The pipeline, baked once and run flat out
Send a triangle through the graphics pipeline
Stage 1 of 5: Input assembly, fixed function, configured not coded. Groups vertices into primitives like triangles.
Input assembly. Groups vertices into primitives like triangles.
All five stages are baked into one VkPipeline up front, so the GPU runs the recipe flat out. Change the recipe and you bake a new pipeline.
What an attachment is, and how you describe it
Render pass vs dynamic rendering
An attachment is a slot you draw into, the image view used as a color or depth target. These two modes differ only in how you name that slot.
Classic Vulkan bakes a render pass and a framebuffer up front to describe the attachments, and every pipeline is built for one specific render pass, so changing the attachments can mean baking a new pipeline. Its subpasses can chain on-chip, which saves memory bandwidth on tile-based mobile GPUs, but it is more boilerplate and all of it is fixed before you draw.
So far the recipe is on paper. The lab below cooks it: for illustration we run a single wooden door with a deliberately simplistic texture, the same door section 8 will serve, pushed through every stage one beat at a time. Beside each station sits the real working data, the raw vertex and index buffers, the three matrices the vertex shader multiplies, the clip counts, the rasterized pixels. At the clipping stage the door is placed deliberately part-way off the screen, so you can watch how a shape that only partly fits gets cut at the edge before it ever becomes pixels.
The recipe, run for real
The pipeline lab: send the door through every stage
Stage 1 of 8: The raw ingredients. This is all the GPU receives: a vertex buffer of raw floats (position, normal, UV per vertex) and an index buffer of 36 numbers. No triangles exist yet.
The raw ingredients. a bag of loose ingredients, nothing assembled. This is all the GPU receives: a vertex buffer of raw floats (position, normal, UV per vertex) and an index buffer of 36 numbers. No triangles exist yet.
That is the whole recipe, run one stage at a time on twelve triangles. As a closing bonus, the film below tells the same story on a game-art example: a dragon carried from loose vertices to assembled triangles, attributes, shading, and one finished frame. Each step is a simplistic AI-generated still standing in for that phase, and I morphed between the frames to make the transition. The dragon itself is borrowed from NVIDIA's RTX Mega Geometry write-up.
Bonus: Indicative example
5. The cooks you write: shaders and shader modules
A shader is the program for one programmable station. The vertex shader transforms each corner of your geometry, mapping the position your app supplied in a vertex buffer to where it lands on screen; it does not invent the geometry. The fragment shader decides the color of each pixel that the geometry covers.
You write shaders in a language like GLSL or HLSL and compile them ahead of time to SPIR-V, a standardized bytecode the driver loads directly. The driver still turns that SPIR-V into the GPU's own machine code when it loads the shader; what Vulkan removes is the driver parsing your GLSL or HLSL source, which is where vendor-specific bugs and surprises used to creep in. That is a real difference from OpenGL, which compiled shader source at runtime and left the result up to each driver.
Concretely, the build step runs a compiler (glslangValidator for GLSL, dxc for HLSL) over your shader source and writes .spv files you ship with the app. At startup each .spv becomes a VkShaderModule, and the pipeline's stage list points one stage at the vertex module's entry point and another at the fragment module's. The same SPIR-V runs on every vendor's driver; only the final compile to native code differs between them.
One shader module can hold several related shaders: each pipeline stage names the entry point it wants inside the module.
The cooks you write
What the two cooks do
The vertex shader transforms each corner it is handed, it does not invent them. Each corner arrives from a vertex buffer; drag the top one to stand in for a different input position and watch its gl_Position change, exactly what the one line of GLSL in the compile flow below computes.
// one value, set once per draw on the CPU
layout(set = 0, binding = 0) uniform Style {
int pattern;
} u;
void main() {
// every covered pixel reads the same u.pattern
outColor = shade(u.pattern, uv);
}A uniform is one value the CPU sets once per draw; every covered pixel reads the same one. Switch u.pattern and the program recolours every pixel: blends color across the x position.
The recipe booklet
How the recipe gets loaded
#version 450
layout(location = 0) in vec2 inPos;
void main() {
gl_Position = vec4(inPos, 0.0, 1.0);
}pStages[0].pName = "main";SPIR-V is compiled ahead of time and loaded straight into the driver. OpenGL took your GLSL and compiled it at runtime, so results varied by vendor. Here the heavy work happens once, at pipeline creation, never mid-frame.
6. Where the ingredients live: buffers, memory, descriptors
The pipeline and its shaders fix the steps. This section is about where the data those steps read actually lives. Beginners often blur several distinct ideas together, so it is worth separating them. A buffer is a described linear region of data: vertices, indices, or uniforms. On its own it is just a description.
Device memory is the actual allocation. You bind a buffer (or an image) to a region of memory yourself, choosing device-local memory for speed or host-visible memory when the CPU needs to write into it. An image is a grid of pixels, 1D, 2D, or 3D, and an image view is the serving order you read it through.
Shaders do not hold raw pointers to any of this. Instead a descriptor set is a tray of references that says this uniform here, that texture there. It holds no memory of its own: the bytes still live in the device memory you allocated and bound, and the tray only points at them. It is not the channel between one shader stage and the next either; handing a vertex shader's output to the fragment shader is the pipeline's job, not the descriptor set's. A descriptor set layout describes the slots on the tray, and a descriptor pool allocates the trays. Validation layers, meanwhile, are the optional health inspector that catches your mistakes while you develop.
In practice the order is concrete. You create a buffer with a usage and a size, ask Vulkan for its memory requirements, choose a matching memory type, allocate a block of device memory, and bind the buffer to an offset inside it. Many engines allocate one large block and suballocate every buffer and image within it. The descriptor set is then filled in with vkUpdateDescriptorSets: binding 0 might point at the uniform buffer with an offset and a range, and binding 1 at an image view plus a sampler. The set holds those references; the data still lives in the memory you allocated.
The tray, before any code
The same four ingredients, now with their Vulkan names.
How a shader reaches its data
The binding chain
layout(set = 0, binding = 0)
uniform Globals { mat4 mvp; };A shader holds no pointers of its own. The descriptor set is the tray of references that tells each binding where its resource actually lives. It holds no memory itself; the bytes still live in the device memory the buffer is bound to.
Four objects people mix up
Buffer, memory, image, image view
Hover or focus an item to see what it is and how it differs. Buffer and device memory are not the same object, and neither are image and image view.
7. Service: command buffers, queues, synchronization
Everything is built. Now we record one frame of work and hand it to the GPU. You do not call draw directly. You record the work into a command buffer, which is allocated from a command pool: bind the pipeline, bind the descriptors, draw. The recorded ticket is then submitted to a queue, and the GPU runs it asynchronously.
The command pool and its command buffers are created once and reused. Each frame you reset a buffer and record it again. Recording just means writing that list of GPU commands onto the ticket; nothing actually runs until you submit, and the queue is only touched at that submit and at present. What changes every frame is the recorded contents, not the objects themselves.
Because the CPU and GPU run on their own clocks, you order things explicitly. Semaphores coordinate GPU-to-GPU steps: wait for the image to be acquired before rendering into it, and wait for rendering before presenting. A fence reports back to the CPU when a frame is done, so it can safely reuse that frame's command buffer and resources.
Concretely, you keep a small number of frames in flight, usually two. Each gets its own command buffer, its own uniform buffer, and its own fence, so the CPU can record the next frame into a fresh set while the GPU still works on the current one. The per-frame dance is always the same: wait on this frame's fence, acquire a swapchain image (which signals an image-available semaphore), record and submit the command buffer (waiting on image-available, signaling render-finished and the fence), then present (waiting on render-finished).
You do not call draw directly
Record a ticket, then submit it
- 1bind pipeline
- 2bind descriptors
- 3draw
The GPU runs the ticket on its own clock. When it is done the frame can be presented, which is what the timeline below works through.
The CPU and GPU run in parallel
One frame on each lane, in flight
Step 1 of 7: Acquire an image. Ask the swapchain for an image. When the device has one ready to draw into, it signals the image-acquired semaphore. The call itself returns right away; the semaphore fires when the image is actually free.
Acquire an image. Ask the swapchain for an image. When the device has one ready to draw into, it signals the image-acquired semaphore. The call itself returns right away; the semaphore fires when the image is actually free.
Two kinds of synchronization
Semaphore vs fence
A GPU-to-GPU ordering primitive. It makes one batch wait for another, for example render waits for image-acquired and present waits for render.
GPU to GPUOrders work on the device. Render waits for the image, and present waits for render. The CPU is never blocked by it.
A GPU-to-CPU signal. The CPU waits on it to know work finished, so it can safely reuse a command buffer or resource.
GPU to CPUThe device tells the host a frame is done, so the CPU can safely recycle that frame command buffer and resources.
The CPU and the GPU run on their own clocks, so every ordering has to be spelled out. Keeping a couple of frames in flight is what keeps both of them busy at once.
8. The whole kitchen, one frame
Step back and look at the global shape of the work. Everything you have met so far belongs to one of two timelines. The objects from sections 2 through 7 are built once during setup and reused every frame: that is the long setup the tutorials are famous for. (Strictly, the swapchain and the views and framebuffers derived from it are rebuilt whenever the window resizes; everything else lasts the whole program.) What remains is a short loop that runs for every single frame, and that loop is shared between two workers on their own clocks. The CPU prepares and hands off work; the GPU executes it. The map below draws that whole workflow in one picture.
The global workflow, one map
Built once, then a loop between two clocks
Built once · before any frame
- Instance + devices§2 · setup
- Swapchain + views§3 · plates
- Pipeline + shaders§4-5 · recipe
- Buffers + descriptors§6 · pantry
- Command pool§7 · tickets
Every frame · the service loop
two workers, two clocks
- 1cpuAcquire an image
ask the swapchain for a plate
- 2cpuRecord the commands
bind the pipeline, then draw
- 3cpuSubmit
the ticket goes to the queue
- 4gpuRun the pipeline
cook into the image view
waits on the image-acquired semaphore
- 5gpuPresent
plate to the surface
waits on the render-finished semaphore
- 6cpuThe fence reports back
frame done, the ticket is safe to reuse, the next frame begins
fence, a GPU-to-CPU signal
Read it lane by lane. Each frame, the CPU acquires an image from the swapchain, records a command buffer, and submits it to the graphics queue. The GPU picks the work up but waits on the image-acquired semaphore, so it never draws on a plate that is still being shown, then runs your baked pipeline into that image's view and signals render-finished. Present waits on that signal before putting the plate on screen, and the fence reports back to the CPU that this frame's command buffer and resources are safe to reuse. That last hand-off closes the loop, and because the two lanes overlap, the CPU is usually already recording the next frame while the GPU still cooks the current one.
That was the shape in the abstract. Now watch the whole kitchen built and run for real on a single door: every object you have met, what it concretely is here, and exactly when it is created once or used on every frame.
One real frame, end to end
Rendering a single door
the door we will render
Hover a cell to name the resource. The whole block is one allocation, suballocated by offset.
The submit signals render-finished and a fence; the CPU waits on the fence before reusing this frame's command buffer and uniform buffer.
Step 1 of 12: Register the app. Create the instance and switch on the validation layers. This registers the app with the Vulkan loader. One VkInstance, made once.
Register the app. Create the instance and switch on the validation layers. This registers the app with the Vulkan loader. One VkInstance, made once.
The loop, running
Many frames: images cycle, two stay in flight
Command buffer: Re-recorded each frame with this frame's draw: bind the pipeline, the vertex and index buffers and the descriptor set, then drawIndexed(36).
Uniform buffer: Holds this frame's changing values, here the door's model-view-projection matrix (its angle this frame). The CPU writes it before recording.
Descriptor set: Points the shaders at this frame's resources: binding 0 to this uniform buffer, binding 1 to the wood texture.
Fence: Raised by the GPU when this frame finishes. The CPU waits on it before reusing this slot, so the next frame never overwrites work still in use.
Three images cycle while two frames stay in flight. Each slot owns its own copy of the four resources above, so while the GPU renders frame N from one slot, the CPU is already filling the other slot for frame N+1. That overlap is what keeps both processors busy.
That is one whole frame, start to finish. If it helps to see it differently, the panel below gathers every object into one connected graph, each where its section introduced it, with the real relationships drawn as edges and the same frame walked across it.
Another way to see itThe whole framework as one connected graph
Every object, one board
The whole kitchen as one graph
How to read it: each node is one object from the article, placed in the zone of the section that introduced it (setup left, presentation right, pipeline centre, pantry lower left, service lower centre, synchronisation lower right). The lines are the real relationships, which object creates, feeds, or signals which, and the dashed arc on the right is the loop closing into the next frame.
One frame, start to finish
Walk a single frame through the kitchen
These are the every-frame beats from the workflow map, traced on the full board. A solid comet sends a signal forward; a hollow comet is a step waiting on a signal before it may run.
Step 1 of 5: Acquire an image. Ask the swapchain for the next image. It signals the image-acquired semaphore once that plate is free to draw on.
Acquire an image. Ask the swapchain for the next image. It signals the image-acquired semaphore once that plate is free to draw on.
Quick reference
Every object and its one job
- GPUthe GPUthe whole kitchen
- InstanceVkInstanceregistering your restaurant with the city
- Physical DeviceVkPhysicalDevicesurveying the kitchens available
- Logical DeviceVkDeviceleasing and staffing one kitchen
- QueueVkQueue, from a queue familythe submission lanes to the cooks
- Shader ModuleVkShaderModulethe loaded recipe booklet
- PipelineVkPipelinethe recipe card
- Shadershader stagea cook at one station
- SurfaceVkSurfaceKHRthe serving hatch to the dining room
- SwapchainVkSwapchainKHRthe carousel of plates
- ImageVkImagethe plate itself
- Image ViewVkImageViewthe serving order for the plate
- BufferVkBuffera bin of raw ingredients
- Device MemoryVkDeviceMemorythe pantry shelf the bin sits on
- Descriptor SetVkDescriptorSeta tray of labeled references handed to a station
- Validation Layersvalidation layersthe health inspector
- Command PoolVkCommandPoolthe rail of blank order tickets
- Command BufferVkCommandBufferone order ticket of recorded steps
- SubmitvkQueueSubmithanding the ticket to the pass
- FenceVkFencethe buzzer that says the food is ready
- SemaphoreVkSemaphorethe hand-off between stations
9. What's next
This was the mental model: the cast of objects, what each one is for, and how they cooperate to cook and serve one frame. None of it required writing a line of code, and that was the point.
If you keep a single picture, keep the workflow map. Build the kitchen once: instance, devices, swapchain, pipeline, buffers, command pool. Then loop forever: acquire, record, submit, present, with two semaphores ordering the GPU's work and a fence reporting back to the CPU. Every Vulkan program you will ever read, however long, is that map with the details filled in.
Do you need Vulkan? Not always. If you just want triangles on screen, OpenGL, WebGPU, or a game engine will get you there with far less ceremony. Vulkan earns its keep when you need the control: predictable CPU cost, real multithreaded recording, one explicit model across platforms. And none of this machinery is graphics-only: the same queues, command buffers, and descriptors drive compute dispatches too.
The next article actually writes the code. It walks from instance creation all the way through the draw call, and ends with a triangle on screen. With the kitchen already mapped, the code reads as a checklist rather than a mystery.
Get in touch
We hope you enjoyed this article. If you have questions, found a bug, or just want to chat about AI and engineering, we'd love to hear from you.
- Emailcontact@fremaconsulting.ch
- LinkedInlinkedin.com/in/tom-salembien
- Xx.com/ohmts10
- GitHubgithub.com/TomSOhm
Future articles?
We're planning more deep dives into:
• Graph Neural Networks
• Transformer Architecture
• System Design for ML
Get them in your inbox