summaryrefslogtreecommitdiffstats
path: root/docs/basic-rendering.md
blob: 09a1f6bb128d46f77599c130cc0232b9afa65c68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# 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`:

``` c linenums="1"
#include <libplacebo/log.h>

pl_log pllog;

int main()
{
    pllog = pl_log_create(PL_API_VER, pl_log_params(
        .log_cb = pl_log_color,
        .log_level = PL_LOG_INFO,
    ));

    // ...

    pl_log_destroy(&pllog);
    return 0;
}
```

!!! note "Compiling"

    You can compile this example with:

    ``` bash
    $ gcc example.c -o example `pkg-config --cflags --libs libplacebo`
    ```

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.

!!! note "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.

!!! note "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](https://code.videolan.org/videolan/libplacebo/-/tree/master/demos). But
for now, we will focus on getting a very simple window on-screen using GLFW:

``` c linenums="1" hl_lines="3 5 6 7 9 17 18 20 21 22 24 25 26 28 29"
// ...

#include <GLFW/glfw3.h>

const char * const title = "libplacebo demo";
int width = 800;
int height = 600;

GLFWwindow *window;

int main()
{
    pllog = pl_log_create(PL_API_VER, pl_log_params(
        .log_level = PL_LOG_INFO,
    ));

    if (!glfwInit())
        return 1;

    window = glfwCreateWindow(width, height, title, NULL, NULL);
    if (!window)
        return 1;

    while (!glfwWindowShouldClose(window)) {
        glfwWaitEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();
    pl_log_destroy(&pllog);
    return 0;
}
```

!!! note "Compiling"

    We now also need to include the glfw3 library to compile this example.

    ``` bash
    $ gcc example.c -o example `pkg-config --cflags --libs glfw3 libplacebo`
    ```

## 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.

``` c linenums="1" hl_lines="3 5-6 15-23 29 36-45"
// ...

pl_opengl opengl;

static bool make_current(void *priv);
static void release_current(void *priv);

int main()
{
    // ...
    window = glfwCreateWindow(width, height, title, NULL, NULL);
    if (!window)
        return 1;

    opengl = pl_opengl_create(pllog, pl_opengl_params(
        .get_proc_addr      = glfwGetProcAddress,
        .allow_software     = true,         // allow software rasterers
        .debug              = true,         // enable error reporting
        .make_current       = make_current, // (1)
        .release_current    = release_current,
    ));
    if (!opengl)
        return 2;

    while (!glfwWindowShouldClose(window)) {
        glfwWaitEvents();
    }

    pl_opengl_destroy(&opengl);
    glfwDestroyWindow(window);
    glfwTerminate();
    pl_log_destroy(&pllog);
    return 0;
}

static bool make_current(void *priv)
{
    glfwMakeContextCurrent(window);
    return true;
}

static void release_current(void *priv)
{
    glfwMakeContextCurrent(NULL);
}
```

1.  Setting this allows the resulting `pl_gpu` to be thread-safe, which
    enables asynchronous transfers to be used. The alternative is to simply
    call `glfwMakeContextCurrent` 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`:

``` c linenums="1" hl_lines="2 4-9 17-22 24-27 30-31 34"
// ...
pl_swapchain swchain;

static void resize_cb(GLFWwindow *win, int new_w, int new_h)
{
    width  = new_w;
    height = new_h;
    pl_swapchain_resize(swchain, &width, &height);
}

int main()
{
    // ...
    if (!opengl)
        return 2;

    swchain = pl_opengl_create_swapchain(opengl, pl_opengl_swapchain_params(
        .swap_buffers   = (void (*)(void *)) glfwSwapBuffers,
        .priv           = window,
    ));
    if (!swchain)
        return 2;

    // (2)
    if (!pl_swapchain_resize(swchain, &width, &height))
        return 2;
    glfwSetFramebufferSizeCallback(window, resize_cb);

    while (!glfwWindowShouldClose(window)) {
        pl_swapchain_swap_buffers(swchain);
        glfwPollEvents(); // (1)
    }

    pl_swapchain_destroy(&swchain);
    pl_opengl_destroy(&opengl);
    glfwDestroyWindow(window);
    glfwTerminate();
    pl_log_destroy(&pllog);
    return 0;
}
```

1.  We change this from `glfwWaitEvents` to `glfwPollEvents` because
    we now want to re-run our main loop once per vsync, rather than only when
    new events arrive.  The `pl_swapchain_swap_buffers` call will ensure
    that this does not execute too quickly.

2.  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:

``` c linenums="1" hl_lines="3-8 15-20"
// ...

static void render_frame(struct pl_swapchain_frame frame)
{
    pl_gpu gpu = opengl->gpu;

    pl_tex_clear(gpu, frame.fbo, (float[4]){ 1.0, 0.5, 0.0, 1.0 });
}

int main()
{
    // ...

    while (!glfwWindowShouldClose(window)) {
        struct pl_swapchain_frame frame;
        while (!pl_swapchain_start_frame(swchain, &frame))
            glfwWaitEvents(); // (1)
        render_frame(frame);
        if (!pl_swapchain_submit_frame(swchain))
            break; // (2)

        pl_swapchain_swap_buffers(swchain);
        glfwPollEvents();
    }

    // ...
}
```

1.  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.

2.  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](#TODO) 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:

??? example "Basic rendering"
    ``` c linenums="1"
    #include <GLFW/glfw3.h>
    
    #include <libplacebo/log.h>
    #include <libplacebo/opengl.h>
    #include <libplacebo/gpu.h>
    
    const char * const title = "libplacebo demo";
    int width = 800;
    int height = 600;
    
    GLFWwindow *window;
    
    pl_log pllog;
    pl_opengl opengl;
    pl_swapchain swchain;
    
    static bool make_current(void *priv);
    static void release_current(void *priv);
    
    static void resize_cb(GLFWwindow *win, int new_w, int new_h)
    {
        width  = new_w;
        height = new_h;
        pl_swapchain_resize(swchain, &width, &height);
    }
    
    static void render_frame(struct pl_swapchain_frame frame)
    {
        pl_gpu gpu = opengl->gpu;
    
        pl_tex_clear(gpu, frame.fbo, (float[4]){ 1.0, 0.5, 0.0, 1.0 });
    }
    
    int main()
    {
        pllog = pl_log_create(PL_API_VER, pl_log_params(
            .log_cb = pl_log_color,
            .log_level = PL_LOG_INFO,
        ));
    
        if (!glfwInit())
            return 1;
    
        window = glfwCreateWindow(width, height, title, NULL, NULL);
        if (!window)
            return 1;
    
        opengl = pl_opengl_create(pllog, pl_opengl_params(
            .get_proc_addr      = glfwGetProcAddress,
            .allow_software     = true,         // allow software rasterers
            .debug              = true,         // enable error reporting
            .make_current       = make_current,
            .release_current    = release_current,
        ));
    
        swchain = pl_opengl_create_swapchain(opengl, pl_opengl_swapchain_params(
            .swap_buffers   = (void (*)(void *)) glfwSwapBuffers,
            .priv           = window,
        ));
        if (!swchain)
            return 2;
    
        if (!pl_swapchain_resize(swchain, &width, &height))
            return 2;
        glfwSetFramebufferSizeCallback(window, resize_cb);
    
        while (!glfwWindowShouldClose(window)) {
            struct pl_swapchain_frame frame;
            while (!pl_swapchain_start_frame(swchain, &frame))
                glfwWaitEvents();
            render_frame(frame);
            if (!pl_swapchain_submit_frame(swchain))
                break;
    
            pl_swapchain_swap_buffers(swchain);
            glfwPollEvents();
        }
    
        pl_swapchain_destroy(&swchain);
        pl_opengl_destroy(&opengl);
        glfwDestroyWindow(window);
        glfwTerminate();
        pl_log_destroy(&pllog);
        return 0;
    }
    
    static bool make_current(void *priv)
    {
        glfwMakeContextCurrent(window);
        return true;
    }
    
    static void release_current(void *priv)
    {
        glfwMakeContextCurrent(NULL);
    }
    ```