Basic windowing / output example
We will demonstrate the basics of the libplacebo GPU output API with a worked example. The goal is to show a simple color on screen.
Creating a pl_log
Almost all major entry-points into libplacebo require providing a log
callback (or NULL
to disable logging). This is abstracted into the pl_log
object type, which we can create with
pl_log_create
:
Compiling
You can compile this example with:
The parameter PL_API_VER
has no special significance and is merely included
for historical reasons. Aside from that, this snippet introduces a number of
core concepts of the libplacebo API:
Parameter structs
For extensibility, almost all libplacebo calls take a pointer to a const
struct pl_*_params
, into which all extensible parameters go. For convenience,
libplacebo provides macros which create anonymous params structs on the stack
(and also fill in default parameters). Note that this only works for C99 and
above, users of C89 and C++ must initialize parameter structs manually.
Under the hood, pl_log_params(...)
just translates to &((struct
pl_log_params) { /* default params */, ... })
. This style of API allows
libplacebo to effectively simulate optional named parameters.
On default parameters
Wherever possible, parameters are designed in such a way that {0}
gives
you a minimal parameter structure, with default behavior and no optional
features enabled. This is done for forwards compatibility - as new
features are introduced, old struct initializers will simply opt out of
them.
Destructors
All libplacebo objects must be destroyed manually using the corresponding
pl_*_destroy
call, which takes a pointer to the variable the object is
stored in. The resulting variable is written to NULL
. This helps prevent
use-after-free bugs.
NULL
As a general rule, all libplacebo destructors are safe to call on
variables containing NULL
. So, users need not explicitly NULL
-test
before calling destructors on variables.
Creating a window
While libplacebo can work in isolation, to render images offline, for the sake of this guide we want to provide something graphical on-screen. As such, we need to create some sort of window. Libplacebo provides no built-in mechanism for this, it assumes the API user will already have a windowing system in-place.
Complete examples (based on GLFW and SDL) can be found in the libplacebo demos. But for now, we will focus on getting a very simple window on-screen using GLFW:
Compiling
We now also need to include the glfw3 library to compile this example.
Creating the pl_gpu
All GPU operations are abstracted into an internal pl_gpu
object, which
serves as the primary entry-point to any sort of GPU interaction. This object
cannot be created directly, but must be obtained from some graphical API:
currently there are Vulkan, OpenGL or D3D11. A pl_gpu
can be accessed from
an API-specific object like pl_vulkan
, pl_opengl
and pl_d3d11
.
In this guide, for simplicity, we will be using OpenGL, simply because that's what GLFW initializes by default.
-
Setting this allows the resulting
pl_gpu
to be thread-safe, which enables asynchronous transfers to be used. The alternative is to simply callglfwMakeContextCurrent
once after creating the window.This method of making the context current is generally preferred, however, so we've demonstrated it here for completeness' sake.
Creating a swapchain
All access to window-based rendering commands are abstracted into an object
known as a "swapchain" (from Vulkan terminology), including the default
backbuffers on D3D11 and OpenGL. If we want to present something to screen,
we need to first create a pl_swapchain
.
We can use this swapchain to perform the equivalent of gl*SwapBuffers
:
-
We change this from
glfwWaitEvents
toglfwPollEvents
because we now want to re-run our main loop once per vsync, rather than only when new events arrive. Thepl_swapchain_swap_buffers
call will ensure that this does not execute too quickly. -
The swapchain needs to be resized to fit the size of the window, which in GLFW is handled by listening to a callback. In addition to setting this callback, we also need to inform the swapchain of the initial window size.
Note that the
pl_swapchain_resize
function handles both resize requests and size queries - hence, the actual swapchain size is returned back to the passed variables.
Getting pixels on the screen
With a swapchain in hand, we're now equipped to start drawing pixels to the screen:
-
If
pl_swapchain_start_frame
fails, it typically means the window is hidden, minimized or blocked. This is not a fatal condition, and as such we simply want to process window events until we can resume rendering. -
If
pl_swapchain_submit_frame
fails, it typically means the window has been lost, and further rendering commands are not expected to succeed. As such, in this case, we simply terminate the example program.
Our main render loop has changed into a combination of
pl_swapchain_start_frame
, rendering, and pl_swapchain_submit_frame
. To
start with, we simply use the pl_tex_clear
function to blit a constant
orange color to the framebuffer.
Interlude: Rendering commands
The previous code snippet represented our first foray into the pl_gpu
API.
For more detail on this API, see the GPU API section. But as a
general rule of thumb, all pl_gpu
-level operations are thread safe,
asynchronous (except when returning something to the CPU), and internally
refcounted (so you can destroy all objects as soon as you no longer need the
reference).
In the example loop, pl_swapchain_swap_buffers
is the only operation that
actually flushes commands to the GPU. You can force an early flush with
pl_gpu_flush()
or pl_gpu_finish()
, but other than that, commands will
"queue" internally and complete asynchronously at some unknown point in time,
until forward progress is needed (e.g. pl_tex_download
).
Conclusion
We have demonstrated how to create a window, how to initialize the libplacebo API, create a GPU instance based on OpenGL, and how to write a basic rendering loop that blits a single color to the framebuffer.
Here is a complete transcript of the example we built in this section: