Following our work in the last lesson, where we successfully created an SDL2 window and an associated OpenGL context, we are now positioned to connect Skia to our application's graphical foundation. This lesson bridges the gap between the windowing system and the rendering library, a crucial step toward drawing high-performance graphics.
Our objective is to initialize a Skia GrDirectContext targeting the underlying GPU backend. This GrDirectContext object is the heart of Skia's GPU acceleration. Think of it as Skia's primary interface to the graphics card, a control center that takes our drawing intentions and translates them into low-level commands that the GPU understands.
The Role of a Graphics API
Before we dive into the code, it's important to solidify our understanding of the software stack. We have SDL2 managing the window, and we want to use Skia for rendering. The link between them is a Graphics API like OpenGL or Vulkan. These APIs are standards that define a common language for talking to different GPUs, abstracting away the specifics of the hardware.
The following video provides an excellent overview of what graphics APIs are and why they are intentionally separated from platform-specific windowing code.
Rendering 2D objects - Software from scratch
This video from the "Software from scratch" series by Muukid clearly explains the concepts behind graphics APIs and rendering architectures.
First, watch the segment explaining what graphics APIs are, from this section. This will clarify the roles of OpenGL and Vulkan as intermediaries to the GPU. Next, watch the segment on architectural separation from this part. The creator discusses designing a rendering library (mug) where the rendering code is separate from the windowing code. This is precisely the architecture that using Skia with SDL enables. Skia is our rendering library, and it's designed to be independent of the surface it's drawing to, whether that's an SDL window on your desktop or a mobile app screen.
In the previous lesson, we used SDL to perform the messy, platform-specific work of creating an OpenGL context. Now, we will hand that context over to Skia, our platform-independent rendering engine.
Initializing the GrDirectContext with OpenGL
The main object we need to create is an instance of GrDirectContext. Skia's GPU backend, named "Ganesh," provides factory functions to create this context from a native GPU context. Since we have an OpenGL context from SDL, we will use the GrDirectContexts::MakeGL function.
This process involves two main steps:
- Create a
GrGLInterfaceobject. This is essentially a struct containing function pointers to the OpenGL implementation provided by the driver. Skia provides a helper function,GrGLMakeNativeInterface(), which automatically discovers these functions from the currently active OpenGL context. - Pass this interface object to
GrDirectContexts::MakeGL()to create the context. The result is returned as a Skia smart pointer,sk_sp<GrDirectContext>.
Let's examine how this is done in the SkiaSDLExample.cpp file we looked at in the last lesson.
skia-c-examples/SkiaSDLExample.cpp at main · HinTak/skia-c-examples
This example file contains the complete setup for an SDL/Skia application. We will focus on the Skia initialization part.
In the main function, after the SDL_Window and SDL_GLContext are created, you will find the Skia initialization logic. While the provided text snippets are fragmented, the logic follows this pattern (which you can find in the full file): An interface is created: sk_sp<const GrGLInterface> interface(GrGLMakeNativeInterface()); This interface is used to create the context: sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(interface)); Locate these steps in the source file. Then, look for where the grContext is used later in the application loop to flush commands to the GPU before the window's buffer is swapped.
The GrDirectContext now holds all the state Skia needs to send commands to the GPU via OpenGL.
A High-Level View of the Rendering Pipeline
It's useful to visualize how this GrDirectContext fits into a larger rendering architecture, especially in a complex, multi-threaded application.

In this model:
- Recorders run on different threads (e.g., one for UI, one for the main content) and generate lists of drawing commands. In Skia, this is analogous to building an
SkPictureor a display list. - The resulting Recordings (command lists) are sent to the central Context. This is our
GrDirectContext. - The Context serializes these commands and submits them to the graphics backend (e.g., Dawn, which abstracts Metal, Vulkan, and D3D). In our case, the backend is OpenGL.
This architecture is fundamental to achieving high performance, as it allows drawing commands to be generated in parallel and batched together for efficient submission to the GPU. Your whiteboard application, while starting simple, can evolve towards this model for features like asynchronous PDF rendering.
Supporting Other Backends: Vulkan
Your interest in low-level systems makes it relevant to see how this process works with other modern graphics APIs like Vulkan. Skia's design makes switching backends straightforward. Instead of MakeGL, you would use MakeVulkan.
The Skia documentation on Vulkan support shows the parallel structure.
This document outlines how to use Skia with the Vulkan backend.
Read the section titled Using the Vulkan Backend. Notice the pattern is almost identical to the OpenGL setup: You populate a backend-specific struct (skgpu::VulkanBackendContext) with handles to your Vulkan instance, physical device, etc. You pass this struct to a factory function, GrDirectContexts::MakeVulkan.
This demonstrates the power of Skia's abstraction. You initialize the GrDirectContext for a specific backend, but from that point on, most of your Skia drawing code remains the same, regardless of whether it's ultimately executed via OpenGL, Vulkan, Metal, or Direct3D.
Conclusion
In this lesson, we successfully bridged the gap between our SDL window and the Skia rendering engine. You learned how to initialize a GrDirectContext, Skia's main gateway to the GPU, using the OpenGL context provided by SDL.
Key Takeaways:
- The
GrDirectContextis the central object in Skia's Ganesh GPU backend, managing all GPU-specific state and command submission. - Skia provides backend-specific factory functions, like
GrDirectContexts::MakeGLandGrDirectContexts::MakeVulkan, to create a context from a native GPU context. - This initialization involves creating an "interface" struct with function pointers to the graphics driver and passing it to the factory function.
- This architecture cleanly separates platform-independent rendering code (Skia) from platform-dependent windowing and context creation (SDL).
- Once drawing is complete for a frame,
GrDirectContext::flushAndSubmit()must be called to send the batched commands to the GPU.
We have established the window and connected Skia to the GPU. However, we still don't have a surface to draw on. In the next lesson, we will complete the setup by using our GrDirectContext to create an SkSurface. This surface will be directly tied to our SDL window, finally giving us a hardware-accelerated canvas to begin drawing our whiteboard.