summaryrefslogtreecommitdiffstats
path: root/docs/basic-rendering.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/basic-rendering.md')
-rw-r--r--docs/basic-rendering.md432
1 files changed, 432 insertions, 0 deletions
diff --git a/docs/basic-rendering.md b/docs/basic-rendering.md
new file mode 100644
index 0000000..09a1f6b
--- /dev/null
+++ b/docs/basic-rendering.md
@@ -0,0 +1,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);
+ }
+ ```